Skip to content

Add route operation callbacks - #167

Merged
eviltester merged 1 commit into
masterfrom
codex/route-operation-callbacks-166
Aug 23, 2026
Merged

Add route operation callbacks#167
eviltester merged 1 commit into
masterfrom
codex/route-operation-callbacks-166

Conversation

@eviltester

Copy link
Copy Markdown
Owner

Summary

  • Add route-level post-operation callbacks for any, successful, failed, and specific-status outcomes.
  • Provide callback context/result objects with route, data scope, principal, request, response, and entity result details.
  • Add callback failure policy support for fail-request vs log-and-continue handling.

Verification

  • mvn -pl thingifier verify
  • mvn clean verify via commit hook
  • mvn -pl thingifier install -DskipTests

Closes #166

Copilot AI lite review requested due to automatic review settings August 23, 2026 21:36
@eviltester
eviltester merged commit e520109 into master Aug 23, 2026
4 checks passed
@eviltester
eviltester deleted the codex/route-operation-callbacks-166 branch August 23, 2026 21:39

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a4bf8c13b8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +96 to +100
this.authenticatedPrincipals =
Map.copyOf(
authenticatedPrincipals == null
? Map.of()
: new HashMap<>(authenticatedPrincipals));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve null principals when snapshotting auth state

When a route uses an authenticator that returns the supported ThingifierApiAuthenticationResult.authenticated() result without a principal, RouteAuthPolicy stores a null map value and this Map.copyOf throws NullPointerException before the callback's failure-policy guard is entered. Consequently, merely registering any operation callback makes otherwise successful principal-less authenticated requests crash; copy the map using an immutable representation that permits null values, or omit null principal entries.

Useful? React with 👍 / 👎.

Comment on lines +652 to +653
return new RouteOperationCallbackApplier(runtime)
.apply(verb, url, policyResponse, context, lifecycle, request);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Run callbacks after the final HTTP policy pass

For HTTP-backed requests, this invokes callbacks after one response-policy application, but ThingifierHttpApi.httpResponseFor applies RouteApiResponsePolicyApplier again afterward. If a success policy changes 200 to an error status, for example, the callback observes that intermediate status while the second pass can select the matching error policy and expose a different status/body to the client, so afterStatus and result.statusCode() do not describe the promised final route-shaped result. Ensure the HTTP path applies response policy only once or move callback execution after its last application.

Useful? React with 👍 / 👎.

Comment on lines +33 to +34
@Test
void afterSuccessfulOperationReceivesFixedRouteUpdateContextAndInstance() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Split the callback context and result scenarios

This test combines several independently failing rules—fixed-route metadata, data-scope and header/body propagation, outcome classification, and affected-instance retrieval—under a name that itself requires “And,” so a failure does not identify which callback contract regressed. Split these into focused tests for context propagation and result semantics as required by the repository's test-design rules.

AGENTS.md reference: AGENTS.md:L6-L13

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Direct-call callback semantics appear incomplete/inconsistent (missing request details in some direct helpers and ambiguous POST operationType fallback), which can break the expected callback contract.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds route-level synchronous “after operation” callbacks to Thingifier routes, including callback context/result objects and a failure policy to either fail the request or log-and-continue. This implements the extension point described in #166 so applications can run trusted side effects tied directly to a route definition.

Changes:

  • Introduces route callback registration APIs on ThingifierApiRouteRule (any/success/failure/status) with per-callback failure policy.
  • Applies callbacks after route response policies (so callbacks observe the shaped ApiResponse) and before legacy HTTP response hooks.
  • Adds callback context/result value objects plus a test suite covering ordering, auth/data-scope/principal propagation, and failure handling.
File summaries
File Description
thingifier/src/test/java/uk/co/compendiumdev/thingifier/api/callbacks/RouteOperationCallbackTest.java New tests for callback invocation, ordering, context/result contents, and failure policy behavior.
thingifier/src/main/java/uk/co/compendiumdev/thingifier/api/ThingifierRestAPIHandler.java Wires callback execution into the shared direct/HTTP handler pipeline after response policy application.
thingifier/src/main/java/uk/co/compendiumdev/thingifier/api/spec/ThingifierApiRouteRule.java Adds fluent callback registration methods and stores callback definitions in route rules.
thingifier/src/main/java/uk/co/compendiumdev/thingifier/api/callbacks/ThingifierApiOperationResult.java Defines the immutable callback result object (status, operation type, instance accessors, response).
thingifier/src/main/java/uk/co/compendiumdev/thingifier/api/callbacks/ThingifierApiOperationContext.java Defines immutable callback context (route, identifiers, scope/store, principals, request details).
thingifier/src/main/java/uk/co/compendiumdev/thingifier/api/callbacks/ThingifierApiOperationCallbackDefinition.java Stores callback metadata (name/outcome/status) and failure policy, with match logic.
thingifier/src/main/java/uk/co/compendiumdev/thingifier/api/callbacks/ThingifierApiOperationCallback.java Functional interface for callbacks.
thingifier/src/main/java/uk/co/compendiumdev/thingifier/api/callbacks/CallbackFailurePolicy.java Failure policy enum for callback exceptions.
thingifier/src/main/java/uk/co/compendiumdev/thingifier/adapter/http/apihandlers/RouteOperationCallbackApplier.java Implements callback execution and builds callback context/result from runtime/lifecycle/request.
Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +311 to +334
private String operationTypeFor(
final RoutingVerb verb, final ThingifierApiLifecycleContext lifecycle) {
if (lifecycle != null && lifecycle.writeCommand() != null) {
return operationTypeFor(lifecycle.writeCommand());
}
if (verb == RoutingVerb.QUERY) {
return "QUERY";
}
if (verb == RoutingVerb.GET || verb == RoutingVerb.HEAD) {
return "READ";
}
if (verb == RoutingVerb.DELETE) {
return "DELETE";
}
if (verb == RoutingVerb.PATCH) {
return "PATCH";
}
if (verb == RoutingVerb.PUT) {
return "REPLACE";
}
if (verb == RoutingVerb.POST) {
return "WRITE";
}
return "";
Comment on lines 563 to +570
private ApiResponse withAuthorizedResponsePolicy(
final RoutingVerb verb,
final String url,
final ThingifierRequestContext context,
final ThingifierApiLifecycleContext lifecycle,
final Supplier<ApiResponse> action) {
return withAuthorizedResponsePolicy(verb, url, context, lifecycle, null, action);
}
class RouteOperationCallbackTest {

@Test
void afterSuccessfulOperationReceivesFixedRouteUpdateContextAndInstance() {
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.

Route Operation Callbacks

2 participants