Crate axum_routes

Crate axum_routes 

Source
Expand description

§Axum Routes

Create an axum::Router from an enum. You can then use this enum to resolve the routes and avoid hardcoding routes in your project.


#[routes]
enum RoutesUsers {
    #[post("/", handler = create_user)]
    CreateUser,
    #[get("/{id}", handler = get_user)]
    GetByID,
    #[put("/{id}", handler = edit_user)]
    EditUser,
    #[delete("/{id}", handler = delete_user)]
    DeleteByID,
    #[get("/other/{id}", handler = get_other_resource)]
    GetOtherResourceByID,
}

#[routes]
enum Routes {
    #[nest("/users")]
    Users(RoutesUsers),
    #[get("/", handler = main)]
    Main,
}

async fn create_user() {} // axum handler
async fn get_user() {}
async fn edit_user() {}
async fn delete_user() {}
async fn get_other_resource() {}

The route path (ie “/{id}”) is exactly what axum supports (underneath it uses the matchit crate). Newer version of axum supports the {parameter} format instead of the old :parameter format.

§Resolving routes

You want to avoid as much as possible to hardcode your routes when developping a web app. It is so easy to put a typo in it, and you end up with broken links in your app.

You can use the resolve! macro to generate your routes. You have compile-time validation of the types of parameters you pass (any type that implements the ToString trait), and run-time validation of the number of parameters you pass (resolve! returns an error if the number of parameters is not valid).

Here, we resolve a nested route with one parameter:

let resolved = axum_routes::resolve!(Routes::Users(RoutesUsers::GetByID), 42).expect("should not fail");
assert_eq!("/users/42", resolved);

This will resolve the whole path, starting at the root node (Users enum), to the GetByID route. But you can also use the nested route alone:

let resolved = axum_routes::resolve!(RoutesUsers::GetByID, 42).expect("should not fail");
assert_eq!("/42", resolved);

If the final route has multiple parameters, and that you resolve the whole route, you need to pass all parameters, in the right order:

let user_id = 42;
let resource_id = 21;
let resolved = axum_routes::resolve!(Routes::Users(RoutesUsers::GetOtherResourceByID), user_id, resource_id).expect("should not fail");
assert_eq!("/users/42/other/21", resolved);

If you pass the wrong number of arguments, the macro will return an error:

let resolved = axum_routes::resolve!(RoutesUsers::GetByID, 42, 21).expect("this will fail");

§Create the axum::Router

The router! macro lets you create and customize the axum::Router from your defined enums.

First, let’s define our router as an enum:

#[routes]
enum MyRoutes {
    #[get("/admin", handler = admin_handler)]
    ProtectedAdmin,
    #[get("/assets/{asset}", handler = assets)]
    Assets,
}

fn main() {
    let router: axum::Router = axum_routes::router!(MyRoutes);

    // .. serve the Router
}

§Applying layer, route_layer, fallback, with_state, ..

Now, we can modify our enum to let axum-routes know that we want to customize the routes, like applying layers, setting the fallback handler, add a state with with_state. (See documentation of MethodRouter).

#[routes]
enum MyRoutes {
    #[get("/admin", handler = admin_handler, customize = protected_admin)]
    ProtectedAdmin,
    #[get("/assets/{asset}", handler = assets, customize = assets_customize)]
    Assets,
}

You can see that we use the customize identifier for the ProtectedAdmin and Assets variants.

So, when we generate the axum::Router associated with the MyRoutes enum, we use the router! macro and pass the customizers to it.

let router = axum_routes::router!(
    self::MyRoutes,
    protected_admin = ${
        // We do have a `protected_layer` that we will share using Arc
        let layer = Arc::clone(protected_layer);
        move |route| {
            route.layer(layer)
        }
    },
    assets_customize = $|route| {
        // For example, apply a rate limiter layer (must be declared above)
        route.layer(my_rate_limiter_layer)
    },
);

Note the $ before the expression that return the closure, it is used to tell axum-routes that this will customize a MethodRouter.

This also works when nesting routers:

#[routes]
enum MyRoutes {
    #[nest("/nested", customize = custom_nested)]
    Nested(OtherRoutes),
}

#[routes]
enum OtherRoutes {
    #[get("/test", handler = handler)]
    Test,
}

fn main() {
    let router = axum_routes::router!(
        self::MyRoutes,
        custom_nested = #|router| {
            router.with_state(State::new())
                .fallback(fallback_handler)
                .layer(ratelimiter)
        },
    );
}

Here we are customizing a axum::Router, so we have to use the #.

Macros§

resolve
Resolve a route.
router
Create the axum::Router instance. The first parameter of the macro must be the path to a type that was created from the routes macro.

Attribute Macros§

routes
The main macro to create an axum::Router from an enum.