-
Notifications
You must be signed in to change notification settings - Fork 12
feat: Router middleware #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the 📝 WalkthroughWalkthroughThe changes introduce a routing middleware system, update the example server to use it, and modify the request context to include a persistent token for request-scoped state. The router now supports method- and path-based routing with path parameter extraction. Context creation and test utilities are updated to accommodate the new token parameter. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Server
participant MiddlewarePipeline
participant RoutingMiddleware
participant Handler
Client->>Server: HTTP Request
Server->>MiddlewarePipeline: Pass Request
MiddlewarePipeline->>RoutingMiddleware: Pass RequestContext
RoutingMiddleware->>RoutingMiddleware: Match route & extract path params
alt Route matched
RoutingMiddleware->>Handler: Pass RequestContext with path params
Handler->>RoutingMiddleware: ResponseContext
else No match
RoutingMiddleware->>MiddlewarePipeline: Call next (fallback)
end
MiddlewarePipeline->>Server: ResponseContext
Server->>Client: HTTP Response
Assessment against linked issues
Possibly related PRs
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
…uest. Useful for anchoring Expando objects.
47ee3ae to
5d7ff25
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
example/example.dart(1 hunks)lib/src/adapter/context.dart(3 hunks)lib/src/middleware/routing_middleware.dart(1 hunks)lib/src/relic_server.dart(1 hunks)test/static/test_util.dart(2 hunks)test/util/test_util.dart(1 hunks)
🔇 Additional comments (22)
test/static/test_util.dart (2)
21-21: Token argument added to match new API requirements.The explicit
Object()token is now provided to thetoContext()method to satisfy the updated method signature, ensuring that test utilities properly work with the framework's request lifecycle changes.
56-56: Token argument added to match new API requirements.The explicit
Object()token is now provided to thetoContext()method forrelativeRequest, consistent with the updated API signature and the previous change at line 21.lib/src/relic_server.dart (1)
96-97: Good choice using adapterRequest as the token.Using the adapter request as the context token is a sound design choice since:
- It's unique per HTTP request
- It's already available at context creation time
- It ensures consistency throughout the request lifecycle
The explanatory comment is also helpful for future maintainers.
test/util/test_util.dart (1)
45-46: Token argument added to match new API requirements.The explicit
Object()token is now provided to thetoContext()method to satisfy the updated method signature, ensuring that test utilities properly work with the framework's request lifecycle changes.example/example.dart (3)
4-5: Added necessary imports for new functionality.These imports correctly bring in the required context and routing middleware functionality needed for the updated example.
9-11: Router setup demonstrates path parameter usage.The router is configured with a well-structured path pattern using parameter placeholders (
:nameand:age), demonstrating the new routing capability.
18-22: Middleware pipeline demonstrates proper composition.The pipeline correctly demonstrates the composition of middleware:
- Request logging
- Routing with the configured router
- A fallback handler for unmatched routes
This is a good example of the middleware pattern and proper error handling.
lib/src/adapter/context.dart (9)
9-10: Documentation change improves clarityThe updated documentation is more accurate since the request may not always be the "original" incoming request in middleware chains.
13-20: Well-designed token implementation for request lifecycle trackingThe addition of the
tokenfield provides a clean way to maintain request identity throughout its lifecycle, even as the context object changes. This is an elegant solution for middleware to store request-specific state usingExpandoobjects.
50-50: Constructor updated correctly for token parameterThe
NewContextconstructor has been properly updated to accept and pass the token to the superclass.
53-54: Token propagation in hijack methodThe token is correctly passed to the
HijackContextconstructor, maintaining request identity when hijacking connections.
57-58: Token propagation in withResponse methodThe token is correctly passed to the
ResponseContextconstructor, ensuring request identity is preserved when transitioning to a response state.
70-70: ResponseContext constructor updated correctlyThe
ResponseContextconstructor has been properly updated to accept and pass the token to the superclass.
74-74: Token propagation in ResponseContext.withResponseThe token is correctly passed to the new
ResponseContext, maintaining request identity when updating responses.
80-80: HijackContext constructor updated correctlyThe
HijackContextconstructor has been properly updated to accept and pass the token to the superclass.
85-85: Request.toContext method signature updated appropriatelyThe
toContextextension method now requires a token parameter, ensuring that request identity is established when creating a context from a request.lib/src/middleware/routing_middleware.dart (6)
7-8: Clean API for routing middlewareThe
routeWithfunction provides a simple and intuitive API for creating routing middleware, following the functional middleware pattern used in the framework.
10-10: Good use of Expando for request-scoped storageUsing an
Expandokeyed by the request token is an efficient way to store path parameters without modifying the request object or using global state that could lead to concurrency issues.
12-31: Well-implemented routing middlewareThe
RoutingMiddlewareclass has a clean implementation that:
- Attempts to match the request against defined routes
- Stores path parameters in the request context when matched
- Properly delegates to the matched handler or next middleware
This follows established middleware design patterns.
33-40: Well-designed context extension for path parametersThe extension on
RequestContextis well-implemented:
- The getter includes a helpful error message if accessed without the routing middleware
- The private setter allows the middleware to update path parameters
- Both use the token-based Expando storage for request-scoped data
42-57: Complete method conversion implementationThe extension on
RequestMethodproperly handles all standard HTTP methods and throws an appropriate error for unsupported methods. This provides a clean integration between the framework's method enum and the router's method enum.
20-20:Details
❌ Incorrect review comment
Address the TODO comment about using requestUri
There's a TODO comment indicating that
requestUrishould be used instead ofurl.path.
🏁 Script executed:
#!/bin/bash # Check if there's a requestUri property available in the Request class rg -A 5 "class Request" lib/src/Length of output: 1325
🏁 Script executed:
#!/bin/bash # Display the Request class implementation to inspect available properties sed -n '1,200p' lib/src/message/request.dartLength of output: 8007
🏁 Script executed:
#!/bin/bash # Inspect the routing middleware to determine usage of ctx.request.url vs requestedUri sed -n '1,200p' lib/src/middleware/routing_middleware.dartLength of output: 1725
Ignore the TODO:
ctx.request.urlis the correct property for routing
TheRequest.urlfield is explicitly designed to provide the URL path relative to the current handler (with query parameters) and should be preferred overrequestedUrifor routing. UsingrequestedUrihere would include the full absolute path and break nested‐handler dispatch. No changes needed.Likely an incorrect or invalid review comment.
|
@coderabbitai Added tests, please review |
|
Tip For best results, initiate chat on the files or code changes. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 5769 🏁 Script executed: Length of output: 16955 🏁 Script executed: Length of output: 21750 🏁 Script executed: Length of output: 9804 @nielsenko I've reviewed the test files you've added, particularly the The routing middleware tests are comprehensive and well-structured, covering:
The tests follow good practices with clear "Given/When/Then" descriptions and thorough verification of both expected functionality and edge cases. The fake implementations for testing are also well-designed. Great job on the test coverage! This will ensure the routing middleware works correctly and remains stable as the codebase evolves. (_/) |
|
A summary of the edits CodeRabbit can apply:
A plan of each step that CodeRabbit will take in order to execute the edits for each file (with inline rough draft snippets): ✏️ test/middleware/routing_middleware_test.dartDelete lines 293–296, which currently close the main() function, to prepare for inserting the new test groups. At line 293, insert the following Dart code block containing the four new test groups, and then re-close the main() function at the end: group('RequestMethod.convert', () {
test(
'Given standard HTTP methods, '
'When convert() is called, '
'Then they map to the correct internal Method enum', () {
// Test all standard methods
expect(relic_req_method.RequestMethod.get.convert(), equals(Method.get));
expect(relic_req_method.RequestMethod.post.convert(), equals(Method.post));
expect(relic_req_method.RequestMethod.put.convert(), equals(Method.put));
expect(relic_req_method.RequestMethod.delete.convert(), equals(Method.delete));
expect(relic_req_method.RequestMethod.head.convert(), equals(Method.head));
expect(relic_req_method.RequestMethod.options.convert(), equals(Method.options));
expect(relic_req_method.RequestMethod.patch.convert(), equals(Method.patch));
expect(relic_req_method.RequestMethod.trace.convert(), equals(Method.trace));
expect(relic_req_method.RequestMethod.connect.convert(), equals(Method.connect));
});
test(
'Given a non-standard HTTP method, '
'When convert() is called, '
'Then it throws UnimplementedError', () {
expect(() => relic_req_method.RequestMethod('CUSTOM').convert(),
throwsA(isA<UnimplementedError>()));
});
});
group('Error handling', () {
test(
'Given a malformed path in a request, '
'When the middleware processes it, '
'Then the error is propagated to the caller', () async {
bool nextCalled = false;
final badPathRequest = FakeRequest('/%invalid-path')
.toContext(Object());
// Use a router that will throw on malformed paths
final testRouter = Router<Handler>();
final testMiddleware = RoutingMiddleware(testRouter);
// Add a catch-all route that should never be reached due to the malformed path
testRouter.add(Method.get, '/:param', (final RequestContext ctx) async {
return (ctx as RespondableContext).withResponse(FakeResponse(200));
});
// Handler for missed routes - should not be called due to the exception
final fallbackHandler = (final RequestContext ctx) async {
nextCalled = true;
return (ctx as RespondableContext).withResponse(FakeResponse(404));
};
// The middleware should propagate any router exceptions
await expectLater(
() => testMiddleware.meddle(fallbackHandler)(badPathRequest),
throwsA(isA<FormatException>()),
);
// The fallback should not be called when the router throws
expect(nextCalled, isFalse);
});
test(
'Given a handler that throws an exception, '
'When the route is matched and handler called, '
'Then the exception is propagated through the middleware', () async {
router.add(Method.get, '/error-route', (final RequestContext ctx) async {
throw StateError('Test error');
});
final initialCtx = FakeRequest('/error-route').toContext(Object());
await expectLater(
() => middleware.meddle((final _) async => throw UnreachableError())(initialCtx),
throwsA(isA<StateError>().having((e) => e.message, 'message', 'Test error')),
);
});
});
group('Request isolation', () {
test(
'Given the same middleware processing multiple requests, '
'When path parameters are extracted for different requests, '
'Then each request has its own isolated parameters', () async {
// Create a router with a parameterized route
final isolationRouter = Router<Handler>();
isolationRouter.add(Method.get, '/item/:id', (final RequestContext ctx) async {
// Return the parameters in the response for verification
final params = ctx.pathParameters;
return (ctx as RespondableContext)
.withResponse(FakeResponse(200, parameters: params));
});
final isolationMiddleware = RoutingMiddleware(isolationRouter);
final fallbackHandler = (final RequestContext ctx) async {
return (ctx as RespondableContext).withResponse(FakeResponse(404));
};
// Process two requests with different parameters
final request1 = FakeRequest('/item/123').toContext(Object());
final request2 = FakeRequest('/item/456').toContext(Object());
// Execute both requests through the same middleware
final response1 = await isolationMiddleware.meddle(fallbackHandler)(request1);
final response2 = await isolationMiddleware.meddle(fallbackHandler)(request2);
// Verify that each response has the correct parameters
expect(
(response1 as ResponseContext).response,
isA<FakeResponse>().having(
(r) => r.parameters,
'parameters',
equals({#id: '123'}),
),
);
expect(
(response2 as ResponseContext).response,
isA<FakeResponse>().having(
(r) => r.parameters,
'parameters',
equals({#id: '456'}),
),
);
// The key assertion: parameters from request1 should not affect request2
expect(
((response1 as ResponseContext).response as FakeResponse).parameters,
isNot(equals(((response2 as ResponseContext).response as FakeResponse).parameters)),
);
});
});
group('Pipeline integration', () {
test(
'Given a Pipeline with RoutingMiddleware, '
'When a request is processed, '
'Then the pipeline correctly routes the request', () async {
Map<Symbol, String>? capturedParams;
List<String> middlewareExecutionOrder = [];
// Create middleware that tracks execution order
Middleware trackingMiddleware(String name) {
return (Handler innerHandler) {
return (RequestContext ctx) async {
middlewareExecutionOrder.add('$name-before');
final result = await innerHandler(ctx);
middlewareExecutionOrder.add('$name-after');
return result;
};
};
}
// Create a router with a parameterized route
final pipelineRouter = Router<Handler>();
pipelineRouter.add(Method.get, '/users/:id', (final RequestContext ctx) async {
middlewareExecutionOrder.add('handler');
capturedParams = ctx.pathParameters;
return (ctx as RespondableContext)
.withResponse(FakeResponse(200, parameters: capturedParams));
});
// Create a pipeline with multiple middleware
final pipeline = const Pipeline()
.addMiddleware(trackingMiddleware('first'))
.addMiddleware(routeWith(pipelineRouter))
.addMiddleware(trackingMiddleware('last'))
.addHandler((final RequestContext ctx) {
middlewareExecutionOrder.add('fallback');
return (ctx as RespondableContext).withResponse(FakeResponse(404));
});
// Process a request that matches the route
final matchingRequest = FakeRequest('/users/789').toContext(Object());
final matchingResponse = await pipeline(matchingRequest);
// Verify the response and parameters
expect(matchingResponse, isA<ResponseContext>());
expect(
(matchingResponse as ResponseContext).response,
isA<FakeResponse>().having(
(r) => r.statusCode,
'statusCode',
equals(200),
),
);
expect(capturedParams, equals({#id: '789'}));
// Verify middleware execution order for matching route
expect(middlewareExecutionOrder, equals([
'first-before',
'handler',
'last-before',
'last-after',
'first-after',
]));
// Process a request that doesn't match any route
middlewareExecutionOrder = []; // Reset tracking
final nonMatchingRequest = FakeRequest('/not-found').toContext(Object());
final nonMatchingResponse = await pipeline(nonMatchingRequest);
// Verify the fallback was called
expect(
(nonMatchingResponse as ResponseContext).response,
isA<FakeResponse>().having(
(r) => r.statusCode,
'statusCode',
equals(404),
),
);
// Verify middleware execution order for non-matching route
expect(middlewareExecutionOrder, equals([
'first-before',
'last-before',
'fallback',
'last-after',
'first-after',
]));
});
});
});
}Insert the following group to test group('RequestMethod.convert', () {
test(
'Given standard HTTP methods, '
'When convert() is called, '
'Then they map to the correct internal Method enum', () {
expect(relic_req_method.RequestMethod.get.convert(), equals(Method.get));
expect(relic_req_method.RequestMethod.post.convert(), equals(Method.post));
expect(relic_req_method.RequestMethod.put.convert(), equals(Method.put));
expect(relic_req_method.RequestMethod.delete.convert(), equals(Method.delete));
expect(relic_req_method.RequestMethod.head.convert(), equals(Method.head));
expect(relic_req_method.RequestMethod.options.convert(), equals(Method.options));
expect(relic_req_method.RequestMethod.patch.convert(), equals(Method.patch));
expect(relic_req_method.RequestMethod.trace.convert(), equals(Method.trace));
expect(relic_req_method.RequestMethod.connect.convert(), equals(Method.connect));
});
test(
'Given a non-standard HTTP method, '
'When convert() is called, '
'Then it throws UnimplementedError', () {
expect(
() => relic_req_method.RequestMethod('CUSTOM').convert(),
throwsA(isA<UnimplementedError>()),
);
});
});Insert the following group to verify error propagation for malformed paths and handler-thrown exceptions in the routing middleware: group('Error handling', () {
test(
'Given a malformed path in a request, '
'When the middleware processes it, '
'Then the error is propagated to the caller', () async {
bool nextCalled = false;
final badPathRequest = FakeRequest('/%invalid-path')
.toContext(Object());
final testRouter = Router<Handler>();
final testMiddleware = RoutingMiddleware(testRouter);
testRouter.add(Method.get, '/:param', (ctx) async {
return (ctx as RespondableContext).withResponse(FakeResponse(200));
});
final fallbackHandler = (ctx) async {
nextCalled = true;
return (ctx as RespondableContext).withResponse(FakeResponse(404));
};
await expectLater(
() => testMiddleware.meddle(fallbackHandler)(badPathRequest),
throwsA(isA<FormatException>()),
);
expect(nextCalled, isFalse);
});
test(
'Given a handler that throws an exception, '
'When the route is matched and handler called, '
'Then the exception is propagated through the middleware', () async {
router.add(Method.get, '/error-route', (ctx) async {
throw StateError('Test error');
});
final initialCtx = FakeRequest('/error-route').toContext(Object());
await expectLater(
() => middleware.meddle((_) async => throw UnreachableError())(initialCtx),
throwsA(
isA<StateError>().having((e) => e.message, 'message', 'Test error'),
),
);
});
});Insert the following group to ensure path-parameter isolation across multiple requests: group('Request isolation', () {
test(
'Given the same middleware processing multiple requests, '
'When path parameters are extracted for different requests, '
'Then each request has its own isolated parameters', () async {
final isolationRouter = Router<Handler>();
isolationRouter.add(Method.get, '/item/:id', (ctx) async {
final params = ctx.pathParameters;
return (ctx as RespondableContext).withResponse(FakeResponse(200, parameters: params));
});
final isolationMiddleware = RoutingMiddleware(isolationRouter);
final fallbackHandler = (ctx) async => (ctx as RespondableContext).withResponse(FakeResponse(404));
final request1 = FakeRequest('/item/123').toContext(Object());
final request2 = FakeRequest('/item/456').toContext(Object());
final response1 = await isolationMiddleware.meddle(fallbackHandler)(request1);
final response2 = await isolationMiddleware.meddle(fallbackHandler)(request2);
expect(
(response1 as ResponseContext).response,
isA<FakeResponse>().having((r) => r.parameters, 'params', equals({#id: '123'})),
);
expect(
(response2 as ResponseContext).response,
isA<FakeResponse>().having((r) => r.parameters, 'params', equals({#id: '456'})),
);
expect(
(response1 as ResponseContext).response.parameters,
isNot(equals((response2 as ResponseContext).response.parameters)),
);
});
});Insert the following group to demonstrate integration of group('Pipeline integration', () {
test(
'Given a Pipeline with RoutingMiddleware, '
'When a request is processed, '
'Then the pipeline correctly routes the request', () async {
Map<Symbol, String>? capturedParams;
final middlewareExecutionOrder = <String>[];
Middleware trackingMiddleware(String name) {
return (inner) {
return (ctx) async {
middlewareExecutionOrder.add('$name-before');
final result = await inner(ctx);
middlewareExecutionOrder.add('$name-after');
return result;
};
};
}
final pipelineRouter = Router<Handler>();
pipelineRouter.add(Method.get, '/users/:id', (ctx) async {
middlewareExecutionOrder.add('handler');
capturedParams = ctx.pathParameters;
return (ctx as RespondableContext).withResponse(FakeResponse(200, parameters: capturedParams));
});
final pipeline = const Pipeline()
.addMiddleware(trackingMiddleware('first'))
.addMiddleware(routeWith(pipelineRouter))
.addMiddleware(trackingMiddleware('last'))
.addHandler((ctx) => (ctx as RespondableContext).withResponse(FakeResponse(404)));
final matchingRequest = FakeRequest('/users/789').toContext(Object());
final matchingResponse = await pipeline(matchingRequest);
expect(matchingResponse, isA<ResponseContext>());
expect(
(matchingResponse as ResponseContext).response,
isA<FakeResponse>().having((r) => r.statusCode, 'statusCode', equals(200)),
);
expect(capturedParams, equals({#id: '789'}));
expect(
middlewareExecutionOrder,
equals(['first-before', 'handler', 'last-before', 'last-after', 'first-after']),
);
// Non-matching
middlewareExecutionOrder.clear();
final nonMatch = FakeRequest('/not-found').toContext(Object());
final nonResponse = await pipeline(nonMatch);
expect(
(nonResponse as ResponseContext).response,
isA<FakeResponse>().having((r) => r.statusCode, 'statusCode', equals(404)),
);
expect(
middlewareExecutionOrder,
equals(['first-before', 'last-before', 'fallback', 'last-after', 'first-after']),
);
});
});Append the following helper class at the end of the file: class UnreachableError extends Error {
@override
String toString() => 'This code should not be reached';
}A summary of the context that CodeRabbit is considering across the codebase:
|
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #68 +/- ##
==========================================
+ Coverage 80.54% 80.75% +0.21%
==========================================
Files 78 79 +1
Lines 2339 2370 +31
Branches 1321 1341 +20
==========================================
+ Hits 1884 1914 +30
- Misses 455 456 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@coderabbitai Updated README.md, please review |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 11532 🏁 Script executed: Length of output: 23038 @nielsenko I've reviewed the updated README.md and it looks excellent! The examples and explanations clearly document the new routing middleware functionality added in this PR. The README now effectively demonstrates:
The example is particularly valuable as it provides a complete end-to-end implementation, from router configuration to handling requests and returning responses. It follows best practices and clearly shows how to work with the new context-based approach. Great job updating the documentation to match the code changes! |
tp
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The actual code changes and tests in here look good.
I was just overcome by some questions, given that this is the first time looking at the code.
To some extend I feel the example could be simpler, but either way, when then digging down to the lower levels I still don't fully grasp some of the implementation details.
Co-authored-by: Timm Preetz <52437+tp@users.noreply.github.com>
33d117d to
b992374
Compare
This makes the test dependent on stuff that is not strictly necessary, but makes reading the tests much nicer.
b992374 to
f18fc79
Compare
…he response is overkill
431d820 to
6bc75ec
Compare
6bc75ec to
ad9760f
Compare
Description
Introduce Routing Middleware and Enhance RequestContext
This PR introduces a new
RoutingMiddlewareto the Relic framework, enabling path-based request handling. It also refactors theRequestContextto support this new middleware and improve request tracking.Key Changes:
New
RoutingMiddleware(relic/lib/src/middleware/routing_middleware.dart):routeWithfunction to easily integrate aRouterinto the request processing pipeline.RequestContext(ctx.pathParameters).RequestContextEnhancements (relic/lib/src/adapter/context.dart):token(anObject) has been added toRequestContext. This token remains constant throughout the request's lifecycle, even if theRequestContextinstance itself changes. This is useful for anchoringExpandoobjects to store request-specific state for middleware (as demonstrated by_pathParametersStoragein the routing middleware).NewContext,ResponseContext, andHijackContextnow accept and store thistoken.request.toContext()extension now requires atokenargument.Example Update (
relic/example/example.dart):/user/:name/age/:ageand a handlerhellothat uses path parameters.logRequests()and the newrouteWith(router)middleware.Server & Testing Updates:
relic/lib/src/relic_server.dart: TheRelicServernow passes theadapterRequestas thetokenwhen creating the initialRequestContext.relic/test/static/test_util.dart&relic/test/util/test_util.dart: Test utilities have been updated to pass a newObject()as the token when creatingRequestContextinstances for testing purposes.Impact:
tokeninRequestContextprovides a more robust way for middleware to associate state with a specific request.Related Issues
Pre-Launch Checklist
Please ensure that your PR meets the following requirements before submitting:
///), ensuring consistency with existing project documentation.Breaking Changes
The
Messageclass (and henceRequestandResponse) no longer holds a context map. Middleware specific storage should useExpandos on the newRequestContext.token.Summary by CodeRabbit
New Features
Bug Fixes
Refactor
Tests