Skip to content

Commit

Permalink
Merge pull request #88 from francescovallone/feat/74
Browse files Browse the repository at this point in the history
 feat(#74): allow passing Path Parameters as handler parameters - closes #74
  • Loading branch information
francescovallone authored Sep 17, 2024
2 parents 4849bce + b1ab755 commit 4158de8
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 21 deletions.
50 changes: 42 additions & 8 deletions .website/foundations/handler.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# Handler

The handler is a function that receives a `RequestContext` object and returns an object. The handler is responsible for processing the request and returning a response to the client.
The handler is a function that receives a `RequestContext` object, possibly a list of parameters, and returns an object.

The handler is responsible for processing the request and returning a response.

## Creating a Handler

To create a handler, you can create an anonymous function when defining the route.
An handler can be defined in the following ways. You can either use an anonymous function or a named function with just one parameter of type `RequestContext`.

::: code-group

```dart
```dart [Anonymous Function (Context)]
import 'package:serinus/serinus.dart';
class MyController extends Controller {
Expand All @@ -18,9 +22,7 @@ class MyController extends Controller {
}
```

Or you can create a named function and pass it to the `on` method.

```dart
```dart [Named Function (Context)]
import 'package:serinus/serinus.dart';
class MyController extends Controller {
Expand All @@ -34,6 +36,38 @@ class MyController extends Controller {
}
```

In both cases the handler will receive a `RequestContext` object as a parameter and must return an object.
:::

Or you can use a named function with the `RequestContext` and the list of parameters in case of parametric routes.

::: code-group

```dart [Anonymous Function (Context, Parameters)]
import 'package:serinus/serinus.dart';
class MyController extends Controller {
MyController({super.path = '/'}) {
on(Route.get('/<name>'), (RequestContext context, String name) async {
return 'Hello $name!';
});
}
}
```

```dart [Named Function (Context, Parameters)]
import 'package:serinus/serinus.dart';
class MyController extends Controller {
MyController({super.path = '/'}) {
on(Route.get('/<name>'), _helloWorld);
}
Future<String> _helloWorld(RequestContext context, String name) async {
return 'Hello World $name!';
}
}
```

:::

The object will be then serialized and sent to the client.
In the case of a parametric route, the handler must have the same number of parameters as the number of parameters in the route and they must follow the _**same order**_.
8 changes: 2 additions & 6 deletions packages/serinus/lib/src/core/controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,7 @@ import 'route.dart';
typedef ReqResHandler<T> = Future<T> Function(RequestContext context);

/// Shortcut for a route handler. It takes a [Route] and a [ReqResHandler].
typedef RouteHandler = ({
Route route,
ReqResHandler handler,
ParseSchema? schema
});
typedef RouteHandler = ({Route route, Function handler, ParseSchema? schema});

/// The [Controller] class is used to define a controller.
abstract class Controller {
Expand Down Expand Up @@ -49,7 +45,7 @@ abstract class Controller {
///
/// It should not be overridden.
@mustCallSuper
void on<R extends Route, O>(R route, ReqResHandler<O> handler,
void on<R extends Route, O>(R route, Function handler,
{ParseSchema? schema}) {
final routeExists = _routes.values.any(
(r) => r.route.path == route.path && r.route.method == route.method);
Expand Down
10 changes: 8 additions & 2 deletions packages/serinus/lib/src/handlers/request_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -286,14 +286,20 @@ class RequestHandler extends Handler {

/// Executes the [handler] from the route
Future<Object?> executeHandler(
RequestContext context, Route route, ReqResHandler handler) async {
RequestContext context, Route route, Function handler) async {
config.tracerService.addEvent(
name: TraceEvents.onHandle,
begin: true,
request: context.request,
context: context,
traced: 'r-${route.runtimeType}');
Object? result = await handler.call(context);
Object? result;
if (handler is ReqResHandler) {
result = await handler.call(context);
} else {
result =
await Function.apply(handler, [context, ...context.params.values]);
}
await config.tracerService.addSyncEvent(
name: TraceEvents.onHandle,
request: context.request,
Expand Down
29 changes: 29 additions & 0 deletions packages/serinus/test/http/responses_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ class TestController extends Controller {
}
return streamable.end();
});
on(
TestRoute(path: '/path/<value>'),
(context) async {
return context.params['value'];
},
);
on(
TestRoute(path: '/path/path/<value>'),
(RequestContext context, String v) async {
return v;
},
);
}
}

Expand Down Expand Up @@ -241,5 +253,22 @@ void main() async {
]));
},
);
test(
'an handler can both accept only the context or the context and a list of path parameters',
() async {
final request = await HttpClient()
.getUrl(Uri.parse('http://localhost:3000/path/test'));
final response = await request.close();
final body = await response.transform(Utf8Decoder()).join();
expect(body, 'test');

final request2 = await HttpClient()
.getUrl(Uri.parse('http://localhost:3000/path/path/test'));
final response2 = await request2.close();
final body2 = await response2.transform(Utf8Decoder()).join();

expect(body2, 'test');
},
);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,26 @@ import 'package:serinus_cli/src/commands/generate/builder.dart';
import 'package:serinus_cli/src/commands/generate/recase.dart';

class Generator {
final Directory outputDirectory;
final File? entrypointFile;
final ReCase itemName;
final SerinusAnalyzer analyzer;

Generator({
required this.outputDirectory,
required this.entrypointFile,
required this.itemName,
required this.analyzer,
});

final Directory outputDirectory;
final File? entrypointFile;
final ReCase itemName;
final SerinusAnalyzer analyzer;

final DartEmitter emitter = DartEmitter(
allocator: Allocator(),
orderDirectives: true,
);

Future<void> replaceGetters(
String filePath, String fileName, GeneratedElement element) async {
String filePath, String fileName, GeneratedElement element,) async {
if (entrypointFile != null) {
final updates = await analyzer.analyze(
outputDirectory.absolute.path,
Expand Down

0 comments on commit 4158de8

Please sign in to comment.