From a2c872186513cece22d6d531c3ec0d952ea3f6af Mon Sep 17 00:00:00 2001 From: Marcus Twichel Date: Thu, 19 Dec 2024 12:04:50 -0700 Subject: [PATCH] feat(dart_frog): add tryRead method to RequestContext --- docs/docs/basics/dependency-injection.md | 11 +++ packages/dart_frog/lib/src/context.dart | 15 ++++ .../dart_frog/test/src/provider_test.dart | 86 +++++++++++++++++++ 3 files changed, 112 insertions(+) diff --git a/docs/docs/basics/dependency-injection.md b/docs/docs/basics/dependency-injection.md index 3d8d8e2a1..134ae8452 100644 --- a/docs/docs/basics/dependency-injection.md +++ b/docs/docs/basics/dependency-injection.md @@ -34,6 +34,17 @@ Response onRequest(RequestContext context) { } ``` +If you want to attempt to `read` a value that has not been provided, it will throw a `StateError`. However, if you want to _attempt_ to `read` a value whether it's provided or not, you can use `context.tryRead()`. If no value matching that time has been provided, it will return `null`. + +```dart +import 'package:dart_frog/dart_frog.dart'; + +Response onRequest(RequestContext context) { + final greeting = context.tryRead(); + return Response(body: greeting ?? 'Default Greeting'); +} +``` + ### Extracting Providers In the above example, we defined the `provider` inline. This is fine for simple cases, but for more complex providers or providers which you want to reuse, it can be helpful to extract the provider to its own file: diff --git a/packages/dart_frog/lib/src/context.dart b/packages/dart_frog/lib/src/context.dart index 10a1fb582..4f849d060 100644 --- a/packages/dart_frog/lib/src/context.dart +++ b/packages/dart_frog/lib/src/context.dart @@ -43,6 +43,21 @@ This can happen if $T was not provided to the request context: return (value as T Function())(); } + /// Attempt to lookup an instance of [T] from the [request] context. + /// + /// Returns `null` if [T] is not available within the provided + /// [request] context. + T? tryRead() { + try { + return read(); + // Explicitly catching [StateError] as it's what it throw + // when [read] fails + // ignore: avoid_catching_errors + } on StateError catch (_) { + return null; + } + } + /// Get URL parameters captured by the [Router.mount]. /// They can be accessed from inside the mounted routes. Map get mountedParams { diff --git a/packages/dart_frog/test/src/provider_test.dart b/packages/dart_frog/test/src/provider_test.dart index 2132f6a5c..09143c1d2 100644 --- a/packages/dart_frog/test/src/provider_test.dart +++ b/packages/dart_frog/test/src/provider_test.dart @@ -83,4 +83,90 @@ void main() { await server.close(); }); + + group('using tryRead()', () { + test('values can be provided and read via middleware', () async { + const value = '__test_value__'; + String? nullableValue; + Handler middleware(Handler handler) { + return handler + .use(provider((_) => value)) + .use(provider((_) => nullableValue)); + } + + Response onRequest(RequestContext context) { + final value = context.tryRead(); + final nullableValue = context.tryRead(); + return Response(body: '$value:$nullableValue'); + } + + final handler = + const Pipeline().addMiddleware(middleware).addHandler(onRequest); + + final server = await serve(handler, 'localhost', 3010); + final client = http.Client(); + final response = await client.get(Uri.parse('http://localhost:3010/')); + + await expectLater(response.statusCode, equals(HttpStatus.ok)); + await expectLater(response.body, equals('$value:$nullableValue')); + + await server.close(); + }); + + test('descendant providers can access provided values', () async { + const url = 'http://localhost/'; + Handler middleware(Handler handler) { + return handler.use( + provider((context) { + final stringValue = context.tryRead(); + return stringValue == null ? null : Uri.parse(stringValue); + }), + ).use(provider((context) => url)); + } + + Response onRequest(RequestContext context) { + final value = context.tryRead(); + return Response(body: value.toString()); + } + + final handler = + const Pipeline().addMiddleware(middleware).addHandler(onRequest); + + final server = await serve(handler, 'localhost', 3011); + final client = http.Client(); + final response = await client.get(Uri.parse('http://localhost:3011/')); + + await expectLater(response.statusCode, equals(HttpStatus.ok)); + await expectLater(response.body, equals(url)); + + await server.close(); + }); + + test('null is returned and no StateError is thrown', () async { + Object? exception; + Uri? value; + Response onRequest(RequestContext context) { + try { + value = context.tryRead(); + } catch (e) { + exception = e; + } + return Response(); + } + + final handler = const Pipeline() + .addMiddleware((handler) => handler) + .addHandler(onRequest); + + final server = await serve(handler, 'localhost', 3012); + final client = http.Client(); + final response = await client.get(Uri.parse('http://localhost:3012/')); + + await expectLater(response.statusCode, equals(HttpStatus.ok)); + expect(exception, isNull); + expect(value, isNull); + + await server.close(); + }); + }); }