Skip to content

Return 400 on a malformed request envelope instead of 500 (#19)#21

Open
phillipclapham wants to merge 1 commit into
sunilp:mainfrom
phillipclapham:route-envelope-400
Open

Return 400 on a malformed request envelope instead of 500 (#19)#21
phillipclapham wants to merge 1 commit into
sunilp:mainfrom
phillipclapham:route-envelope-400

Conversation

@phillipclapham

@phillipclapham phillipclapham commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Closes #19.

The /ldp/messages route didn't wrap request.json() / model_validate, so a malformed request envelope — bad JSON, or a body that fails envelope validation — surfaced as a 500 rather than a 400.

This parses the envelope defensively:

  • bad JSON (JSONDecodeError) or a bad encoding (UnicodeDecodeError) → 400
  • a well-encoded body that isn't a valid envelope (ValidationError) → 400
  • anything else (a server/transport fault, or a bug inside handle_message) still propagates as 500 — it's ours, not the peer's, and stays observable

The 400 body is a structured LdpError (transport / MALFORMED_ENVELOPE). The bundled client calls raise_for_status() before parsing, so the non-envelope error shape doesn't affect it. (Happy to switch to a non-retryable category if you'd prefer for client errors.)

To make the route testable without binding a socket, the Starlette app moves into a _build_app() helper; run() is otherwise unchanged.

Tests cover: malformed JSON → 400, invalid envelope → 400, valid envelope still dispatches, a SESSION_PROPOSE→SESSION_ACCEPT round-trip confirming PayloadMode serializes over the wire, identity route unaffected. Starlette is in the [server] extra (not [dev]), so the route tests pytest.importorskip it — they skip rather than fail collection in a dev-only env. (Minor: TestClient emits a StarletteDeprecationWarning about httpx2 on starlette 1.2.1 — left your deps untouched, flagging in case you'd rather pin httpx2.)

Summary by CodeRabbit

  • Bug Fixes

    • Strengthened input validation and error handling for incoming requests, ensuring malformed data is properly rejected with clear error messages to improve server reliability.
  • Tests

    • Added comprehensive test coverage for request validation, error handling, and endpoint functionality to ensure robust behavior across various scenarios.

Wrap envelope parsing in the /ldp/messages route so bad JSON or a body that
fails envelope validation returns a structured LDP error at 400 rather than
leaking as an unhandled 500. Extract the Starlette app into _build_app() so the
route is testable with a client without binding a socket; run() is unchanged
behaviourally.
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cc486b85-9a6f-43bd-9656-0b75f0bae1c8

📥 Commits

Reviewing files that changed from the base of the PR and between c59c1fe and a2be01c.

📒 Files selected for processing (2)
  • sdk/python/src/ldp_protocol/delegate.py
  • sdk/python/tests/test_delegate.py

📝 Walkthrough

Walkthrough

The LdpDelegate HTTP server logic is refactored: app construction moves from run() into a new _build_app() method. A _bad_request() helper is added to return structured 400 responses with LdpError.transport, and POST /ldp/messages now explicitly catches JSON decode and envelope validation errors rather than letting them propagate as 500s. Tests covering these paths are added.

Envelope Parsing Hardening

Layer / File(s) Summary
_build_app(), _bad_request(), and hardened /ldp/messages parsing
sdk/python/src/ldp_protocol/delegate.py
Adds json/TYPE_CHECKING imports and a TYPE_CHECKING-guarded Starlette import; factors app construction into _build_app(); defines _bad_request() returning a JSONResponse with LdpError.transport(...) at status 400; wraps request.json() for JSONDecodeError/UnicodeDecodeError and LdpEnvelope model validation for ValidationError, both mapping to the structured 400 response.
run() wiring and TestHttpRouteEnvelopeParsing
sdk/python/src/ldp_protocol/delegate.py, sdk/python/tests/test_delegate.py
run() delegates app construction to self._build_app() before starting uvicorn; TestHttpRouteEnvelopeParsing uses Starlette TestClient to assert malformed JSON, invalid envelopes, valid dispatch, and unaffected /ldp/identity behavior.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant _build_app as POST /ldp/messages
  participant _bad_request
  participant handle_message

  Client->>"POST /ldp/messages": request with body
  alt malformed JSON / Unicode error
    "POST /ldp/messages"->>_bad_request: JSONDecodeError or UnicodeDecodeError
    _bad_request-->>Client: 400 · LdpError MALFORMED_ENVELOPE
  else envelope schema invalid
    "POST /ldp/messages"->>_bad_request: ValidationError
    _bad_request-->>Client: 400 · LdpError MALFORMED_ENVELOPE
  else valid LdpEnvelope
    "POST /ldp/messages"->>handle_message: LdpEnvelope
    handle_message-->>"POST /ldp/messages": LdpEnvelope response
    "POST /ldp/messages"-->>Client: 200 · model_dump(by_alias=True)
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 Hoppity-hop through the HTTP lane,
Bad JSON now bounces with status 400 plain.
_bad_request catches each mangled byte,
No more 500 gremlins prowling the night.
The envelope's guarded, the route is sound—
This rabbit's code review is homeward bound! 🌙

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly describes the main change: returning HTTP 400 status for malformed request envelopes instead of 500, which matches the primary objective of the PR and the linked issue #19.
Linked Issues check ✅ Passed The PR implementation fully addresses issue #19: it wraps envelope parsing to return HTTP 400 with structured LDP errors for malformed JSON/validation failures instead of unhandled 500 errors, directly matching the stated objectives.
Out of Scope Changes check ✅ Passed All changes are scoped to the objectives: delegate.py refactors the app builder and hardens POST /ldp/messages parsing, and test_delegate.py adds tests validating malformed envelope handling and envelope dispatch, with no unrelated modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HTTP route returns 500 on malformed request envelope; should be 400 / protocol error

1 participant