Repository navigation
Description
The JSON:API spec requires 400 Bad Request for an include path the server can't identify. JsonApi4j silently ignored relationship names that don't exist, at any position in a path: include=pets or include=placeOfBirth.economy returned a 200 with nothing included and no signal to the client. The CD plugin's unsupportedIncludes strategy only covered paths deeper than maxHops.
How it works now: each server checks the relationships of its own resource types, so no shared schema is needed.
- Core: every read request (by id, multiple, and both relationship reads) is checked against the relationships of the requested type. An unknown first segment gets
400 UNSUPPORTED_INCLUDE, with or without the CD plugin. - Later segments: the resolver sends one level of includes per fetch, so the server that owns the type at that point checks the name. The resolver recognises that server's
400 UNSUPPORTED_INCLUDEand maps the name back to the client's full path (economyoncountriesbecomesplaceOfBirth.economy).
Error format: UnsupportedIncludeException moves to base (pro.api4.jsonapi4j.exception). It's rendered by the default error handler with one error object per path: code UNSUPPORTED_INCLUDE, source.parameter: include, and meta.path naming the path so callers don't have to parse detail.
{ "errors": [ {
"status": "400",
"code": "UNSUPPORTED_INCLUDE",
"detail": "Resource type 'countries' has no relationship 'economy'",
"source": { "parameter": "include" },
"meta": { "path": "placeOfBirth.economy" }
} ] }jsonapi4j.cd.unsupportedIncludes now covers unknown relationship names as well as depth:
FAIL(default): a 400 naming each unsupported path.IGNORE: a 200 with everything that could be resolved, and each unsupported path listed inmeta.includedIncompleteasUNSUPPORTED_INCLUDE.- First segment: when the app answers
400 UNSUPPORTED_INCLUDE, the CD filter serves the request once more without those paths and reports them through the newCompoundDocsRequest.rejectedIncludes. - Later segments: the resolver fetches that batch again without the rejected names, repeating if the backend reports them a few at a time.
- First segment: when the app answers
Only a 400 made up entirely of UNSUPPORTED_INCLUDE errors counts as rejected includes. A 400 with any other error is passed to the client unchanged, or treated as an ordinary failed fetch when it comes from a downstream service.
Also changed
- The CD plugin's own 400 mapping is removed, since the default error handler now covers it.
ErrorObjectgains field-name constants.
Tests
- Unit tests for the validator, the default error mapping, the parser, the HTTP client, the resolver, the include checker, the filter's retry and the request wrapper.
- End-to-end tests in
CompoundDocsOperationsTestsforFAIL, run by the Spring, Quarkus and Servlet samples. - A new
UnsupportedIncludesIgnoredTestscoveringIGNOREend to end, run by the Spring sample.
Docs: a new "Unsupported include paths" section on the CD plugin page. The unsupportedIncludes row, the UnsupportedIncludeStrategy Javadoc, the requirements for standalone backends, the exception tree and the Claude Code plugin's configuration reference are updated.
Breaking changes
- Any JsonApi4j app now answers unknown include names with a 400, even without the CD plugin.
UnsupportedIncludeExceptionhas moved topro.api4.jsonapi4j.exception.
Limitations
- A third-party backend that ignores unknown names, or rejects them in some other form, isn't recognised.
- Under
FAIL, an unknown name deep in a path is only found at that hop, after the earlier hops have already run.
Activity
#216: CD: Return 400 for include paths with unknown relationship name…