From f3afe95e3d572ab0c647d92d1999d1de41ca3987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Tue, 28 Oct 2025 14:01:15 +0100 Subject: [PATCH 01/11] fix: Drop final _ & __ --- example/basic/body_example.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/basic/body_example.dart b/example/basic/body_example.dart index 392d8d61..49b6bbbc 100644 --- a/example/basic/body_example.dart +++ b/example/basic/body_example.dart @@ -50,7 +50,7 @@ Future main() async { '/static/**', StaticHandler.directory( Directory('example/static_files'), - cacheControl: (final _, final __) => CacheControlHeader(maxAge: 3600), + cacheControl: (_, _) => CacheControlHeader(maxAge: 3600), ).asHandler, ); @@ -59,7 +59,7 @@ Future main() async { '/logo', StaticHandler.file( File('example/static_files/logo.svg'), - cacheControl: (final _, final __) => CacheControlHeader(maxAge: 86400), + cacheControl: (_, _) => CacheControlHeader(maxAge: 86400), ).asHandler, ); From c670272b1465d0fa1157da5df51cf61a1cc77127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 3 Oct 2025 13:37:18 +0200 Subject: [PATCH 02/11] docs: Nits --- lib/src/adapter/context.dart | 25 +++++-------------------- lib/src/middleware/middleware.dart | 4 +--- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/lib/src/adapter/context.dart b/lib/src/adapter/context.dart index 8596b407..24351c76 100644 --- a/lib/src/adapter/context.dart +++ b/lib/src/adapter/context.dart @@ -93,27 +93,12 @@ final class NewContext extends RequestContext ConnectContext connect(final WebSocketCallback c) => ConnectContext._(request, token, c); - @override - ResponseContext respond(final Response r) => - ResponseContext._(request, token, r); - - /// Creates a new [NewContext] with a different [Request] while preserving - /// the same [token]. - /// - /// This is a convenience method for middleware that needs to rewrite a - /// request before passing it to the inner handler. Instead of the low-level - /// pattern: - /// ```dart - /// final rewrittenRequest = req.copyWith(requestedUri: newRequested); - /// return await inner(rewrittenRequest.toContext(ctx.token)); - /// ``` - /// - /// You can use the more readable pattern: - /// ```dart - /// final rewrittenRequest = req.copyWith(requestedUri: newRequested); - /// return await inner(ctx.withRequest(rewrittenRequest)); - /// ``` NewContext withRequest(final Request req) => NewContext._(req, token); + + @override + ResponseContext respond(final Response response) => + ResponseContext._(request, token, response); + /// Creates a new [RequestContext] with a different [Request]. } /// A sealed base class for contexts that represent a handled request. diff --git a/lib/src/middleware/middleware.dart b/lib/src/middleware/middleware.dart index 3473710f..0e81e74b 100644 --- a/lib/src/middleware/middleware.dart +++ b/lib/src/middleware/middleware.dart @@ -95,9 +95,7 @@ typedef Middleware = Handler Function(Handler innerHandler); /// create a new response object. /// /// If provided, [onError] receives errors thrown by the inner handler. It -/// does not receive errors thrown by [onRequest] or [onResponse], nor -/// does it receive [HijackException]s. It can either return a new response or -/// throw an error. +/// does not receive errors thrown by [onRequest] or [onResponse]. /// /// ## Examples /// From 40b65d0f4345b7a4174be9aee6f793834211de79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 3 Oct 2025 13:47:20 +0200 Subject: [PATCH 03/11] docs: Add doc comments to LookupResult hierarchy --- lib/src/router/lookup_result.dart | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/lib/src/router/lookup_result.dart b/lib/src/router/lookup_result.dart index 88a13d7d..5603c74e 100644 --- a/lib/src/router/lookup_result.dart +++ b/lib/src/router/lookup_result.dart @@ -2,30 +2,56 @@ import 'method.dart'; import 'normalized_path.dart'; import 'path_trie.dart'; +/// Represents the result of looking up a route in the router. +/// +/// This is a sealed class hierarchy with three possible outcomes: +/// +/// - [RouterMatch]: The route was found and matched successfully +/// - [PathMiss]: No route exists for the given path +/// - [MethodMiss]: A route exists for the path, but not for the HTTP method sealed class LookupResult { const LookupResult(); } +/// Base class for lookup failures. +/// +/// A [Miss] indicates the lookup did not find a matching handler. +/// See [PathMiss] and [MethodMiss] for specific failure types. sealed class Miss extends LookupResult { const Miss(); } +/// Indicates that no route exists for the requested path. +/// +/// This typically results in a 404 Not Found response. final class PathMiss extends Miss { + /// The normalized path that was not found. final NormalizedPath path; const PathMiss(this.path); } +/// Indicates that a route exists for the path, but not for the HTTP method. +/// +/// This typically results in a 405 Method Not Allowed response with an +/// Allow header containing the [allowed] methods. final class MethodMiss extends Miss { + /// The set of HTTP methods that are allowed for this path. final Set allowed; const MethodMiss(this.allowed); } +/// A successful route match containing the handler value and extracted parameters. final class RouterMatch extends TrieMatch implements LookupResult { RouterMatch(super.value, super.parameters, super.matched, super.remaining); } +/// Extension providing convenient type casting for [LookupResult] instances. extension LookupResultExtension on LookupResult { + /// Casts this result to a [TrieMatch]. + /// + /// Throws a [TypeError] if this is not a [RouterMatch]. + /// Only use this when you've already verified the lookup succeeded. TrieMatch get asMatch => this as RouterMatch; } From 67cd22801d1432a51a4d561e768787770f80305c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 3 Oct 2025 13:54:25 +0200 Subject: [PATCH 04/11] refactor: Replace single-letter parameters with descriptive names Changed 'r' to 'response' and 'c' to 'callback' throughout context.dart for better code clarity and consistency. --- lib/src/adapter/context.dart | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/src/adapter/context.dart b/lib/src/adapter/context.dart index 24351c76..30a8fb02 100644 --- a/lib/src/adapter/context.dart +++ b/lib/src/adapter/context.dart @@ -37,17 +37,17 @@ abstract interface class RespondableContext implements _RequestContextInterface { /// Transitions the context to a state where a response has been associated. /// - /// Takes a [Response] object [r] and returns a [ResponseContext]. - ResponseContext respond(final Response r); + /// Takes a [response] and returns a [ResponseContext]. + ResponseContext respond(final Response response); } /// An interface for request contexts that allow hijacking the underlying connection. abstract interface class HijackableContext implements _RequestContextInterface { /// Takes control of the underlying communication channel (e.g., socket). /// - /// The provided [callback] [c] will be invoked with a [StreamChannel] + /// The provided [callback] will be invoked with a [StreamChannel] /// allowing direct interaction with the connection. Returns a [HijackContext]. - HijackContext hijack(final HijackCallback c); + HijackContext hijack(final HijackCallback callback); } /// Represents the initial state of a request context before it has been @@ -81,24 +81,24 @@ final class NewContext extends RequestContext NewContext._(super.request, super.token) : super._(); @override - HijackContext hijack(final HijackCallback c) => - HijackContext._(request, token, c); + HijackContext hijack(final HijackCallback callback) => + HijackContext._(request, token, callback); /// Transitions this context to a state where a duplex stream (e.g., WebSocket) /// connection is established. /// - /// The provided [DuplexStreamCallback] [c] will be invoked with a + /// The provided [WebSocketCallback] will be invoked with a /// [RelicWebSocket] for managing the bi-directional communication. /// Returns a [ConnectContext]. - ConnectContext connect(final WebSocketCallback c) => - ConnectContext._(request, token, c); + ConnectContext connect(final WebSocketCallback callback) => + ConnectContext._(request, token, callback); + /// Creates a new [RequestContext] with a different [Request]. NewContext withRequest(final Request req) => NewContext._(req, token); @override ResponseContext respond(final Response response) => ResponseContext._(request, token, response); - /// Creates a new [RequestContext] with a different [Request]. } /// A sealed base class for contexts that represent a handled request. @@ -118,8 +118,8 @@ final class ResponseContext extends HandledContext ResponseContext._(super.request, super.token, this.response) : super._(); @override - ResponseContext respond(final Response r) => - ResponseContext._(request, token, r); + ResponseContext respond(final Response response) => + ResponseContext._(request, token, response); } /// A [RequestContext] state indicating that the underlying connection has been From c2170282e4251a3cd2320ee91435340a34f8de06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 3 Oct 2025 14:15:57 +0200 Subject: [PATCH 05/11] docs: Add state transition diagram to RequestContext --- lib/src/adapter/context.dart | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/src/adapter/context.dart b/lib/src/adapter/context.dart index 30a8fb02..9aa83daf 100644 --- a/lib/src/adapter/context.dart +++ b/lib/src/adapter/context.dart @@ -14,8 +14,29 @@ abstract interface class _RequestContextInterface { /// /// [RequestContext] holds the original [Request] and a unique [token] /// that remains constant throughout the request's lifecycle, even as the -/// context itself might transition between different states (e.g., from -/// [NewContext] to [ResponseContext]). +/// context itself might transition between different states. +/// +/// ## State Transitions +/// +/// ┌──────────────────┐ +/// │ [RequestContext] │ (initial state) +/// └─────────┬────────┘ +/// │ +/// ┌─────────────────────┼────────────────────┐ +/// │ │ │ +/// .respond() .hijack() .connect() +/// │ │ │ +/// ▼ ▼ ▼ +/// ┌───────────────────┐ ┌─────────────────┐ ┌──────────────────┐ +/// ┌─►│ [ResponseContext] │ │ [HijackContext] │ │ [ConnectContext] │ +/// │ └─────────┬─────────┘ └─────────────────┘ └──────────────────┘ +/// └────────────┘ +/// .respond() // update response +/// +/// - [NewContext]: Initial state, can transition to any handled state +/// - [ResponseContext]: A response has been generated (can be updated via `respond()`) +/// - [HijackContext]: Connection hijacked for low-level I/O (WebSockets, etc.) +/// - [ConnectContext]: Duplex stream connection established sealed class RequestContext implements _RequestContextInterface { /// The request associated with this context. @override From 7635a142ac37df908c9292c62bc37b11c01e9fd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Tue, 28 Oct 2025 14:27:31 +0100 Subject: [PATCH 06/11] refactor!: RequestContext renamed Context --- example/middleware/example.dart | 2 +- lib/src/adapter/context.dart | 20 +++++++++---------- lib/src/middleware/context_property.dart | 14 ++++++------- lib/src/middleware/routing_middleware.dart | 4 ++-- .../src/middleware/context_property_test.dart | 8 ++++---- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/example/middleware/example.dart b/example/middleware/example.dart index 7310aaf6..377ff402 100644 --- a/example/middleware/example.dart +++ b/example/middleware/example.dart @@ -40,7 +40,7 @@ Future main() async { typedef User = int; // just an example final _auth = ContextProperty('auth'); -extension on RequestContext { +extension on Context { User get user => _auth[this]; } diff --git a/lib/src/adapter/context.dart b/lib/src/adapter/context.dart index 9aa83daf..d84d7b9c 100644 --- a/lib/src/adapter/context.dart +++ b/lib/src/adapter/context.dart @@ -12,7 +12,7 @@ abstract interface class _RequestContextInterface { /// A sealed base class for representing the state of a request as it's /// processed. /// -/// [RequestContext] holds the original [Request] and a unique [token] +/// [Context] holds the original [Request] and a unique [token] /// that remains constant throughout the request's lifecycle, even as the /// context itself might transition between different states. /// @@ -37,19 +37,19 @@ abstract interface class _RequestContextInterface { /// - [ResponseContext]: A response has been generated (can be updated via `respond()`) /// - [HijackContext]: Connection hijacked for low-level I/O (WebSockets, etc.) /// - [ConnectContext]: Duplex stream connection established -sealed class RequestContext implements _RequestContextInterface { +sealed class Context implements _RequestContextInterface { /// The request associated with this context. @override final Request request; /// A unique token representing the request throughout its lifetime. /// - /// While the [RequestContext] might change (e.g., from [NewContext] to + /// While the [Context] might change (e.g., from [NewContext] to /// [ResponseContext]), this [token] remains constant. This is useful for /// associating request-specific state, for example, with [Expando] objects /// in middleware. final Object token; - RequestContext._(this.request, this.token); + Context._(this.request, this.token); } /// An interface for request contexts that can be transitioned to a state @@ -97,7 +97,7 @@ abstract interface class HijackableContext implements _RequestContextInterface { /// }); /// } /// ``` -final class NewContext extends RequestContext +final class NewContext extends Context implements RespondableContext, HijackableContext { NewContext._(super.request, super.token) : super._(); @@ -114,7 +114,7 @@ final class NewContext extends RequestContext ConnectContext connect(final WebSocketCallback callback) => ConnectContext._(request, token, callback); - /// Creates a new [RequestContext] with a different [Request]. + /// Creates a new [Context] with a different [Request]. NewContext withRequest(final Request req) => NewContext._(req, token); @override @@ -127,11 +127,11 @@ final class NewContext extends RequestContext /// A request is considered handled if a response has been formulated /// ([ResponseContext]), the connection has been hijacked ([HijackContext]), /// or a duplex stream connection has been established ([ConnectContext]). -sealed class HandledContext extends RequestContext { +sealed class HandledContext extends Context { HandledContext._(super.request, super.token) : super._(); } -/// A [RequestContext] state indicating that a [Response] has been generated. +/// A [Context] state indicating that a [Response] has been generated. final class ResponseContext extends HandledContext implements RespondableContext { /// The response associated with this context. @@ -143,7 +143,7 @@ final class ResponseContext extends HandledContext ResponseContext._(request, token, response); } -/// A [RequestContext] state indicating that the underlying connection has been +/// A [Context] state indicating that the underlying connection has been /// hijacked. /// /// When a connection is hijacked, the handler takes full control of the @@ -174,7 +174,7 @@ final class HijackContext extends HandledContext { HijackContext._(super.request, super.token, this.callback) : super._(); } -/// A [RequestContext] state indicating that a duplex stream connection +/// A [Context] state indicating that a duplex stream connection /// (e.g., WebSocket) has been established. /// /// ```dart diff --git a/lib/src/middleware/context_property.dart b/lib/src/middleware/context_property.dart index abee6178..b7bb9384 100644 --- a/lib/src/middleware/context_property.dart +++ b/lib/src/middleware/context_property.dart @@ -1,10 +1,10 @@ import '../adapter/context.dart'; -/// Manages a piece of data associated with a specific [RequestContext]. +/// Manages a piece of data associated with a specific [Context]. /// /// `ContextProperty` allows middleware or other parts of the request handling /// pipeline to store and retrieve data scoped to a single request. It uses an -/// [Expando] internally, keyed by a token from the [RequestContext], to ensure +/// [Expando] internally, keyed by a token from the [Context], to ensure /// that data does not leak between requests. /// /// This is useful for passing information like authenticated user objects, @@ -42,7 +42,7 @@ class ContextProperty { /// Throws a [StateError] if no value is found for the [requestContext]'s token /// and the property has not been set. This ensures that accidental access /// to an uninitialized property is caught early. - T operator [](final RequestContext requestContext) { + T operator [](final Context requestContext) { return _storage[requestContext.token] ?? (throw StateError( 'ContextProperty value not found. Property: ${_debugName ?? T.toString()}. ' @@ -54,7 +54,7 @@ class ContextProperty { /// /// This method is a non-throwing alternative to the `operator []`. /// Use this when it's acceptable for the property to be absent. - T? getOrNull(final RequestContext requestContext) { + T? getOrNull(final Context requestContext) { return _storage[requestContext.token]; } @@ -62,7 +62,7 @@ class ContextProperty { /// /// Associates the [value] with the [requestContext]'s token, allowing it /// to be retrieved later using `operator []` or `getOrNull`. - void operator []=(final RequestContext requestContext, final T value) { + void operator []=(final Context requestContext, final T value) { _storage[requestContext.token] = value; } @@ -70,7 +70,7 @@ class ContextProperty { /// /// Returns `true` if a non-null value has been set for the [requestContext]'s /// token, `false` otherwise. - bool exists(final RequestContext requestContext) { + bool exists(final Context requestContext) { return _storage[requestContext.token] != null; } @@ -79,7 +79,7 @@ class ContextProperty { /// This effectively removes the association in the underlying [Expando], /// causing subsequent gets for this [requestContext] (and this property) /// to return `null` (for `getOrNull`) or throw (for `operator []`). - void clear(final RequestContext requestContext) { + void clear(final Context requestContext) { _storage[requestContext.token] = null; // Clears the association in Expando } } diff --git a/lib/src/middleware/routing_middleware.dart b/lib/src/middleware/routing_middleware.dart index 8a79a556..8dee599b 100644 --- a/lib/src/middleware/routing_middleware.dart +++ b/lib/src/middleware/routing_middleware.dart @@ -109,12 +109,12 @@ class _RoutingMiddlewareBuilder { } } -/// Extension on [RequestContext] providing access to routing information. +/// Extension on [Context] providing access to routing information. /// /// These properties are populated when a request is routed using [routeWith] /// or [RouterHandlerEx.asHandler], and are available to all handlers in the processing /// chain. -extension RequestContextEx on RequestContext { +extension ContextEx on Context { /// The portion of the request path that was matched by the route. /// /// For example, if the route pattern is `/api/**` and the request is to diff --git a/test/src/middleware/context_property_test.dart b/test/src/middleware/context_property_test.dart index 2e5326b4..7cf3fc3f 100644 --- a/test/src/middleware/context_property_test.dart +++ b/test/src/middleware/context_property_test.dart @@ -2,7 +2,7 @@ import 'package:relic/relic.dart'; import 'package:relic/src/adapter/context.dart'; import 'package:test/test.dart'; -RequestContext _createContextInstance([final String uriSuffix = 'test']) { +Context _createContextInstance([final String uriSuffix = 'test']) { final request = Request(Method.get, Uri.parse('http://test.com/$uriSuffix')); return request.toContext(Object()); } @@ -10,7 +10,7 @@ RequestContext _createContextInstance([final String uriSuffix = 'test']) { void main() { group('Given a ContextProperty and a RequestContext,', () { late ContextProperty stringProperty; - late RequestContext context; + late Context context; setUp(() { stringProperty = ContextProperty('testStringProperty'); @@ -140,8 +140,8 @@ void main() { group('Given a ContextProperty and two RequestContexts,', () { late ContextProperty stringProperty; - late RequestContext context1; - late RequestContext context2; + late Context context1; + late Context context2; setUp(() { stringProperty = ContextProperty('multiContextProp'); From 7938c41edb99ff71507b95cd90c7efcdb5e4471c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Tue, 28 Oct 2025 14:29:40 +0100 Subject: [PATCH 07/11] refactor!: NewContext renamed RequestContext --- example/basic/body_example.dart | 18 +++++++------- example/context/context_example.dart | 14 +++++------ example/context/context_property_example.dart | 2 +- example/example.dart | 2 +- example/middleware/auth_example.dart | 6 ++--- example/middleware/cors_example.dart | 4 ++-- example/middleware/middleware_example.dart | 10 ++++---- example/middleware/pipeline_example.dart | 6 ++--- lib/src/adapter/context.dart | 22 ++++++++--------- lib/src/handler/handler.dart | 20 ++++++++-------- lib/src/io/static/static_handler.dart | 24 +++++++++---------- lib/src/middleware/middleware.dart | 2 +- lib/src/router/router.dart | 8 +++---- lib/src/router/router_handler_extension.dart | 2 +- test/handler/pipeline_test.dart | 2 +- test/middleware/routing_middleware_test.dart | 18 +++++++------- test/router/router_inject_test.dart | 2 +- test/src/adapter/context_test.dart | 16 ++++++------- test/util/test_util.dart | 6 ++--- 19 files changed, 93 insertions(+), 91 deletions(-) diff --git a/example/basic/body_example.dart b/example/basic/body_example.dart index 49b6bbbc..0782bc74 100644 --- a/example/basic/body_example.dart +++ b/example/basic/body_example.dart @@ -80,12 +80,12 @@ Future main() async { } /// Basic text response handler -ResponseContext helloHandler(final NewContext ctx) { +ResponseContext helloHandler(final RequestContext ctx) { return ctx.respond(Response.ok(body: Body.fromString('Hello, World!'))); } /// JSON with automatic MIME detection handler -ResponseContext dataHandler(final NewContext ctx) { +ResponseContext dataHandler(final RequestContext ctx) { return ctx.respond( Response.ok( body: Body.fromString('{"message": "Hello"}'), @@ -95,7 +95,7 @@ ResponseContext dataHandler(final NewContext ctx) { } /// Small file handler - read entire file into memory -Future smallFileHandler(final NewContext ctx) async { +Future smallFileHandler(final RequestContext ctx) async { final file = File('example.txt'); if (!await file.exists()) { @@ -108,7 +108,7 @@ Future smallFileHandler(final NewContext ctx) async { } /// Large file handler - stream for memory efficiency -Future largeFileHandler(final NewContext ctx) async { +Future largeFileHandler(final RequestContext ctx) async { final file = File('large-file.dat'); if (!await file.exists()) { @@ -128,14 +128,14 @@ Future largeFileHandler(final NewContext ctx) async { } /// Reading request body as string handler -Future echoHandler(final NewContext ctx) async { +Future echoHandler(final RequestContext ctx) async { final content = await ctx.request.readAsString(); return ctx.respond(Response.ok(body: Body.fromString('You sent: $content'))); } /// JSON API handler -Future apiDataHandler(final NewContext ctx) async { +Future apiDataHandler(final RequestContext ctx) async { final jsonData = await ctx.request.readAsString(); final data = jsonDecode(jsonData); @@ -152,7 +152,7 @@ Future apiDataHandler(final NewContext ctx) async { } /// File upload handler with size validation -Future uploadHandler(final NewContext ctx) async { +Future uploadHandler(final RequestContext ctx) async { const maxFileSize = 10 * 1024 * 1024; // 10MB final contentLength = ctx.request.body.contentLength; @@ -171,7 +171,7 @@ Future uploadHandler(final NewContext ctx) async { } /// Image response handler with automatic format detection -Future imageHandler(final NewContext ctx) async { +Future imageHandler(final RequestContext ctx) async { final file = File('example/static_files/logo.svg'); final imageBytes = await file.readAsBytes(); @@ -186,7 +186,7 @@ Future imageHandler(final NewContext ctx) async { } /// Streaming response handler with chunked transfer encoding -Future streamHandler(final NewContext ctx) async { +Future streamHandler(final RequestContext ctx) async { Stream generateLargeDataset() async* { for (var i = 0; i < 100; i++) { await Future.delayed(const Duration(milliseconds: 50)); diff --git a/example/context/context_example.dart b/example/context/context_example.dart index 48729930..a7a19fe0 100644 --- a/example/context/context_example.dart +++ b/example/context/context_example.dart @@ -6,7 +6,7 @@ import 'package:relic/relic.dart'; import 'package:web_socket/web_socket.dart'; /// Demonstrates the four main context types in Relic using proper routing: -/// - NewContext: Starting point for all requests +/// - RequestContext: Starting point for all requests /// - ResponseContext: HTTP response handling /// - ConnectContext: WebSocket connections /// - HijackContext: Raw connection control @@ -33,7 +33,7 @@ String _htmlHomePage() { '''; } -Future homeHandler(final NewContext ctx) async { +Future homeHandler(final RequestContext ctx) async { return ctx.respond( Response.ok( body: Body.fromString( @@ -45,7 +45,7 @@ Future homeHandler(final NewContext ctx) async { ); } -Future apiHandler(final NewContext ctx) async { +Future apiHandler(final RequestContext ctx) async { final data = { 'message': 'Hello from Relic API!', 'timestamp': DateTime.now().toIso8601String(), @@ -59,7 +59,7 @@ Future apiHandler(final NewContext ctx) async { ); } -Future userHandler(final NewContext ctx) async { +Future userHandler(final RequestContext ctx) async { final userId = ctx.pathParameters[#id]; final data = { 'userId': userId, @@ -74,7 +74,7 @@ Future userHandler(final NewContext ctx) async { ); } -ConnectContext webSocketHandler(final NewContext ctx) { +ConnectContext webSocketHandler(final RequestContext ctx) { return ctx.connect((final webSocket) async { log('WebSocket connection established'); @@ -98,7 +98,7 @@ ConnectContext webSocketHandler(final NewContext ctx) { }); } -HijackContext customProtocolHandler(final NewContext ctx) { +HijackContext customProtocolHandler(final RequestContext ctx) { return ctx.hijack((final channel) { log('Connection hijacked for custom protocol'); @@ -115,7 +115,7 @@ HijackContext customProtocolHandler(final NewContext ctx) { }); } -Future dataHandler(final NewContext ctx) async { +Future dataHandler(final RequestContext ctx) async { final request = ctx.request; // Access basic HTTP information diff --git a/example/context/context_property_example.dart b/example/context/context_property_example.dart index 69088342..952671e4 100644 --- a/example/context/context_property_example.dart +++ b/example/context/context_property_example.dart @@ -18,7 +18,7 @@ Handler requestIdMiddleware(final Handler next) { } // Handler that uses the stored request ID -Future handler(final NewContext ctx) async { +Future handler(final RequestContext ctx) async { // Retrieve the request ID that was set by middleware final requestId = _requestIdProperty[ctx]; diff --git a/example/example.dart b/example/example.dart index 7373c2f0..49b33223 100644 --- a/example/example.dart +++ b/example/example.dart @@ -22,7 +22,7 @@ Future main() async { await app.serve(); } -ResponseContext hello(final NewContext ctx) { +ResponseContext hello(final RequestContext ctx) { final name = ctx.pathParameters[#name]; final age = int.parse(ctx.pathParameters[#age]!); diff --git a/example/middleware/auth_example.dart b/example/middleware/auth_example.dart index 7d638e68..f5d34c2e 100644 --- a/example/middleware/auth_example.dart +++ b/example/middleware/auth_example.dart @@ -5,7 +5,7 @@ import 'package:relic/relic.dart'; /// Simple authentication middleware Middleware authMiddleware() { return (final Handler innerHandler) { - return (final NewContext ctx) async { + return (final RequestContext ctx) async { // Check for API key in header final apiKey = ctx.request.headers['X-API-Key']?.first; @@ -22,12 +22,12 @@ Middleware authMiddleware() { } /// Public handler (no auth needed) -Future publicHandler(final NewContext ctx) async { +Future publicHandler(final RequestContext ctx) async { return ctx.respond(Response.ok(body: Body.fromString('This is public!'))); } /// Protected handler (needs auth) -Future protectedHandler(final NewContext ctx) async { +Future protectedHandler(final RequestContext ctx) async { return ctx.respond(Response.ok(body: Body.fromString('This is protected!'))); } diff --git a/example/middleware/cors_example.dart b/example/middleware/cors_example.dart index 2346fda5..dbd280d4 100644 --- a/example/middleware/cors_example.dart +++ b/example/middleware/cors_example.dart @@ -7,7 +7,7 @@ import 'package:relic/relic.dart'; /// Simple CORS middleware Middleware corsMiddleware() { return (final Handler innerHandler) { - return (final NewContext ctx) async { + return (final RequestContext ctx) async { // Handle preflight requests if (ctx.request.method == Method.options) { return ctx.respond( @@ -40,7 +40,7 @@ Middleware corsMiddleware() { } /// API handler -Future apiHandler(final NewContext ctx) async { +Future apiHandler(final RequestContext ctx) async { final data = {'message': 'Hello from CORS API!'}; return ctx.respond(Response.ok(body: Body.fromString(jsonEncode(data)))); diff --git a/example/middleware/middleware_example.dart b/example/middleware/middleware_example.dart index 18833a99..916b2d63 100644 --- a/example/middleware/middleware_example.dart +++ b/example/middleware/middleware_example.dart @@ -7,7 +7,7 @@ import 'package:relic/relic.dart'; /// Middleware that adds a custom header Middleware addHeaderMiddleware() { return (final Handler innerHandler) { - return (final NewContext ctx) async { + return (final RequestContext ctx) async { final result = await innerHandler(ctx); if (result is ResponseContext) { @@ -27,7 +27,7 @@ Middleware addHeaderMiddleware() { /// Timing middleware Middleware timingMiddleware() { return (final Handler innerHandler) { - return (final NewContext ctx) async { + return (final RequestContext ctx) async { final stopwatch = Stopwatch()..start(); final result = await innerHandler(ctx); @@ -43,7 +43,7 @@ Middleware timingMiddleware() { /// Simple error handling middleware Middleware errorHandlingMiddleware() { return (final Handler innerHandler) { - return (final NewContext ctx) async { + return (final RequestContext ctx) async { try { return await innerHandler(ctx); } catch (error) { @@ -58,13 +58,13 @@ Middleware errorHandlingMiddleware() { } /// Simple handlers -Future homeHandler(final NewContext ctx) async { +Future homeHandler(final RequestContext ctx) async { return ctx.respond( Response.ok(body: Body.fromString('Hello from home page!')), ); } -Future apiHandler(final NewContext ctx) async { +Future apiHandler(final RequestContext ctx) async { final data = {'message': 'Hello from API!'}; return ctx.respond(Response.ok(body: Body.fromString(jsonEncode(data)))); diff --git a/example/middleware/pipeline_example.dart b/example/middleware/pipeline_example.dart index e2613910..c81c93be 100644 --- a/example/middleware/pipeline_example.dart +++ b/example/middleware/pipeline_example.dart @@ -5,7 +5,7 @@ import 'package:relic/relic.dart'; /// Simple middleware that adds a header Middleware addServerHeader() { return (final Handler innerHandler) { - return (final NewContext ctx) async { + return (final RequestContext ctx) async { final result = await innerHandler(ctx); if (result is ResponseContext) { @@ -23,7 +23,7 @@ Middleware addServerHeader() { } /// Simple handler -Future simpleHandler(final NewContext ctx) async { +Future simpleHandler(final RequestContext ctx) async { return ctx.respond( Response.ok(body: Body.fromString('Hello from Pipeline!')), ); @@ -41,7 +41,7 @@ void main() async { RelicApp() ..use('/', logRequests()) ..use('/', addServerHeader()) - ..get('/router', (final NewContext ctx) async { + ..get('/router', (final RequestContext ctx) async { return ctx.respond( Response.ok(body: Body.fromString('Hello from Router!')), ); diff --git a/lib/src/adapter/context.dart b/lib/src/adapter/context.dart index d84d7b9c..31c4338e 100644 --- a/lib/src/adapter/context.dart +++ b/lib/src/adapter/context.dart @@ -33,7 +33,7 @@ abstract interface class _RequestContextInterface { /// └────────────┘ /// .respond() // update response /// -/// - [NewContext]: Initial state, can transition to any handled state +/// - [RequestContext]: Initial state, can transition to any handled state /// - [ResponseContext]: A response has been generated (can be updated via `respond()`) /// - [HijackContext]: Connection hijacked for low-level I/O (WebSockets, etc.) /// - [ConnectContext]: Duplex stream connection established @@ -44,7 +44,7 @@ sealed class Context implements _RequestContextInterface { /// A unique token representing the request throughout its lifetime. /// - /// While the [Context] might change (e.g., from [NewContext] to + /// While the [Context] might change (e.g., from [RequestContext] to /// [ResponseContext]), this [token] remains constant. This is useful for /// associating request-specific state, for example, with [Expando] objects /// in middleware. @@ -77,11 +77,11 @@ abstract interface class HijackableContext implements _RequestContextInterface { /// This context can transition to either a [ResponseContext] via [respond], /// a [HijackContext] via [hijack], or a [ConnectContext] via [connect]. /// -/// Every handler receives a [NewContext] as its starting point: +/// Every handler receives a [RequestContext] as its starting point: /// /// ```dart /// // Simple HTTP handler -/// Future apiHandler(NewContext ctx) async { +/// Future apiHandler(RequestContext ctx) async { /// return ctx.respond(Response.ok( /// body: Body.fromString('Hello from Relic!'), /// )); @@ -97,9 +97,9 @@ abstract interface class HijackableContext implements _RequestContextInterface { /// }); /// } /// ``` -final class NewContext extends Context +final class RequestContext extends Context implements RespondableContext, HijackableContext { - NewContext._(super.request, super.token) : super._(); + RequestContext._(super.request, super.token) : super._(); @override HijackContext hijack(final HijackCallback callback) => @@ -115,7 +115,7 @@ final class NewContext extends Context ConnectContext._(request, token, callback); /// Creates a new [Context] with a different [Request]. - NewContext withRequest(final Request req) => NewContext._(req, token); + RequestContext withRequest(final Request req) => RequestContext._(req, token); @override ResponseContext respond(final Response response) => @@ -152,7 +152,7 @@ final class ResponseContext extends HandledContext /// communication. /// /// ```dart -/// HijackContext customProtocolHandler(NewContext ctx) { +/// HijackContext customProtocolHandler(RequestContext ctx) { /// return ctx.hijack((channel) { /// log('Connection hijacked for custom protocol'); /// @@ -178,7 +178,7 @@ final class HijackContext extends HandledContext { /// (e.g., WebSocket) has been established. /// /// ```dart -/// ConnectContext chatHandler(NewContext ctx) { +/// ConnectContext chatHandler(RequestContext ctx) { /// return ctx.connect((webSocket) async { /// // The WebSocket is now active /// webSocket.sendText('Welcome to chat!'); @@ -200,9 +200,9 @@ final class ConnectContext extends HandledContext { /// Internal extension methods for [Request]. extension RequestInternal on Request { - /// Creates a new [NewContext] from this [Request]. + /// Creates a new [RequestContext] from this [Request]. /// /// This is the initial context state for an incoming request, using the /// provided [token] to uniquely identify the request throughout its lifecycle. - NewContext toContext(final Object token) => NewContext._(this, token); + RequestContext toContext(final Object token) => RequestContext._(this, token); } diff --git a/lib/src/handler/handler.dart b/lib/src/handler/handler.dart index 81ea3aac..6410a6eb 100644 --- a/lib/src/handler/handler.dart +++ b/lib/src/handler/handler.dart @@ -7,16 +7,16 @@ import '../message/response.dart'; import '../router/method.dart'; import '../router/router.dart'; -/// A function that processes a [NewContext] to produce a [HandledContext]. +/// A function that processes a [RequestContext] to produce a [HandledContext]. /// -/// For example, a static file handler may access the [Request] via the [NewContext], +/// For example, a static file handler may access the [Request] via the [RequestContext], /// read the requested URI from the filesystem, and return a [ResponseContext] /// (a type of [HandledContext]) containing the file data as its body. /// /// A function which produces a [Handler], either by wrapping one or more other handlers, /// or using function composition is known as a "middleware". /// -/// A [Handler] may receive a [NewContext] directly from an HTTP server adapter or it +/// A [Handler] may receive a [RequestContext] directly from an HTTP server adapter or it /// may have been processed by other middleware. Similarly, the resulting [HandledContext] /// may be directly returned to an HTTP server adapter or have further processing /// done by other middleware. @@ -24,7 +24,7 @@ import '../router/router.dart'; /// ## Basic Handler /// /// ```dart -/// ResponseContext myHandler(NewContext ctx) { +/// ResponseContext myHandler(RequestContext ctx) { /// return ctx.respond( /// Response.ok( /// body: Body.fromString('Hello, World!'), @@ -36,7 +36,7 @@ import '../router/router.dart'; /// ## Async Handler /// /// ```dart -/// Future asyncHandler(NewContext ctx) async { +/// Future asyncHandler(RequestContext ctx) async { /// final data = await fetchDataFromDatabase(); /// return ctx.respond( /// Response.ok( @@ -50,7 +50,7 @@ import '../router/router.dart'; /// /// ```dart /// // Route: /users/:id -/// Handler userHandler(NewContext ctx) { +/// Handler userHandler(RequestContext ctx) { /// final id = ctx.pathParameters[#id]; /// return ctx.respond( /// Response.ok( @@ -59,7 +59,7 @@ import '../router/router.dart'; /// ); /// }; /// ``` -typedef Handler = FutureOr Function(NewContext ctx); +typedef Handler = FutureOr Function(RequestContext ctx); /// A handler specifically designed to produce a [ResponseContext]. /// @@ -93,11 +93,11 @@ typedef Responder = FutureOr Function(Request); /// a response. /// /// This adapts a simpler `Request -> Response` function ([Responder]) into -/// the standard [Handler] format. The returned [Handler] takes a [NewContext], +/// the standard [Handler] format. The returned [Handler] takes a [RequestContext], /// retrieves its [Request] (which is passed to the [responder]), and then uses /// the [Response] from the [responder] to create a [ResponseContext]. /// -/// The input [NewContext] to the generated [Handler] must be a +/// The input [RequestContext] to the generated [Handler] must be a /// [RespondableContext] (i.e., capable of producing a response) for the /// `respond` call to succeed. The handler ensures the resulting context is /// a [ResponseContext]. @@ -144,7 +144,7 @@ abstract class HandlerObject implements RouterInjectable { void injectIn(final RelicRouter router) => router.get('/', call); /// The implementation of this [HandlerObject] - FutureOr call(final NewContext ctx); + FutureOr call(final RequestContext ctx); /// Returns this [HandlerObject] as a [Handler]. Handler get asHandler => call; diff --git a/lib/src/io/static/static_handler.dart b/lib/src/io/static/static_handler.dart index 2eecfbce..c9a47747 100644 --- a/lib/src/io/static/static_handler.dart +++ b/lib/src/io/static/static_handler.dart @@ -32,7 +32,7 @@ class FileInfo { } typedef CacheControlFactory = - CacheControlHeader? Function(NewContext ctx, FileInfo fileInfo); + CacheControlHeader? Function(RequestContext ctx, FileInfo fileInfo); /// LRU cache for file information to avoid repeated file system operations. final _fileInfoCache = LruCache(10000); @@ -126,7 +126,7 @@ class StaticHandler extends HandlerObject { } @override - FutureOr call(final NewContext ctx) { + FutureOr call(final RequestContext ctx) { return switch (entity) { Directory() => _handleDirectory(ctx, entity as Directory), File() => _handleFile(ctx, entity as File), @@ -136,7 +136,7 @@ class StaticHandler extends HandlerObject { } Future _handleDirectory( - final NewContext ctx, + final RequestContext ctx, final Directory directory, ) async { final resolvedRootPath = directory.resolveSymbolicLinksSync(); @@ -193,7 +193,7 @@ class StaticHandler extends HandlerObject { } Future _handleFile( - final NewContext ctx, + final RequestContext ctx, final File file, ) async { return await _serveFile( @@ -210,7 +210,7 @@ Future _serveFile( final File file, final MimeTypeResolver mimeResolver, final CacheControlFactory cacheControl, - final NewContext ctx, + final RequestContext ctx, ) async { // Validate HTTP method final method = ctx.request.method; @@ -240,7 +240,7 @@ bool _isMethodAllowed(final Method method) { } /// Returns a 405 Method Not Allowed response. -ResponseContext _methodNotAllowedResponse(final NewContext ctx) { +ResponseContext _methodNotAllowedResponse(final RequestContext ctx) { return ctx.respond( Response( HttpStatus.methodNotAllowed, @@ -311,7 +311,7 @@ Headers _buildBaseHeaders( /// Checks conditional request headers and returns 304 response if appropriate. Response? _checkConditionalHeaders( - final NewContext ctx, + final RequestContext ctx, final FileInfo fileInfo, final Headers headers, ) { @@ -339,7 +339,7 @@ Response? _checkConditionalHeaders( /// Handles HTTP range requests for partial content. Future _handleRangeRequest( - final NewContext ctx, + final RequestContext ctx, final FileInfo fileInfo, final Headers headers, final RangeHeader rangeHeader, @@ -358,7 +358,7 @@ Future _handleRangeRequest( } /// Validates If-Range header for range requests. -bool _isRangeRequestValid(final NewContext ctx, final FileInfo fileInfo) { +bool _isRangeRequestValid(final RequestContext ctx, final FileInfo fileInfo) { final ifRange = ctx.request.headers.ifRange; if (ifRange == null) return true; @@ -379,7 +379,7 @@ bool _isRangeRequestValid(final NewContext ctx, final FileInfo fileInfo) { /// Serves the complete file without ranges. ResponseContext _serveFullFile( - final NewContext ctx, + final RequestContext ctx, final FileInfo fileInfo, final Headers headers, final Method method, @@ -394,7 +394,7 @@ ResponseContext _serveFullFile( /// Serves a single range of the file. ResponseContext _serveSingleRange( - final NewContext ctx, + final RequestContext ctx, final FileInfo fileInfo, final Headers headers, final Range range, @@ -425,7 +425,7 @@ ResponseContext _serveSingleRange( /// Serves multiple ranges as multipart response. Future _serveMultipleRanges( - final NewContext ctx, + final RequestContext ctx, final FileInfo fileInfo, final Headers headers, final List ranges, diff --git a/lib/src/middleware/middleware.dart b/lib/src/middleware/middleware.dart index 0e81e74b..63de14f8 100644 --- a/lib/src/middleware/middleware.dart +++ b/lib/src/middleware/middleware.dart @@ -23,7 +23,7 @@ import '../../relic.dart'; /// /// ```dart /// Middleware loggingMiddleware = (Handler innerHandler) { -/// return (NewContext ctx) async { +/// return (RequestContext ctx) async { /// print('Request: ${ctx.request.method} ${ctx.request.url}'); /// final result = await innerHandler(ctx); /// print('Completed request'); diff --git a/lib/src/router/router.dart b/lib/src/router/router.dart index 3a776da8..e3125b3b 100644 --- a/lib/src/router/router.dart +++ b/lib/src/router/router.dart @@ -317,10 +317,10 @@ typedef RelicRouter = Router; /// ..delete('/', delete); /// } /// -/// ResponseContext create(final NewContext ctx) { } -/// ResponseContext read(final NewContext ctx) { } -/// ResponseContext update(final NewContext ctx) { } -/// ResponseContext delete(final NewContext ctx) { } +/// ResponseContext create(final RequestContext ctx) { } +/// ResponseContext read(final RequestContext ctx) { } +/// ResponseContext update(final RequestContext ctx) { } +/// ResponseContext delete(final RequestContext ctx) { } /// } /// ``` typedef RouterInjectable = InjectableIn; diff --git a/lib/src/router/router_handler_extension.dart b/lib/src/router/router_handler_extension.dart index db9f0e27..a1466a9f 100644 --- a/lib/src/router/router_handler_extension.dart +++ b/lib/src/router/router_handler_extension.dart @@ -24,7 +24,7 @@ extension RouterHandlerEx on RelicRouter { /// Similar to [HandlerObject] this extension allows a [Router] /// to be callable like a [Handler]. - FutureOr call(final NewContext ctx) => const Pipeline() + FutureOr call(final RequestContext ctx) => const Pipeline() .addMiddleware(routeWith(this)) .addHandler(fallback ?? respondWith((_) => Response.notFound()))(ctx); } diff --git a/test/handler/pipeline_test.dart b/test/handler/pipeline_test.dart index 160a7ed6..dc9c9463 100644 --- a/test/handler/pipeline_test.dart +++ b/test/handler/pipeline_test.dart @@ -28,7 +28,7 @@ void main() { return response; }; - HandledContext innerHandler(final NewContext request) { + HandledContext innerHandler(final RequestContext request) { expect(accessLocation, 2); accessLocation = 3; return syncHandler(request); diff --git a/test/middleware/routing_middleware_test.dart b/test/middleware/routing_middleware_test.dart index 9711350e..4002ba25 100644 --- a/test/middleware/routing_middleware_test.dart +++ b/test/middleware/routing_middleware_test.dart @@ -34,7 +34,7 @@ void main() { 'When a request matches the parameterized route, ' 'Then the handler receives correct path parameters', () async { Map? capturedParams; - Future testHandler(final NewContext ctx) async { + Future testHandler(final RequestContext ctx) async { capturedParams = ctx.pathParameters; return ctx.respond(Response(200)); } @@ -59,7 +59,7 @@ void main() { 'Then the handler receives empty path parameters', () async { Map? capturedParams; - Future testHandler(final NewContext ctx) async { + Future testHandler(final RequestContext ctx) async { capturedParams = ctx.pathParameters; return ctx.respond(Response(200)); } @@ -85,7 +85,7 @@ void main() { 'Then the next handler is called and pathParameters is empty', () async { bool nextCalled = false; - Future nextHandler(final NewContext ctx) async { + Future nextHandler(final RequestContext ctx) async { nextCalled = true; expect(ctx.pathParameters, isEmpty); return ctx.respond(Response(404)); @@ -126,7 +126,7 @@ void main() { bool handler2Called = false; router1.add(Method.get, '/router1/:item', ( - final NewContext ctx, + final RequestContext ctx, ) async { handler1Called = true; params1 = ctx.pathParameters; @@ -174,7 +174,7 @@ void main() { }), ); router2.add(Method.get, '/router2/:data', ( - final NewContext ctx, + final RequestContext ctx, ) async { handler2Called = true; params2 = ctx.pathParameters; @@ -253,7 +253,7 @@ void main() { final nestedRouter = RelicRouter(); nestedRouter.add(Method.get, '/details/:detailId', ( - final NewContext ctx, + final RequestContext ctx, ) async { nestedHandlerCalled = true; capturedParams = ctx.pathParameters; @@ -300,7 +300,7 @@ void main() { // Define handler for the leaf router leafRouter.add(Method.get, '/action/:actionName', ( - final NewContext ctx, + final RequestContext ctx, ) async { deeplyNestedHandlerCalled = true; capturedParams = ctx.pathParameters; @@ -345,7 +345,9 @@ void main() { final mainRouter = RelicRouter(); final subRouter = RelicRouter(); - subRouter.add(Method.get, '/:id/end', (final NewContext ctx) async { + subRouter.add(Method.get, '/:id/end', ( + final RequestContext ctx, + ) async { // sub-router uses :id capturedParams = ctx.pathParameters; return ctx.respond(Response(200)); diff --git a/test/router/router_inject_test.dart b/test/router/router_inject_test.dart index 923abfe2..6b8c4cbb 100644 --- a/test/router/router_inject_test.dart +++ b/test/router/router_inject_test.dart @@ -139,7 +139,7 @@ class _EchoHandlerObject extends HandlerObject { void injectIn(final Router router) => router.post(mountAt, call); @override - FutureOr call(final NewContext ctx) { + FutureOr call(final RequestContext ctx) { final data = ctx.request.body.read(); return ctx.respond(Response.ok(body: Body.fromDataStream(data))); } diff --git a/test/src/adapter/context_test.dart b/test/src/adapter/context_test.dart index 8ea29065..4398b50c 100644 --- a/test/src/adapter/context_test.dart +++ b/test/src/adapter/context_test.dart @@ -4,12 +4,12 @@ import 'package:test/test.dart'; void main() { group( - 'Given a NewContext, when withRequest is called with a new Request,', + 'Given a RequestContext, when withRequest is called with a new Request,', () { - late NewContext context; + late RequestContext context; late Request originalRequest; late Request newRequest; - late NewContext newContext; + late RequestContext newContext; late Object token; setUp(() { @@ -23,8 +23,8 @@ void main() { newContext = context.withRequest(newRequest); }); - test('then it returns a NewContext instance', () { - expect(newContext, isA()); + test('then it returns a RequestContext instance', () { + expect(newContext, isA()); }); test('then the new context contains the new request', () { @@ -73,9 +73,9 @@ void main() { ); group( - 'Given a NewContext, when withRequest is called with a request created using copyWith,', + 'Given a RequestContext, when withRequest is called with a request created using copyWith,', () { - late NewContext context; + late RequestContext context; late Request originalRequest; late Object token; @@ -94,7 +94,7 @@ void main() { ); final newContext = context.withRequest(rewrittenRequest); - expect(newContext, isA()); + expect(newContext, isA()); expect( newContext.request.requestedUri, Uri.parse('http://test.com/rewritten'), diff --git a/test/util/test_util.dart b/test/util/test_util.dart index d03dba6f..e304a2ea 100644 --- a/test/util/test_util.dart +++ b/test/util/test_util.dart @@ -12,7 +12,7 @@ final helloBytes = utf8.encode('hello,'); final worldBytes = utf8.encode(' world'); -typedef SyncHandler = HandledContext Function(NewContext); +typedef SyncHandler = HandledContext Function(RequestContext); /// A simple, synchronous handler. /// @@ -23,7 +23,7 @@ SyncHandler createSyncHandler({ final Headers? headers, final Body? body, }) { - return (final NewContext ctx) { + return (final RequestContext ctx) { return ctx.respond( Response( statusCode, @@ -39,7 +39,7 @@ SyncHandler createSyncHandler({ final SyncHandler syncHandler = createSyncHandler(); /// Calls [createSyncHandler] and wraps the response in a [Future]. -Future asyncHandler(final NewContext ctx) async { +Future asyncHandler(final RequestContext ctx) async { return syncHandler(ctx); } From 272c608ce2c1680b3f783b0d6f3a98454d83c577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 27 Oct 2025 15:15:55 +0100 Subject: [PATCH 08/11] refactor: Drop _RequestContextInterface --- lib/src/adapter/context.dart | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/lib/src/adapter/context.dart b/lib/src/adapter/context.dart index 31c4338e..7566968f 100644 --- a/lib/src/adapter/context.dart +++ b/lib/src/adapter/context.dart @@ -1,14 +1,5 @@ import '../../relic.dart'; -/// An internal interface defining the base contract for request contexts. -/// -/// It ensures that any request context provides access to the original -/// [Request]. -abstract interface class _RequestContextInterface { - /// The original incoming request associated with this context. - Request get request; -} - /// A sealed base class for representing the state of a request as it's /// processed. /// @@ -37,9 +28,8 @@ abstract interface class _RequestContextInterface { /// - [ResponseContext]: A response has been generated (can be updated via `respond()`) /// - [HijackContext]: Connection hijacked for low-level I/O (WebSockets, etc.) /// - [ConnectContext]: Duplex stream connection established -sealed class Context implements _RequestContextInterface { +sealed class Context { /// The request associated with this context. - @override final Request request; /// A unique token representing the request throughout its lifetime. @@ -54,8 +44,7 @@ sealed class Context implements _RequestContextInterface { /// An interface for request contexts that can be transitioned to a state /// where a response has been provided. -abstract interface class RespondableContext - implements _RequestContextInterface { +abstract interface class RespondableContext implements Context { /// Transitions the context to a state where a response has been associated. /// /// Takes a [response] and returns a [ResponseContext]. @@ -63,7 +52,7 @@ abstract interface class RespondableContext } /// An interface for request contexts that allow hijacking the underlying connection. -abstract interface class HijackableContext implements _RequestContextInterface { +abstract interface class HijackableContext implements Context { /// Takes control of the underlying communication channel (e.g., socket). /// /// The provided [callback] will be invoked with a [StreamChannel] From 107223640d390d39bd712d902fc3e940f55eb774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 27 Oct 2025 15:23:50 +0100 Subject: [PATCH 09/11] test: Add @visibleForTesting annotation on RequestInternal extension --- lib/src/adapter/context.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/adapter/context.dart b/lib/src/adapter/context.dart index 7566968f..47d23291 100644 --- a/lib/src/adapter/context.dart +++ b/lib/src/adapter/context.dart @@ -1,3 +1,5 @@ +import 'package:meta/meta.dart'; + import '../../relic.dart'; /// A sealed base class for representing the state of a request as it's @@ -188,6 +190,7 @@ final class ConnectContext extends HandledContext { } /// Internal extension methods for [Request]. +@visibleForTesting extension RequestInternal on Request { /// Creates a new [RequestContext] from this [Request]. /// From 4eda4023ea83103007e49c66410115694ebb3636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Wed, 29 Oct 2025 07:05:19 +0100 Subject: [PATCH 10/11] refactor!: ConnectContext renamed to ConnectionContext (to match respond & ResponseContext) --- example/context/context_example.dart | 6 ++-- lib/src/adapter/context.dart | 36 +++++++++++------------ lib/src/middleware/middleware_logger.dart | 2 +- lib/src/relic_server.dart | 2 +- test/src/adapter/context_test.dart | 10 +++---- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/example/context/context_example.dart b/example/context/context_example.dart index a7a19fe0..469979cb 100644 --- a/example/context/context_example.dart +++ b/example/context/context_example.dart @@ -8,7 +8,7 @@ import 'package:web_socket/web_socket.dart'; /// Demonstrates the four main context types in Relic using proper routing: /// - RequestContext: Starting point for all requests /// - ResponseContext: HTTP response handling -/// - ConnectContext: WebSocket connections +/// - ConnectionContext: WebSocket connections /// - HijackContext: Raw connection control /// Simple HTML page for demonstration @@ -24,7 +24,7 @@ String _htmlHomePage() {

This demonstrates the different context types in Relic using proper routing.

@@ -74,7 +74,7 @@ Future userHandler(final RequestContext ctx) async { ); } -ConnectContext webSocketHandler(final RequestContext ctx) { +ConnectionContext webSocketHandler(final RequestContext ctx) { return ctx.connect((final webSocket) async { log('WebSocket connection established'); diff --git a/lib/src/adapter/context.dart b/lib/src/adapter/context.dart index 47d23291..b4c5a5c3 100644 --- a/lib/src/adapter/context.dart +++ b/lib/src/adapter/context.dart @@ -15,21 +15,21 @@ import '../../relic.dart'; /// │ [RequestContext] │ (initial state) /// └─────────┬────────┘ /// │ -/// ┌─────────────────────┼────────────────────┐ -/// │ │ │ -/// .respond() .hijack() .connect() -/// │ │ │ -/// ▼ ▼ ▼ -/// ┌───────────────────┐ ┌─────────────────┐ ┌──────────────────┐ -/// ┌─►│ [ResponseContext] │ │ [HijackContext] │ │ [ConnectContext] │ -/// │ └─────────┬─────────┘ └─────────────────┘ └──────────────────┘ +/// ┌─────────────────────┼──────────────────────┐ +/// │ │ │ +/// .respond() .hijack() .connect() +/// │ │ │ +/// ▼ ▼ ▼ +/// ┌───────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ +/// ┌─►│ [ResponseContext] │ │ [HijackContext] │ │ [ConnectionContext] │ +/// │ └─────────┬─────────┘ └─────────────────┘ └─────────────────────┘ /// └────────────┘ /// .respond() // update response /// /// - [RequestContext]: Initial state, can transition to any handled state /// - [ResponseContext]: A response has been generated (can be updated via `respond()`) /// - [HijackContext]: Connection hijacked for low-level I/O (WebSockets, etc.) -/// - [ConnectContext]: Duplex stream connection established +/// - [ConnectionContext]: Duplex stream connection established sealed class Context { /// The request associated with this context. final Request request; @@ -66,7 +66,7 @@ abstract interface class HijackableContext implements Context { /// handled (i.e., before a response is generated or the connection is hijacked). /// /// This context can transition to either a [ResponseContext] via [respond], -/// a [HijackContext] via [hijack], or a [ConnectContext] via [connect]. +/// a [HijackContext] via [hijack], or a [ConnectionContext] via [connect]. /// /// Every handler receives a [RequestContext] as its starting point: /// @@ -79,7 +79,7 @@ abstract interface class HijackableContext implements Context { /// } /// /// // WebSocket handler -/// ConnectContext wsHandler(NewContext ctx) { +/// ConnectionContext wsHandler(RequestContext ctx) { /// return ctx.connect((webSocket) async { /// webSocket.sendText('Welcome!'); /// await for (final event in webSocket.events) { @@ -101,9 +101,9 @@ final class RequestContext extends Context /// /// The provided [WebSocketCallback] will be invoked with a /// [RelicWebSocket] for managing the bi-directional communication. - /// Returns a [ConnectContext]. - ConnectContext connect(final WebSocketCallback callback) => - ConnectContext._(request, token, callback); + /// Returns a [ConnectionContext]. + ConnectionContext connect(final WebSocketCallback callback) => + ConnectionContext._(request, token, callback); /// Creates a new [Context] with a different [Request]. RequestContext withRequest(final Request req) => RequestContext._(req, token); @@ -117,7 +117,7 @@ final class RequestContext extends Context /// /// A request is considered handled if a response has been formulated /// ([ResponseContext]), the connection has been hijacked ([HijackContext]), -/// or a duplex stream connection has been established ([ConnectContext]). +/// or a duplex stream connection has been established ([ConnectionContext]). sealed class HandledContext extends Context { HandledContext._(super.request, super.token) : super._(); } @@ -169,7 +169,7 @@ final class HijackContext extends HandledContext { /// (e.g., WebSocket) has been established. /// /// ```dart -/// ConnectContext chatHandler(RequestContext ctx) { +/// ConnectionContext chatHandler(RequestContext ctx) { /// return ctx.connect((webSocket) async { /// // The WebSocket is now active /// webSocket.sendText('Welcome to chat!'); @@ -183,10 +183,10 @@ final class HijackContext extends HandledContext { /// }); /// } /// ``` -final class ConnectContext extends HandledContext { +final class ConnectionContext extends HandledContext { /// The callback function provided to handle the duplex stream connection. final WebSocketCallback callback; - ConnectContext._(super.request, super.token, this.callback) : super._(); + ConnectionContext._(super.request, super.token, this.callback) : super._(); } /// Internal extension methods for [Request]. diff --git a/lib/src/middleware/middleware_logger.dart b/lib/src/middleware/middleware_logger.dart index 22232d47..11cf8c5e 100644 --- a/lib/src/middleware/middleware_logger.dart +++ b/lib/src/middleware/middleware_logger.dart @@ -23,7 +23,7 @@ Middleware logRequests({final Logger? logger}) => (final innerHandler) { final msg = switch (handledCtx) { final ResponseContext rc => '${rc.response.statusCode}', final HijackContext _ => 'hijacked', - final ConnectContext _ => 'connected', + final ConnectionContext _ => 'connected', }; localLogger(_message(startTime, handledCtx.request, watch.elapsed, msg)); return handledCtx; diff --git a/lib/src/relic_server.dart b/lib/src/relic_server.dart index 7d52735c..378f5eed 100644 --- a/lib/src/relic_server.dart +++ b/lib/src/relic_server.dart @@ -126,7 +126,7 @@ final class _RelicServer implements RelicServer { rc.response, ), final HijackContext hc => adapter.hijack(adapterRequest, hc.callback), - final ConnectContext cc => adapter.connect(adapterRequest, cc.callback), + final ConnectionContext cc => adapter.connect(adapterRequest, cc.callback), }; } catch (error, stackTrace) { _logError( diff --git a/test/src/adapter/context_test.dart b/test/src/adapter/context_test.dart index 4398b50c..0ce2a183 100644 --- a/test/src/adapter/context_test.dart +++ b/test/src/adapter/context_test.dart @@ -62,12 +62,12 @@ void main() { expect(hijackContext.token, same(token)); }); - test('then the new context can transition to ConnectContext', () { - final connectContext = newContext.connect((final webSocket) {}); + test('then the new context can transition to ConnectionContext', () { + final connectionContext = newContext.connect((final webSocket) {}); - expect(connectContext, isA()); - expect(connectContext.request, same(newRequest)); - expect(connectContext.token, same(token)); + expect(connectionContext, isA()); + expect(connectionContext.request, same(newRequest)); + expect(connectionContext.token, same(token)); }); }, ); From 4e463ab4c23dc73a908943e59dc78c2dffb6729a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Wed, 29 Oct 2025 07:07:41 +0100 Subject: [PATCH 11/11] refactor!: HijackContext renamed to HijackedContext (to match respond & ResponseContext) --- example/context/context_example.dart | 6 ++-- lib/src/adapter/context.dart | 44 +++++++++++------------ lib/src/handler/handler.dart | 9 ++--- lib/src/middleware/middleware_logger.dart | 2 +- lib/src/relic_server.dart | 7 ++-- test/hijack/relic_hijack_test.dart | 4 +-- test/src/adapter/context_test.dart | 10 +++--- 7 files changed, 43 insertions(+), 39 deletions(-) diff --git a/example/context/context_example.dart b/example/context/context_example.dart index 469979cb..f38f2943 100644 --- a/example/context/context_example.dart +++ b/example/context/context_example.dart @@ -9,7 +9,7 @@ import 'package:web_socket/web_socket.dart'; /// - RequestContext: Starting point for all requests /// - ResponseContext: HTTP response handling /// - ConnectionContext: WebSocket connections -/// - HijackContext: Raw connection control +/// - HijackedContext: Raw connection control /// Simple HTML page for demonstration String _htmlHomePage() { @@ -25,7 +25,7 @@ String _htmlHomePage() {
  • API Example (ResponseContext)
  • User API with Parameters (ResponseContext)
  • WebSocket Example (ConnectionContext)
  • -
  • Custom Protocol (HijackContext)
  • +
  • Custom Protocol (HijackedContext)
  • This demonstrates the different context types in Relic using proper routing.

    @@ -98,7 +98,7 @@ ConnectionContext webSocketHandler(final RequestContext ctx) { }); } -HijackContext customProtocolHandler(final RequestContext ctx) { +HijackedContext customProtocolHandler(final RequestContext ctx) { return ctx.hijack((final channel) { log('Connection hijacked for custom protocol'); diff --git a/lib/src/adapter/context.dart b/lib/src/adapter/context.dart index b4c5a5c3..8633ffa3 100644 --- a/lib/src/adapter/context.dart +++ b/lib/src/adapter/context.dart @@ -11,24 +11,24 @@ import '../../relic.dart'; /// /// ## State Transitions /// -/// ┌──────────────────┐ -/// │ [RequestContext] │ (initial state) -/// └─────────┬────────┘ -/// │ -/// ┌─────────────────────┼──────────────────────┐ -/// │ │ │ -/// .respond() .hijack() .connect() -/// │ │ │ -/// ▼ ▼ ▼ -/// ┌───────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ -/// ┌─►│ [ResponseContext] │ │ [HijackContext] │ │ [ConnectionContext] │ -/// │ └─────────┬─────────┘ └─────────────────┘ └─────────────────────┘ +/// ┌──────────────────┐ +/// │ [RequestContext] │ (initial state) +/// └─────────┬────────┘ +/// │ +/// ┌──────────────────────┼───────────────────────┐ +/// │ │ │ +/// .respond() .hijack() .connect() +/// │ │ │ +/// ▼ ▼ ▼ +/// ┌───────────────────┐ ┌───────────────────┐ ┌─────────────────────┐ +/// ┌─►│ [ResponseContext] │ │ [HijackedContext] │ │ [ConnectionContext] │ +/// │ └─────────┬─────────┘ └───────────────────┘ └─────────────────────┘ /// └────────────┘ /// .respond() // update response /// /// - [RequestContext]: Initial state, can transition to any handled state /// - [ResponseContext]: A response has been generated (can be updated via `respond()`) -/// - [HijackContext]: Connection hijacked for low-level I/O (WebSockets, etc.) +/// - [HijackedContext]: Connection hijacked for low-level I/O (WebSockets, etc.) /// - [ConnectionContext]: Duplex stream connection established sealed class Context { /// The request associated with this context. @@ -58,15 +58,15 @@ abstract interface class HijackableContext implements Context { /// Takes control of the underlying communication channel (e.g., socket). /// /// The provided [callback] will be invoked with a [StreamChannel] - /// allowing direct interaction with the connection. Returns a [HijackContext]. - HijackContext hijack(final HijackCallback callback); + /// allowing direct interaction with the connection. Returns a [HijackedContext]. + HijackedContext hijack(final HijackCallback callback); } /// Represents the initial state of a request context before it has been /// handled (i.e., before a response is generated or the connection is hijacked). /// /// This context can transition to either a [ResponseContext] via [respond], -/// a [HijackContext] via [hijack], or a [ConnectionContext] via [connect]. +/// a [HijackedContext] via [hijack], or a [ConnectionContext] via [connect]. /// /// Every handler receives a [RequestContext] as its starting point: /// @@ -93,8 +93,8 @@ final class RequestContext extends Context RequestContext._(super.request, super.token) : super._(); @override - HijackContext hijack(final HijackCallback callback) => - HijackContext._(request, token, callback); + HijackedContext hijack(final HijackCallback callback) => + HijackedContext._(request, token, callback); /// Transitions this context to a state where a duplex stream (e.g., WebSocket) /// connection is established. @@ -116,7 +116,7 @@ final class RequestContext extends Context /// A sealed base class for contexts that represent a handled request. /// /// A request is considered handled if a response has been formulated -/// ([ResponseContext]), the connection has been hijacked ([HijackContext]), +/// ([ResponseContext]), the connection has been hijacked ([HijackedContext]), /// or a duplex stream connection has been established ([ConnectionContext]). sealed class HandledContext extends Context { HandledContext._(super.request, super.token) : super._(); @@ -143,7 +143,7 @@ final class ResponseContext extends HandledContext /// communication. /// /// ```dart -/// HijackContext customProtocolHandler(RequestContext ctx) { +/// HijackedContext customProtocolHandler(RequestContext ctx) { /// return ctx.hijack((channel) { /// log('Connection hijacked for custom protocol'); /// @@ -159,10 +159,10 @@ final class ResponseContext extends HandledContext /// }); /// } /// ``` -final class HijackContext extends HandledContext { +final class HijackedContext extends HandledContext { /// The callback function provided to handle the hijacked connection. final HijackCallback callback; - HijackContext._(super.request, super.token, this.callback) : super._(); + HijackedContext._(super.request, super.token, this.callback) : super._(); } /// A [Context] state indicating that a duplex stream connection diff --git a/lib/src/handler/handler.dart b/lib/src/handler/handler.dart index 6410a6eb..93648eeb 100644 --- a/lib/src/handler/handler.dart +++ b/lib/src/handler/handler.dart @@ -68,12 +68,13 @@ typedef Handler = FutureOr Function(RequestContext ctx); typedef ResponseHandler = FutureOr Function(RespondableContext ctx); -/// A handler specifically designed to produce a [HijackContext]. +/// A handler specifically designed to produce a [HijackedContext]. /// -/// It takes a [HijackableContext] and must return a [FutureOr]. +/// It takes a [HijackableContext] and must return a [FutureOr]. /// This is useful for handlers that are guaranteed to hijack the connection /// (e.g., for WebSocket upgrades). -typedef HijackHandler = FutureOr Function(HijackableContext ctx); +typedef HijackHandler = + FutureOr Function(HijackableContext ctx); /// A function which handles exceptions. /// @@ -121,7 +122,7 @@ Handler respondWith(final Responder responder) { /// /// This adapts a [HijackCallback] into the [HijackHandler] format. /// The returned handler takes a [HijackableContext], invokes the [callback] -/// to take control of the connection, and produces a [HijackContext]. +/// to take control of the connection, and produces a [HijackedContext]. HijackHandler hijack(final HijackCallback callback) { return (final ctx) { return ctx.hijack(callback); diff --git a/lib/src/middleware/middleware_logger.dart b/lib/src/middleware/middleware_logger.dart index 11cf8c5e..89348bdc 100644 --- a/lib/src/middleware/middleware_logger.dart +++ b/lib/src/middleware/middleware_logger.dart @@ -22,7 +22,7 @@ Middleware logRequests({final Logger? logger}) => (final innerHandler) { final handledCtx = await innerHandler(ctx); final msg = switch (handledCtx) { final ResponseContext rc => '${rc.response.statusCode}', - final HijackContext _ => 'hijacked', + final HijackedContext _ => 'hijacked', final ConnectionContext _ => 'connected', }; localLogger(_message(startTime, handledCtx.request, watch.elapsed, msg)); diff --git a/lib/src/relic_server.dart b/lib/src/relic_server.dart index 378f5eed..9e3e2566 100644 --- a/lib/src/relic_server.dart +++ b/lib/src/relic_server.dart @@ -125,8 +125,11 @@ final class _RelicServer implements RelicServer { adapterRequest, rc.response, ), - final HijackContext hc => adapter.hijack(adapterRequest, hc.callback), - final ConnectionContext cc => adapter.connect(adapterRequest, cc.callback), + final HijackedContext hc => adapter.hijack(adapterRequest, hc.callback), + final ConnectionContext cc => adapter.connect( + adapterRequest, + cc.callback, + ), }; } catch (error, stackTrace) { _logError( diff --git a/test/hijack/relic_hijack_test.dart b/test/hijack/relic_hijack_test.dart index 18b5d207..a5c9b3fc 100644 --- a/test/hijack/relic_hijack_test.dart +++ b/test/hijack/relic_hijack_test.dart @@ -23,11 +23,11 @@ void main() { group('Given a server', () { test('when request context is hijacked ' - 'then an HijackContext is returned and the request times out because ' + 'then a HijackedContext is returned and the request times out because ' 'server does not write the response to the HTTP response', () async { await _scheduleServer((final ctx) { final newCtx = ctx.hijack((_) {}); - expect(newCtx, isA()); + expect(newCtx, isA()); return newCtx; }); expect(_get(), throwsA(isA())); diff --git a/test/src/adapter/context_test.dart b/test/src/adapter/context_test.dart index 0ce2a183..2e3d8489 100644 --- a/test/src/adapter/context_test.dart +++ b/test/src/adapter/context_test.dart @@ -54,12 +54,12 @@ void main() { expect(responseContext.token, same(token)); }); - test('then the new context can transition to HijackContext', () { - final hijackContext = newContext.hijack((final channel) {}); + test('then the new context can transition to HijackedContext', () { + final hijackedContext = newContext.hijack((final channel) {}); - expect(hijackContext, isA()); - expect(hijackContext.request, same(newRequest)); - expect(hijackContext.token, same(token)); + expect(hijackedContext, isA()); + expect(hijackedContext.request, same(newRequest)); + expect(hijackedContext.token, same(token)); }); test('then the new context can transition to ConnectionContext', () {