Summary
Add read-only route-level afterResponse hooks that run after Thingifier has produced the final HTTP response, including final status, headers, body where available, response policies, response views, and content negotiation.
Proposed API
thingifier.apiContract()
.route(GET, "/todos")
.afterResponse((ctx, response) -> {
if (response.statusCode() == 200
&& "application/xml".equals(response.contentType())
&& ctx.requestHeaders().get("Accept").contains("application/xml")) {
challenges.pass(ctx.authenticatedPrincipal(), CHALLENGE.GET_ACCEPT_XML);
}
});
Add to ThingifierApiRouteRule:
afterResponse(ThingifierApiResponseCallback callback)
Response view:
public final class ThingifierApiFinalResponse {
int statusCode();
/**
* Final HTTP Content-Type header after content negotiation.
* This is not the internal ApiResponse preferred/structured type.
*/
String contentType();
HttpHeadersBlock headers();
/**
* Final response body when available.
* May be empty or unavailable for HEAD, streaming, or no-body responses.
*/
Optional<String> body();
ApiResponse apiResponse();
}
Context
The callback should receive a read-only route context with:
- original public route path, e.g.
/secret/note
- matched route rule pattern
- resolved Thingifier route info
- resolved target entity and identifier, including fixed identifiers
- relationship target details where applicable
- request headers and query parameters
- active data scope name and store
- authenticated principals when present
For matched-route auth failures, afterResponse should still run for 401 and 403 responses. Principal accessors may return empty/null when authentication failed before a principal was established.
Runtime Order
- Match route
- Resolve scoped session
- Run explicit route authentication
- Run request validators
- Run entity/domain validation
- Execute operation
- Run existing operation callbacks/lifecycle hooks
- Apply route response policies, response views, and content negotiation
- Render final
HttpApiResponse
- Run route-level
afterResponse callbacks
- Run legacy/global HTTP response hooks
Behaviour
afterResponse is read-only in v1.
- It must not mutate the already-produced response.
- If a callback throws, Thingifier logs the exception and preserves the already-produced response.
- Response mutation remains the job of route response policies or legacy HTTP response hooks.
- Hooks run for generated routes, fixed routes, and custom route mappings.
- Hooks run for successful responses and matched-route failures, including
401 and 403.
- Hooks do not run for completely unmatched
404 responses in v1.
- Callbacks are code-only and are not serialized to YAML/model export.
- No OpenAPI changes are required.
Acceptance Criteria
- A route can register an
afterResponse callback.
- The callback receives the final HTTP
Content-Type after negotiation.
- The callback receives the final status code.
- The callback receives final headers.
- The callback can inspect the body when available.
- The callback receives public route path and resolved target route details.
- Fixed routes expose the resolved fixed identifier.
- Matched-route
401/403 responses invoke the callback with no authenticated principal when auth failed.
- Callback exceptions are logged and do not change the response.
- Existing lifecycle hooks, operation callbacks, response policies, and legacy HTTP response hooks remain backwards compatible.
Summary
Add read-only route-level
afterResponsehooks that run after Thingifier has produced the final HTTP response, including final status, headers, body where available, response policies, response views, and content negotiation.Proposed API
Add to
ThingifierApiRouteRule:Response view:
Context
The callback should receive a read-only route context with:
/secret/noteFor matched-route auth failures,
afterResponseshould still run for401and403responses. Principal accessors may return empty/null when authentication failed before a principal was established.Runtime Order
HttpApiResponseafterResponsecallbacksBehaviour
afterResponseis read-only in v1.401and403.404responses in v1.Acceptance Criteria
afterResponsecallback.Content-Typeafter negotiation.401/403responses invoke the callback with no authenticated principal when auth failed.