Skip to main content

Crate axum_myroutes

Crate axum_myroutes 

Source
Expand description

This crate provides convenient and reliable way to work with routes in axum. Define routes along with their methods, paths and handlers in an enum, add construct axum::Router with a single call, then refer to any route (and construct a link to it) via corresponding enum variant, which provides compile time checked internal links for your application. Also, axum extractor is provided for handlers to be aware of their routes, and capable of constructing links to themselves.

§Example

use axum::extract::Path;
use axum_myroutes::routes;

// Specify routes
#[derive(Clone, Copy)]
#[routes]
enum Route {
    #[get("/", handler = home)]
    Home,
    #[get("/items/{id}", handler = item_by_id)]
    ItemById,
}

async fn home() -> String {
    // Construct links to routes
    format!(
        "<a href={}>To first item</a>",
        Route::ItemById.url_for().path_param("id", 1).unwrap().build().unwrap()
    )
}

// My{...} extractor is generated for the enum
async fn item_by_id(route: MyRoute, Path(id): Path<u64>) -> String {
    format!(
        "<a href={}>To home</a><a href={}>To self</a><a href={}>To next</a>",
        Route::Home.url_for().build().unwrap(),
        // Construct links to current route, parameters are already filled...
        route.url_for().build().unwrap(),
        // ...but can be modified
        route.url_for().path_param("id", id + 1).unwrap().build().unwrap(),
    )
}

#[tokio::main]
async fn main() {
    let app = Route::to_router();
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

§Route methods and path construction

#[routes] enum has methods to query information related to route and to construct paths to it. It’s possible to specify path params with param(), query params with query_param() and fragment with fragment() methods of PathBuilder returned by url_for().

// Get route path (pattern)
assert_eq!(Route::ItemById.path(), "/{id}");

// Get route name (name of enum variant)
assert_eq!(Route::ItemById.name(), "ItemById");

// Construct url (path_param fails on unknown parameter)
assert_eq!(Route::ItemById.url_for().path_param("id", 123).unwrap().build().unwrap(), "/123");

// But there's also shorted relaxed variant under `generic_param` feature
// which can sets both path and query params
//assert_eq!(Route::ItemById.url_for().param("id", 123).build().unwrap(), "/123");

// Error on missing parameter
assert!(Route::ItemById.url_for().build().is_err());

// Can also set query params and fragment
assert_eq!(
    Route::ItemById
        .url_for()
        .path_param("id", 123).unwrap()
        .query_param("foo", "bar")
        .fragment("frag")
        .build()
        .unwrap(),
    "/123?foo=bar#frag"
);

§Route props

A type to store additional route properties can be provided, set per-route, and retrieved with route method.

There are options to toggle Default requirement on props type, and to allow static construction of props, enabled through routes arguments.

#[derive(Default)]
struct RouteProps {
    require_auth: bool,
}

#[derive(Clone, Copy)]
#[routes(props_type = RouteProps)]
enum Route {
    #[get("/public", handler = handler)]
    Public,
    #[get("/private", handler = handler, props = RouteProps { require_auth: true })]
    Private,
}

assert_eq!(Route::Public.props().require_auth, false);
assert_eq!(Route::Private.props().require_auth, true);

§Extractors

Extractor type is automatically provided for the route enum, with the same name prefixed with My.

async fn item_by_id(route: MyRoute) {
    // Same methods as route variant
    assert_eq!(route.path(), "/");
    assert_eq!(route.name(), "ItemById");
    assert_eq!(route.props().require_auth, false);

    // In extractor, parameters are already filled,
    // so path to self can be constructed right away
    assert!(route.url_for().build().is_ok());
}

§Router with state

If router with state is used (e.g. .with_state() is called on a router), the state type must be passed to #[routes] argument:

#[derive(Clone)]
struct AppState;

#[derive(Clone, Copy)]
#[routes(state_type = AppState)]  // note state_type argument
enum Route {
    #[get("/", handler = handler)]
    Home,
}

#[tokio::main]
async fn main() {
    let app = Route::to_router()
        .with_state(AppState);  // add state as usual
}

§Middleware with access to current route

Current route can be accessed from a middleware, and this provides a powerful mechanism to control routes behavior in a centralized way with route props. However, for a middleware to see a current route, it should be added to a router before the route information is added, which means you can’t add such middleware to constructed Router. Instead, use to_router_with() to intervene with the router construction and insert middleware a the right spot.

use axum::{extract::Request, middleware::{from_fn, Next}, response::IntoResponse};
async fn middleware(route: MyRoute, request: Request, next: Next) -> impl IntoResponse {
    if route.props().require_auth {
        // check auth
    }
    next.run(request).await
}

#[tokio::main]
async fn main() {
    let app = Route::to_router_with(|route| route.layer(from_fn(middleware)));
}

Structs§

PathBuilder
Route path builder.

Enums§

PathBuilderError

Attribute Macros§

routes
Main attribute macro for routes enum.