fix(server): return JSON-RPC -32700 on non-JSON request body
CI / lint (pull_request) Successful in 45s
CI / helm (pull_request) Successful in 30s
CI / typecheck (pull_request) Successful in 1m2s
CI / quality (pull_request) Successful in 1m7s
CI / build (pull_request) Successful in 56s
CI / security (pull_request) Successful in 1m13s
CI / push-validation (pull_request) Successful in 27s
CI / integration_tests (pull_request) Successful in 3m1s
CI / unit_tests (pull_request) Failing after 6m17s
CI / coverage (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 3s

Wrap `await request.json()` in its own try-except for
`json.JSONDecodeError` so malformed/non-JSON request bodies yield
HTTP 400 with JSON-RPC error code -32700 (Parse error) instead of
propagating unhandled to FastAPI's ServerErrorMiddleware and
returning HTTP 500.

Update the BDD step `step_post_a2a_malformed` to send actual raw
non-JSON bytes (`content=b"not-valid-json"`) so the scenario
exercises the JSON parse failure path rather than the existing
A2aRequest validation path (-32600).

ISSUES CLOSED: #863
This commit is contained in:
2026-05-29 04:37:27 -04:00
committed by Forgejo
parent 41b23ef987
commit c690ae12ad
2 changed files with 20 additions and 3 deletions
+5 -2
View File
@@ -132,8 +132,11 @@ def step_post_a2a(context: Any, operation: str) -> None:
@when("I POST a malformed JSON body to /a2a")
def step_post_a2a_malformed(context: Any) -> None:
# Send a body that cannot be parsed into A2aRequest (missing operation)
context.response = context.test_client.post("/a2a", json={"bad": "data"})
context.response = context.test_client.post(
"/a2a",
content=b"not-valid-json",
headers={"Content-Type": "application/json"},
)
@then("the response status code should be {code:d}")
@@ -13,6 +13,7 @@ at ``/.well-known/agent.json`` per the A2A specification.
from __future__ import annotations
import json
from typing import Any
import structlog
@@ -120,7 +121,20 @@ def create_asgi_app(
schema, dispatches it through the facade, and returns the
:class:`A2aResponse` envelope.
"""
body: dict[str, Any] = await request.json()
try:
body: dict[str, Any] = await request.json()
except json.JSONDecodeError as exc:
logger.warning("a2a.server.parse_error", error=str(exc))
return JSONResponse(
status_code=400,
content={
"jsonrpc": "2.0",
"error": {
"code": -32700,
"message": "Parse error: request body is not valid JSON",
},
},
)
# Accept "operation" as an alias for "method" (proprietary wire format).
if "method" not in body and "operation" in body: