-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Type safe routing #756
Type safe routing #756
Conversation
Overall, I love the way you solved this! When I saw the PR title I first thought it would be a solution to the context / |
I think this is a nice addition. Empowers users to inverse the logic so that instead of routes having handlers, now handlers have routes. |
Realized that by adding let app = Router::new()
.typed_get(users_index)
.typed_post(users_create)
.typed_get(users_show)
.typed_get(users_edit); I think thats way nicer because you only have to use and learn Edit: One could even do let app = Router::new()
.get(users_index)
.post(users_create)
.get(users_show)
.get(users_edit); But maybe thats one step too far 😅 |
a6a37d3
to
da7418b
Compare
This is ready for review now :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One interesting issue I see with this is nested routers. Nesting a router that uses the new typed routes under a route that has its own captures wouldn't work.
Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com>
Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com>
Tell me if this is too much but I had a thought this morning. Why only make the paths types, why not the http methods as well? Basically: use axum::{response::IntoResponse, Router};
use axum_extra::routing::{Any, Get, OneOf, Post, RouterExt, TypedPath};
use serde::Deserialize;
#[tokio::main]
async fn main() {
let app = Router::new()
.typed_route(users_index)
.typed_route(users_create)
.typed_route(users_show)
.typed_route(users_edit);
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
#[derive(TypedPath)]
#[typed_path("/users")]
struct UsersCollection;
#[derive(Deserialize, TypedPath)]
#[typed_path("/users/:id")]
struct UsersMember {
id: u32,
}
#[derive(Deserialize, TypedPath)]
#[typed_path("/users/:id/edit")]
struct UsersEdit(u32);
async fn users_index(_: Get, _: UsersCollection) -> impl IntoResponse {
"users#index"
}
async fn users_create(_: Post, _: UsersCollection, _payload: String) -> impl IntoResponse {
"users#create"
}
async fn users_show(_: Any, UsersMember { id }: UsersMember) -> impl IntoResponse {
format!("users#show: {}", id)
}
async fn users_edit(_: OneOf<(Get, Post)>, UsersEdit(id): UsersEdit) -> impl IntoResponse {
format!("users#edit: {}", id)
} I think this is pretty interesting but its probably also gonna be a little foreign to some people. Too much advanced type trickery? A hunch I have is that it might be convenient for openapi spec generation since more of the data is in the handler itself, but thats just a hunch. |
I think on a user's perspective it the signatures might start getting a bit too noise with both the http method and the typed path. |
Regarding the general idea: It looks like this approach could also enable typesafe reverse routing. Safely generating URLs that are guaranteed to be accepted by the router is a useful feature. Changed the path in some way? All links created using reverse routing are automatically kept in sync. |
I think that's a valid concern. Question is whether you think it's a worthy trade off.
Do you have an example of what that might look like?
Yep that's part of the idea. Typed paths will implement Display and return a path that you can use with a tags and such. It doesn't guarantee that you've actually added the route to the router. Don't think that's possible without going full on Haskell. |
After thinking about it for the last days, I don't think the typed method stuff makes that much sense. In theory it could be nice for generating redirects but only with a decently more complex API (I really don't like the I think typed paths are worthwhile because they not only solve the problem of parameter mismatches (by the way: what about ¹ or even requests? there's some server-server APIs like Matrix federation where you could use the same path type for receiving and sending requests |
1776e84
to
b2bd51d
Compare
Thats fair. I've reverted the typed method stuff.
Oh I had forgotten about that. Currently the re |
I think it should, yes. Why not? |
@jplatte done! Its not the most efficient implementation but probably good enough. |
Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com>
* wip * wip * make macro implement trait * checkpoint * checkpoint * Simplify things quite a bit * re-export `axum_macros::TypedPath` from `axum_extra` * docs * add missing feature * fix docs link * fix features * fix missing imports * make serde an optional dep again * ui tests * Break things up a bit * Update span for `FromRequest` impls to point to callsite * make docs feature labels show up automatically * Apply suggestions from code review Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com> * add note about Display/Serialize being compatible * Update axum-extra/src/routing/typed.rs Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com> * fix missing docs link * what about typed methods? * Revert "what about typed methods?" This reverts commit cc1f989. * don't allow wildcards for now * percent encode params * Update axum-extra/src/routing/typed.rs Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com> * rephrase args * changelog Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com>
* wip * wip * make macro implement trait * checkpoint * checkpoint * Simplify things quite a bit * re-export `axum_macros::TypedPath` from `axum_extra` * docs * add missing feature * fix docs link * fix features * fix missing imports * make serde an optional dep again * ui tests * Break things up a bit * Update span for `FromRequest` impls to point to callsite * make docs feature labels show up automatically * Apply suggestions from code review Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com> * add note about Display/Serialize being compatible * Update axum-extra/src/routing/typed.rs Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com> * fix missing docs link * what about typed methods? * Revert "what about typed methods?" This reverts commit cc1f989. * don't allow wildcards for now * percent encode params * Update axum-extra/src/routing/typed.rs Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com> * rephrase args * changelog Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com>
* wip * wip * make macro implement trait * checkpoint * checkpoint * Simplify things quite a bit * re-export `axum_macros::TypedPath` from `axum_extra` * docs * add missing feature * fix docs link * fix features * fix missing imports * make serde an optional dep again * ui tests * Break things up a bit * Update span for `FromRequest` impls to point to callsite * make docs feature labels show up automatically * Apply suggestions from code review Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com> * add note about Display/Serialize being compatible * Update axum-extra/src/routing/typed.rs Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com> * fix missing docs link * what about typed methods? * Revert "what about typed methods?" This reverts commit cc1f989. * don't allow wildcards for now * percent encode params * Update axum-extra/src/routing/typed.rs Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com> * rephrase args * changelog Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com>
Some things thats been bugging me about axum's routing for the longest time:
Router::router
and the path you try and extract withPath
. Requires you to make changes in multiple places.Path
might try and extract captures that don't exist on the routeAttributes like
#[get("/users/:id")]
can fix that but they require some pretty gnarly parsing of the function arguments to make sure things are aligned. RA also isn't great with attributes so I'd like to avoid them.I've been experimenting with another approach that I think is interesting:
Advantages:
#[derive(_)]
of a known trait which RA tends to work well with.@jplatte @programatik29 what do you think about this?