Skip to content
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

feat(dart_frog): add tryRead method to RequestContext #1652

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat(dart_frog): add tryRead method to RequestContext
mtwichel committed Dec 19, 2024
commit a2c872186513cece22d6d531c3ec0d952ea3f6af
11 changes: 11 additions & 0 deletions docs/docs/basics/dependency-injection.md
Original file line number Diff line number Diff line change
@@ -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<T>()`. 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<String>();
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:
15 changes: 15 additions & 0 deletions packages/dart_frog/lib/src/context.dart
Original file line number Diff line number Diff line change
@@ -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<T>() {
Copy link
Contributor

@alestiago alestiago Jan 2, 2025

Choose a reason for hiding this comment

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

Nit: If we end up adding tryRead I think we should modify the implementation to not catch the StateError, but instead implement tryRead so it does what read is doing right now but without the null check to avoid creating an additional StateError object when using tryRead. We could then base the read implementation on tryRead for code sharing.

try {
return read<T>();
// 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<String, String> get mountedParams {
86 changes: 86 additions & 0 deletions packages/dart_frog/test/src/provider_test.dart
Original file line number Diff line number Diff line change
@@ -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<String>((_) => value))
.use(provider<String?>((_) => nullableValue));
}

Response onRequest(RequestContext context) {
final value = context.tryRead<String>();
final nullableValue = context.tryRead<String?>();
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<Uri?>((context) {
final stringValue = context.tryRead<String>();
return stringValue == null ? null : Uri.parse(stringValue);
}),
).use(provider<String>((context) => url));
}

Response onRequest(RequestContext context) {
final value = context.tryRead<Uri?>();
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<Uri>();
} 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();
});
});
}