feat: Non-JSON bodies#53
Merged
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
Adds first-class support for non-JSON request bodies by dispatching request-body parsing based on the incoming Content-Type (matched against requestBody.content in the OpenAPI spec). This extends the server beyond the previous “always JSON-decode the body” behavior while reusing existing validation/coercion mechanics.
Changes:
- Add built-in parsing for
application/x-www-form-urlencoded(toMap<String,Object>, with property-type coercion) andtext/plain(toString). - Refactor request preparation to select the correct parser by
Content-Type(and hoist shared coercion intoValueCoercion). - Add unit + integration tests and update fixtures/docs to cover the new content types.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/java/com/retailsvc/http/internal/RequestPreparationFilter.java | Dispatch body parsing by Content-Type and reuse shared coercion for parameters. |
| src/main/java/com/retailsvc/http/internal/FormUrlEncodedParser.java | New form-urlencoded parser + schema-driven coercion for object properties. |
| src/main/java/com/retailsvc/http/internal/TextPlainParser.java | New text/plain parser with charset handling. |
| src/main/java/com/retailsvc/http/internal/ContentTypeHeader.java | New helper for parsing Content-Type subtype/parameters. |
| src/main/java/com/retailsvc/http/internal/ValueCoercion.java | New shared coercion helper for parameter and form-field string values. |
| src/test/java/com/retailsvc/http/NonJsonBodyIT.java | Integration tests covering form + text request bodies and error cases. |
| src/test/java/com/retailsvc/http/start/FormEchoHandler.java | Test handler to echo parsed form body. |
| src/test/java/com/retailsvc/http/start/TextEchoHandler.java | Test handler to echo parsed text body. |
| src/test/java/com/retailsvc/http/internal/FormUrlEncodedParserTest.java | Unit tests for parsing and coercion behavior. |
| src/test/java/com/retailsvc/http/internal/TextPlainParserTest.java | Unit tests for charset/default decoding. |
| src/test/java/com/retailsvc/http/internal/ContentTypeHeaderTest.java | Unit tests for header parsing utilities. |
| src/test/java/com/retailsvc/http/internal/ValueCoercionTest.java | Unit tests for coercion success/failure paths. |
| src/test/resources/openapi.json | Add /form-echo and /text-echo operations for tests. |
| src/test/resources/openapi.yaml | Mirror the new operations in the YAML fixture. |
| README.md | Document supported request body content types and coercion behavior. |
| docs/superpowers/specs/2026-05-13-non-json-request-bodies-design.md | Design doc for the feature and intended behaviors. |
| docs/superpowers/plans/2026-05-13-non-json-request-bodies.md | Implementation plan/checklist for the feature. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Double.parseDouble accepts "NaN", "Infinity", and "-Infinity", which are not valid JSON numbers. They would slip past NumberSchema coercion and bypass numeric constraint validation (any comparison with NaN is false). Reject non-finite results with the same type error used for other coercion failures.
Per RFC 9110/2045 media types are case-insensitive. Lower-case the result of ContentTypeHeader.subtype() and lower-case spec content-type keys at parse time so headers like "Application/JSON" or "Text/Plain" match requestBody.content entries regardless of casing. Also fixes a compile error in FormUrlEncodedParser.decodeComponent which referenced a non-existent ValidationException constructor; the URLDecoder failure path now wraps the IllegalArgumentException as a proper ValidationError(/body, decode, ...).
- ContentTypeHeader.parameter: replace dual-continue loop with a single
early-return guarded by an inline key check; extract quote-stripping
into a small unquote() helper.
- FormUrlEncodedParser.decodeComponent: chain the original
IllegalArgumentException via initCause() on the rethrown
ValidationException so the underlying cause is preserved.
- FormUrlEncodedParser.addEntry: replace get()+null-check+put() with
Map.merge(), which is the right primitive for collision handling.
- TextPlainParserTest: use isEmpty() instead of isEqualTo("").
The method returns the full media type (e.g. "application/json"), not the RFC 9110 subtype (e.g. "json"). Rename the method, its single caller's local variable, and the test method names to match what's actually returned.
- Selection is by the full media type (type/subtype), not the RFC 9110 subtype. Note that lookup is case-insensitive. - After coercion, form list values are not strictly List<String> — an integer-array property yields List<Long>, etc. Describe the pre- and post-coercion shape correctly.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



No description provided.