Skip to content

[bug] Server collapses JSON extractor errors to HTTP 400 #400

Description

@ting-hong-shieh

Symptom

The Rust server converts every Axum JsonRejection into the same HTTP 400 invalid_body response. This changes status codes that clients use to distinguish an unsupported media type from an oversized request.

On current main:

Request Expected Actual
Valid JSON without Content-Type: application/json HTTP 415 HTTP 400 invalid_body
Body larger than the configured 32 MiB limit HTTP 413 HTTP 400 invalid_body

Reproduction

Start the server with any valid route configuration:

switchyard-server --config routes.toml --host 127.0.0.1 --port 4000

Missing content type:

curl -sS -o response.json -w '%{http_code}\n' \
  http://127.0.0.1:4000/v1/chat/completions \
  --data '{"model":"noop-route","messages":[]}'

Observed:

400
{"error":{"code":"invalid_body","message":"Request body must be valid JSON: Expected request with `Content-Type: application/json`","type":"invalid_request_error"}}

Oversized body:

mkfile 33m oversized.bin
curl -sS -o response.json -w '%{http_code}\n' \
  http://127.0.0.1:4000/v1/chat/completions \
  -H 'Content-Type: application/json' \
  --data-binary @oversized.bin

Observed:

400
{"error":{"code":"invalid_body","message":"Request body must be valid JSON: Failed to buffer the request body: length limit exceeded","type":"invalid_request_error"}}

Expected vs. actual

  • Expected: Preserve the extractor rejection status: 415 for a missing JSON content type and 413 for a body over the configured limit. Anthropic requests should render the existing 413 request_too_large error type.
  • Actual: llm_json_body stringifies the rejection, then invalid_body_error always creates a 400 response. The server's explicit Anthropic mapping for StatusCode::PAYLOAD_TOO_LARGE is therefore unreachable for this extractor failure.

Environment

  • Switchyard commit SHA: b256d936f1d77bf13ec9bec399ea0a253e07ca05
  • Rust version: rustc 1.96.1 (31fca3adb 2026-06-26)
  • OS / arch: macOS 26.5.2, arm64
  • Install path: source checkout
  • Inbound format: OpenAI Chat Completions; the same extractor path is shared by all three endpoints
  • Backend: local noop route; no provider credentials

Additional context

The relevant path is crates/switchyard-server/src/lib.rs: endpoint extractors return Result<Json<Value>, JsonRejection>, llm_json_body reduces an error to a string, and invalid_body_error hard-codes StatusCode::BAD_REQUEST.

I searched open and closed issues and pull requests for JSON extractor errors, length limit exceeded, missing content type 415, payload too large 413, and invalid_body status; I found no matching report or in-flight fix. PR #320 is related error-envelope work but does not preserve extractor status codes.

I'm happy to send a PR with focused 400/413/415 regression tests if this behavior should be preserved.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions