List all routings

Hi, I want to implement dynamic permission system for my forum.

I use Axum.

I thought if I can list all of my routings and every routing assign itself in a specified database row I can create dynamic permission system. That's why I have to list all my routings with their associated routing functions.

Example


async fn pong() -> impl IntoResponse {
   StatusCode::OK
}
fn main() {
   let router = Router.new().route("/ping", get(pong));
   // I need this kind of thing
   for routing in router {
      assert_eq!((ping, pong), routing);
  }
}

I don't see a way in Axum to list routes. And even if there was a way, you would need to add additional information to each route. So it seems you definitely need a separate list or set of routes that you'll have to keep in sync with the routes you add to Axum's Router. Is that what you were wondering about?

It's possible to get the routes as they are stored in a hashmap under pathrouter struct axum/axum/src/routing/path_router.rs at 96e071c8fba443ed3bebcee5320da32b46bf4a59 · tokio-rs/axum · GitHub

The problem is it's a private struct and can't be accessed from router.

You can store them yourself. E.g. with a hack like this:

use axum::routing::{get, MethodRouter};
use axum::{http::StatusCode, response::IntoResponse, Router};
use std::collections::HashMap;

struct RouterMap<S> {
    routes: HashMap<String, MethodRouter<S>>,
}

impl<S> RouterMap<S> {
    fn new() -> Self {
        Self {
            routes: HashMap::new(),
        }
    }
}

impl<S: Clone + Send + Sync + 'static> RouterMap<S> {
    fn route_with_map(
        &mut self,
        router: Router<S>,
        path: &str,
        method_router: MethodRouter<S>,
    ) -> Router<S> {
        self.routes.insert(path.to_string(), method_router.clone());

        router.route(path, method_router)
    }
}

async fn pong() -> impl IntoResponse {
    StatusCode::OK
}

fn main() {
    let mut routes: RouterMap<()> = RouterMap::new();

    let _router = routes.route_with_map(Router::new(), "/ping", get(pong));
    for (path, _route) in routes.routes {
        assert_eq!(path, "/ping");
    }
}
2 Likes

Here's another attempt, this time with a trait. This is a little better, because it allows a fluent call-chain, like the original Router API.

use axum::routing::{get, MethodRouter};
use axum::{http::StatusCode, response::IntoResponse, Router};
use std::collections::HashMap;

trait RouteMap {
    type Map;
    type Method;

    fn route_with_map(self, map: &mut Self::Map, path: &str, method_router: Self::Method) -> Self;
}

impl<S: Clone + Send + Sync + 'static> RouteMap for Router<S> {
    type Map = HashMap<String, MethodRouter<S>>;
    type Method = MethodRouter<S>;

    fn route_with_map(self, map: &mut Self::Map, path: &str, method_router: Self::Method) -> Self {
        map.insert(path.to_string(), method_router.clone());

        self.route(path, method_router)
    }
}

async fn pong() -> impl IntoResponse {
    StatusCode::OK
}

fn main() {
    let mut routes = HashMap::new();

    let _router = Router::<()>::new().route_with_map(&mut routes, "/ping", get(pong));
    for (path, _route) in routes {
        assert_eq!(path, "/ping");
    }
}

(And I don't know why Discourse can't highlight it correctly. Its parser must only half-support the Rust syntax.)

1 Like

Thanks a lot. Really appreciated.

I have been trying to implement it but I encounter a problem. I can't use this trait for method router with appstate.
a
I tried to modify trait but couldn't fix it. I'm not very good at traits.

You should be setting the <S> appropriately. Something like this:

Router::<AppState>::new()
1 Like

Normally I use routers like this.

pub async fn route(State(app_state): State<AppState>) -> Router {
    Router::new()
        .route("/", get(alive))
        .nest(
            "/roles",
            role_route(axum::extract::State(app_state.clone())),
        )
        .nest(
            "/users",
            user_route(axum::extract::State(app_state.clone())),
        )
        .layer(CorsLayer::permissive())
        .with_state(app_state)
}

pub fn role_route(State(app_state): State<AppState>) -> Router<AppState> {
    Router::new()
        .route("/", post(create))
        .route("/:id", get(read))
        .with_state(app_state)
}

pub fn user_route(State(app_state): State<AppState>) -> Router<AppState> {
   todo
}

I'm not sure your example about appstate can fit with this structure I think have to modify trait for appstate but I don't know how.

Oh, finally understand your solution and implemented it. Thanks.

I'm dropping an example for future readers:

trait RouteMap {
    type Map;
    type Method;

    fn route_with_map(self, map: &mut Self::Map, path: &str, method_router: Self::Method) -> Self;
}

impl<T: Clone + Send + Sync + 'static> RouteMap for Router<T> {
    type Map = HashMap<String, MethodRouter<T>>;
    type Method = MethodRouter<T>;

    fn route_with_map(self, map: &mut Self::Map, path: &str, method_router: MethodRouter<T>) -> Self {
        map.insert(path.to_owned(), method_router.clone());
        self.route(path, method_router)
    } 
}

You changed a few things that didn’t have to be changed. It is unclear what the problem was.

The changes I can see are:

  • S renamed to T.
  • method_router argument in the implementation changed from the associated type to the underlying type. But left as the associated type in the trait definition.
  • path.to_string() changed to path.to_owned().

All of these changes are no-ops. Am I missing something?

These changes are not important in general. Main situation is I couldn't understand that I have to give another generic to MethodRouter as you said before. Since I'm not very good at traits it was hard for me to understand that you are pointing directly to my problem. That's why I couldn't understand in the first place and thought you understand me wrong. After couple thinking and trying I finally understand your point about defining another trait for MethodRouter then I'm able to implement what I wanted correctly. In the end you solved all of my problems. So right now there is no problem at all.