-
Notifications
You must be signed in to change notification settings - Fork 125
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
Support mounting dynamic routes. FIxes #250 #288
base: master
Are you sure you want to change the base?
Support mounting dynamic routes. FIxes #250 #288
Conversation
The original code does not support mounting routes with non simple parameters that include regexps. In order to support those I had to adapt the Example: test('can mount dynamic routes with regexp', () async {
var app = Router();
app.mount(r'/before/<bookId|\d+>/after', (Request request, String bookId) {
var router = Router();
router.get('/', (r) => Response.ok('book ${int.parse(bookId)}'));
return router(request);
});
app.all('/<_|[^]*>', (Request request) {
return Response.ok('catch-all-handler');
});
server.mount(app);
expect(await get('/before/123/after'), 'book 123');
expect(await get('/before/abc/after'), 'catch-all-handler');
}); |
ac228e8
to
a8706ba
Compare
@jonasfj – thoughts on this? |
I don't think I particularly dislike this specific feature. I just sort of generally would prefer to avoid adding any features. Routing is a critical feature, introducing unnecessary complexity is not desirable. (A) what are the use cases for this? (B) how will it play with code generation? It'll kind of interfere with any future attempts at generating REST API clients and documentation. (C) Can't it already be done, by using Part of me would rather see feature a heavy shelf router be proposed and maintained by the community. Since many of these features are a matter of opinion and many people won't need them. Part of me thinks |
Curious, how does parameters in Also, won't this pretty much require dynamic creation of a See: (This isn't a lightweight operation you should do for every route every time you handle a request). If we wanted to have patterns in mount, then we should instead add their values to But this is all getting quite complicated. Moreover it's nice that all routes are evaluated early on, so that issues in patterns throw at the server startup time. When it comes to structuring a server I would even suggest that nested routers are rarely a good idea. Sure maybe separate Even if you want multiple routers, why not write the full path pattern in each route, and just concatenate the routers with If you really need more advanced than that, why not build a framework with custom code generation? (Sorry, if most of my comments question if we need more features 🤣, do note I'd love to see more community maintained shelf packages) |
@jonasfj Thanks for the feedback!
My main use case is organizing related routes, say different routes for a specific user in a composable way or via appropriate isolated classes/functions.
That's a good point. I haven't played much with the code generation in
I would love to see an example of that, maybe it's just me and I'm not seeing the full potential with the current state of shelf_router. I agree that it would be better to leave things as simple as possible, specially if it's a package maintained by the Dart team and there is not enough bandwith to develop new more complex features. Maybe @felangel could share more input related to this feature in the context of |
Look at the implementation of |
@jonasfj I managed to simplify the PR code. Now the router entry and param parsing code is untouched. What I believe is not possible to do with
Maybe there is another way to do it? I thought about including the list of parameters to the context, but given that the map of parameters is already available through the context I thought it would be better to keep the list itself invisible to the outside, same as now. |
Can't you do something like: final app = Router();
app.all('/user/<user>/<rest|.*>', (request, user, rest) {
final subrouter = createSubRouter();
return subrouter(request.change(path: 'user/$user/'));
}); I'm not saying it's pretty, but it can be done in an extension method (maybe there is a few corner cases missing here), like adding additional patterns to handle trailing That said, it's still horribly slow to do this. |
@jonasfj Ok I see what you mean now, thanks for the snippet! var app = Router();
final Handler Function(String) usersHandler = createUsersHandler();
app.mount('/users/<user>', (Request request, String user) {
final handler = usersHandler(user);
return handler(request);
}); But I'm not sure how |
Two ideas: final userRouter = Router();
userRouter.get('/posts/<postId>', (request, userId, postId) {
...
});
final app = Router();
app.mount('/user/<userId>/', userRouter); This is not crazy, but perhaps super easy to do, and not easy to understand or follow. This would fairly easy to support, but also involve a lot of typying: final userRouter = Router();
userRouter.get('/posts/<postId>', (request, postId) {
final userId = param(request, 'userId');
...
});
final app = Router();
app.mount('/user/<userId>/', userRouter); This would require use of zones and be rather magical: part 'userservice.g.dart'; // generated with 'pub run build_runner build'
class UserService {
String get userId => Router.param('userId'); // this can only work if Router start using zones
@Route.get('/posts/<postId>')
Response listUsers(Request request, String postId) {
// can access userId from within the context of a request.
...
}
Router get router => _$UserServiceRouter(this);
}
final app = Router();
app.mount('/user/<userId>/', userRouter); I think these are fun ideas for making better logic, but I think it should live outside of |
@jonasfj This throws an internal error in |
@jonasfj I've modified the PR in 1a7d4dc so that Routers are not created inside the handlers. For that to work, the parameters extracted from the These changes allow to create the routes similar to the second code snippet in your previous comment. void main() {
Router createUsersRouter() {
var router = Router();
String getUser(Request r) => r.mountedParams['user']!;
router.get('/self', (Request request) {
return Response.ok("I'm ${getUser(request)}");
});
router.get('/', (Request request) {
return Response.ok('${getUser(request)} root');
});
return router;
}
var app = Router();
final usersRouter = createUsersRouter();
app.mount('/users/<user>', (Request r, String user) => usersRouter(r));
} There is another question and that is if the function passed to |
What would be the alternative? It would be nice imo to have both the mount handler as well as nested route handlers (from sub-routers) receive the params. |
Yes, having them both ways would be my preferred way as well. |
2e7dc54
to
c1d4d07
Compare
@jonasfj Is there anything else that you see it could be improved in the PR in order to get merged? |
@jonasfj ? |
@jonasfj is it possible to take another look at this PR? |
Hello!
I took a stab at issue #250 .
Related
google/dart-neats#40
VeryGoodOpenSource/dart_frog#136
This allows defining nested routes in a quite nicely composable manner.
Some examples
/users/jake -> "jake root"
/users/jake/hello/john -> "jake salutes john"