Skip to content

feat(apigateway): map request-contract errors to 400 instead of 500#327

Merged
paulcruse3 merged 3 commits into
masterfrom
feat/contract-errors-400
Jun 9, 2026
Merged

feat(apigateway): map request-contract errors to 400 instead of 500#327
paulcruse3 merged 3 commits into
masterfrom
feat/contract-errors-400

Conversation

@paulcruse3

Copy link
Copy Markdown
Member

What

The apigateway Router now maps request-contract violations to 400 instead of letting them fall through to a generic 500:

  • pydantic.ValidationError (a handler building a request model from a bad body)
  • json.JSONDecodeError (malformed JSON body)

Why

These are client errors, not server faults. Today they hit the Router's catch-all except Exception and return 500 internal service error, which hides the real problem from callers and looks like an outage.

How

  • New except (ValidationError, json.JSONDecodeError) branch in route(), before the generic handler.
  • New __handle_contract_error() sets code=400 and surfaces each failure via response.set_error(...), so the response carries the standard errors[] contract (one entry per invalid field for validation; a single body entry for bad JSON). Routed through on_error so apps can still hook it.
  • ApiException (e.g. 418) and unhandled exceptions (500) are unchanged.

Tests

  • test_pydantic_validation_error_returns_400, test_invalid_json_body_returns_400 (mapping router) + 2 mock handlers and 2 mock_request builders.
  • Full apigateway suite green locally: 291 passed.

Pydantic ValidationError and malformed-JSON (json.JSONDecodeError) are client
contract violations, not server faults, but they fell through the Router's
generic except into a 500. Catch them explicitly and respond 400, surfacing
each failure via response.set_error so the response carries the standard
errors[] contract (one entry per invalid field for validation). Routed through
on_error so services can still hook it.

Adds mapping-router tests + mock handlers for the validation and bad-JSON paths.
ApiException (418) and unhandled exceptions (500) are unchanged.
…helpers

Pull the isinstance/loop/fallback out of __handle_contract_error into a pure
__contract_errors builder, and share the on_error-or-log tail via a single
__dispatch_error used by both error handlers. Each function is now linear.
@sonarqubecloud

sonarqubecloud Bot commented Jun 9, 2026

Copy link
Copy Markdown

@paulcruse3 paulcruse3 merged commit d8189fc into master Jun 9, 2026
8 checks passed
@paulcruse3 paulcruse3 deleted the feat/contract-errors-400 branch June 9, 2026 18:44
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.

1 participant