Skip to content

fix: return 422 instead of 500 for invalid options on the multipart endpoints - #672

Open
DanielNg0729 wants to merge 1 commit into
docling-project:mainfrom
DanielNg0729:fix/form-depends-validation-422
Open

fix: return 422 instead of 500 for invalid options on the multipart endpoints#672
DanielNg0729 wants to merge 1 commit into
docling-project:mainfrom
DanielNg0729:fix/form-depends-validation-422

Conversation

@DanielNg0729

Copy link
Copy Markdown
Contributor

Hi maintainers, while running test on #662, i saw a bug so i try to fix it here. Thanks a lot!
Invalid convert/chunk options submitted as multipart/form-data crash the request with an unhandled-exception 500 instead of being rejected with a 422. The JSON-body endpoints reject the very same payloads with a 422, so the two request paths disagree on what a client error looks like.

FormDepends flattens nested models and dict fields into form parameters that carry JSON strings, and therefore has to validate them itself. It reported every failure as a plain ValueError, which FastAPI has no handler for; the model is also rebuilt with a bare cls(**newdata), so field constraints and model_validator checks raise a pydantic.ValidationError that escapes the same way. Neither is a RequestValidationError, so FastAPI never recognises them as validation problems.

Affected on /v1/convert/file, /v1/convert/file/async, /v1/chunk/file and
/v1/chunk/file/async:

Request Before After
Malformed JSON in a nested option (pdf_heading_hierarchy_options, picture_description_local, vlm_pipeline_model_*, …) 500 422
Well-formed JSON with a bad value inside a nested option 500 422
Malformed JSON in a dict option (ocr_custom_config, layout_custom_config, …) 500 422
Out-of-range value for a constrained field 500 422
Mutually exclusive options set together (picture_description_local + picture_description_api) 500 422
Unparsable callbacks value 500 422

Errors are re-anchored onto the form field that carried them, including the convert_ / chunking_ prefixes used by the chunk endpoints, so the response points at the field the client actually sent:

{
  "detail": [
    {
      "type": "int_parsing",
      "loc": ["body", "convert_pdf_heading_hierarchy_options", "max_level"],
      "msg": "Input should be a valid integer, unable to parse string as an integer",
      "input": "banana"
    }
  ]
}

All invalid fields in one request are reported together rather than only the first. Model-level errors omit input, which on the form path would otherwise echo back the entire options object including every default the caller never sent.

Also fixed: PEP 604 unions were not recognised

is_pydantic_model() and is_json_field() matched only typing.Union, so a field annotated SomeOptions | None rather than Optional[SomeOptions] was never flattened into a JSON form field. get_origin() returns types.UnionType for that spelling, not typing.Union. No shipped option field uses it today, so this is latent rather than user-visible — but the repo's own lint rules push new code toward X | None, and the next option written that way would silently stop accepting nested values over the form path. Both spellings are now matched.

Testing

tests/test_form_options_validation.py covers each row of the table above, that valid options still round-trip unchanged, that the reported location keeps its form-field prefix, and that both union spellings are detected. The tests fail on
main and pass with this change.

…ndpoints

FormDepends decodes nested models and dict fields from JSON strings itself
and reported failures as a plain ValueError, which FastAPI does not translate
into a validation response. Rebuilding the model with cls(**newdata) leaked
pydantic ValidationError the same way, so field constraints and model
validators also crashed the request. Raise RequestValidationError with error
locations re-anchored onto the originating form field.

Also match PEP 604 unions when detecting nested models and dict fields;
get_origin returns types.UnionType rather than typing.Union for X | None,
so such fields were never flattened into JSON form fields.

Signed-off-by: Daniel Nguyen <danielnguyenh07@gmail.com>
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

DCO Check Passed

Thanks @DanielNg0729, all your commits are properly signed off. 🎉

@mergify

mergify Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 Merge protection satisfied — ready to merge.

Show 1 satisfied protection

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert)(?:\(.+\))?(!)?:

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