use std::cmp::Reverse;
use std::collections::HashSet;
use axum::Router;
use axum::http::Method;
#[derive(Clone)]
pub struct RegisteredRoute {
pub method: Method,
pub path: &'static str,
pub handler_label: &'static str,
pub install: Option<fn(Router) -> Router>,
}
inventory::collect!(RegisteredRoute);
pub fn router_from_inventory() -> Router {
struct InstallEntry {
path: &'static str,
install: fn(Router) -> Router,
}
let mut entries: Vec<InstallEntry> = inventory::iter::<RegisteredRoute>
.into_iter()
.filter_map(|r| {
r.install.map(|install| InstallEntry {
path: r.path,
install,
})
})
.collect();
let mut seen = HashSet::new();
entries.retain(|e| seen.insert(e.install as usize));
entries.sort_by_key(|e| Reverse(e.path.len()));
let mut router = Router::new();
for e in entries {
router = (e.install)(router);
}
router
}
pub fn route_descriptors() -> impl Iterator<Item = RouteDescriptor> + 'static {
inventory::iter::<RegisteredRoute>
.into_iter()
.map(|r| RouteDescriptor {
method: r.method.clone(),
path: r.path,
handler_label: r.handler_label,
})
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RouteDescriptor {
pub method: Method,
pub path: &'static str,
pub handler_label: &'static str,
}
pub fn format_route_table() -> String {
let mut rows: Vec<_> = route_descriptors().collect();
rows.sort_by(|a, b| {
a.path
.cmp(b.path)
.then_with(|| a.method.as_str().cmp(b.method.as_str()))
});
let mut out = String::from("METHOD PATH HANDLER\n");
for d in rows {
out.push_str(&format!(
"{:<8} {:<33} {}\n",
d.method.as_str(),
d.path,
d.handler_label
));
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_inventory_builds() {
let _ = router_from_inventory();
}
}