pub mod rate_limit;
pub mod redirect_mapper;
pub mod table;
pub use rate_limit::{Decision, KeyFn, KeySource, OnBackendError, RateLimit, RateLimitService};
pub use redirect_mapper::{RedirectMapper, RedirectMapperService};
pub use table::RouteTable;
use crate::error::{Error, Result};
use axum::Router;
use axum::handler::Handler as AxumHandler;
use axum::middleware::from_fn;
use axum::response::IntoResponse;
use axum::routing::MethodRouter;
pub use axum::extract::Request;
pub use axum::response::Response;
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
pub trait RouterState: Clone + Send + Sync + 'static {}
impl<T: Clone + Send + Sync + 'static> RouterState for T {}
pub struct Route<S: RouterState = ()> {
path: String,
name: Option<String>,
method_router: MethodRouter<S>,
}
impl<S: RouterState> Route<S> {
fn method<H, T>(method: axum::http::Method, path: impl Into<String>, handler: H) -> Self
where
H: AxumHandler<T, S> + Send + 'static,
T: 'static,
{
Route {
path: path.into(),
name: None,
method_router: method_router_for(method, handler),
}
}
pub fn get<H, T>(path: impl Into<String>, handler: H) -> Self
where
H: AxumHandler<T, S> + Send + 'static,
T: 'static,
{
Self::method(axum::http::Method::GET, path, handler)
}
pub fn post<H, T>(path: impl Into<String>, handler: H) -> Self
where
H: AxumHandler<T, S> + Send + 'static,
T: 'static,
{
Self::method(axum::http::Method::POST, path, handler)
}
pub fn put<H, T>(path: impl Into<String>, handler: H) -> Self
where
H: AxumHandler<T, S> + Send + 'static,
T: 'static,
{
Self::method(axum::http::Method::PUT, path, handler)
}
pub fn patch<H, T>(path: impl Into<String>, handler: H) -> Self
where
H: AxumHandler<T, S> + Send + 'static,
T: 'static,
{
Self::method(axum::http::Method::PATCH, path, handler)
}
pub fn delete<H, T>(path: impl Into<String>, handler: H) -> Self
where
H: AxumHandler<T, S> + Send + 'static,
T: 'static,
{
Self::method(axum::http::Method::DELETE, path, handler)
}
pub fn head<H, T>(path: impl Into<String>, handler: H) -> Self
where
H: AxumHandler<T, S> + Send + 'static,
T: 'static,
{
Self::method(axum::http::Method::HEAD, path, handler)
}
pub fn options<H, T>(path: impl Into<String>, handler: H) -> Self
where
H: AxumHandler<T, S> + Send + 'static,
T: 'static,
{
Self::method(axum::http::Method::OPTIONS, path, handler)
}
#[must_use]
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn middleware<M>(mut self, middleware: M) -> Self
where
M: Middleware,
{
self.method_router = middleware_layer(middleware, self.method_router);
self
}
#[must_use]
pub fn layer<L>(mut self, layer: L) -> Self
where
L: tower::Layer<axum::routing::Route> + Clone + Send + Sync + 'static,
L::Service: tower::Service<Request, Error = std::convert::Infallible>
+ Clone
+ Send
+ Sync
+ 'static,
<L::Service as tower::Service<Request>>::Response: IntoResponse + 'static,
<L::Service as tower::Service<Request>>::Future: Send + 'static,
{
self.method_router = self.method_router.layer(layer);
self
}
}
fn method_router_for<S, H, T>(
method: axum::http::Method,
handler: H,
) -> axum::routing::MethodRouter<S>
where
S: RouterState,
H: AxumHandler<T, S> + Send + 'static,
T: 'static,
{
use axum::routing::*;
match method {
axum::http::Method::GET => get(handler),
axum::http::Method::POST => post(handler),
axum::http::Method::PUT => put(handler),
axum::http::Method::PATCH => patch(handler),
axum::http::Method::DELETE => delete(handler),
axum::http::Method::HEAD => head(handler),
axum::http::Method::OPTIONS => options(handler),
_ => any(handler),
}
}
pub struct RouteGroup<S: RouterState = ()> {
prefix: String,
apply_mw: Vec<RouteLayer<S>>,
routes: Vec<Route<S>>,
}
#[derive(Clone)]
pub struct RouteLayer<S: RouterState> {
#[allow(clippy::type_complexity)]
inner: Arc<dyn Fn(MethodRouter<S>) -> MethodRouter<S> + Send + Sync + 'static>,
}
impl<S: RouterState> RouteLayer<S> {
pub fn new<F>(f: F) -> Self
where
F: Fn(MethodRouter<S>) -> MethodRouter<S> + Send + Sync + 'static,
{
RouteLayer { inner: Arc::new(f) }
}
pub fn from_layer<L>(layer: L) -> Self
where
L: tower::Layer<axum::routing::Route> + Clone + Send + Sync + 'static,
L::Service: tower::Service<Request, Error = std::convert::Infallible>
+ Clone
+ Send
+ Sync
+ 'static,
<L::Service as tower::Service<Request>>::Response: IntoResponse + 'static,
<L::Service as tower::Service<Request>>::Future: Send + 'static,
{
RouteLayer::new(move |method_router: MethodRouter<S>| method_router.layer(layer.clone()))
}
pub fn apply(&self, method_router: MethodRouter<S>) -> MethodRouter<S> {
(self.inner)(method_router)
}
}
#[derive(Clone)]
pub struct RouterLayer<S: RouterState> {
#[allow(clippy::type_complexity)]
inner: Arc<dyn Fn(Router<S>) -> Router<S> + Send + Sync + 'static>,
}
impl<S: RouterState> RouterLayer<S> {
pub fn new<F>(f: F) -> Self
where
F: Fn(Router<S>) -> Router<S> + Send + Sync + 'static,
{
RouterLayer { inner: Arc::new(f) }
}
pub fn from_layer<L>(layer: L) -> Self
where
L: tower::Layer<axum::routing::Route> + Clone + Send + Sync + 'static,
L::Service: tower::Service<Request, Error = std::convert::Infallible>
+ Clone
+ Send
+ Sync
+ 'static,
<L::Service as tower::Service<Request>>::Response: IntoResponse + 'static,
<L::Service as tower::Service<Request>>::Future: Send + 'static,
{
RouterLayer::new(move |router: Router<S>| router.layer(layer.clone()))
}
pub fn apply(&self, router: Router<S>) -> Router<S> {
(self.inner)(router)
}
}
pub type MiddlewareLayer<S> = RouterLayer<S>;
impl<S: RouterState> RouteGroup<S> {
pub fn new<I: IntoRoutes<S>>(prefix: impl Into<String>, routes: I) -> Self {
RouteGroup {
prefix: prefix.into(),
apply_mw: Vec::new(),
routes: routes.into_routes(),
}
}
#[must_use]
pub fn middleware<M>(mut self, middleware: M) -> Self
where
M: Middleware,
{
let m = middleware.clone();
self.apply_mw
.push(RouteLayer::new(move |method_router: MethodRouter<S>| {
middleware_layer(m.clone(), method_router)
}));
self
}
#[must_use]
pub fn layer<L>(mut self, layer: L) -> Self
where
L: tower::Layer<axum::routing::Route> + Clone + Send + Sync + 'static,
L::Service: tower::Service<Request, Error = std::convert::Infallible>
+ Clone
+ Send
+ Sync
+ 'static,
<L::Service as tower::Service<Request>>::Response: IntoResponse + 'static,
<L::Service as tower::Service<Request>>::Future: Send + 'static,
{
self.apply_mw.push(RouteLayer::from_layer(layer));
self
}
}
impl<S: RouterState> IntoRoutes<S> for RouteGroup<S> {
fn into_routes(self) -> Vec<Route<S>> {
let prefix = self.prefix;
let group_mw = self.apply_mw;
self.routes
.into_iter()
.map(move |mut r| {
r.path = join_path(&prefix, &r.path);
r.method_router = group_mw
.iter()
.fold(r.method_router, |method_router, mw| mw.apply(method_router));
r
})
.collect()
}
}
pub trait IntoRoutes<S: RouterState> {
fn into_routes(self) -> Vec<Route<S>>;
}
impl<S: RouterState> IntoRoutes<S> for Route<S> {
fn into_routes(self) -> Vec<Route<S>> {
vec![self]
}
}
impl<S: RouterState, const N: usize> IntoRoutes<S> for [Route<S>; N] {
fn into_routes(self) -> Vec<Route<S>> {
self.into_iter().collect()
}
}
impl<S: RouterState, const N: usize> IntoRoutes<S> for [RouteGroup<S>; N] {
fn into_routes(self) -> Vec<Route<S>> {
self.into_iter().flat_map(RouteGroup::into_routes).collect()
}
}
impl<S: RouterState> IntoRoutes<S> for Vec<Route<S>> {
fn into_routes(self) -> Vec<Route<S>> {
self
}
}
pub struct Routes<S: RouterState = ()> {
router: Router<S>,
names: HashMap<String, String>,
}
impl<S: RouterState> Routes<S> {
#[must_use]
pub fn new<I: IntoRoutes<S>>(routes: I) -> Self {
let mut out = Routes {
router: Router::new(),
names: HashMap::new(),
};
for r in routes.into_routes() {
if let Some(name) = r.name {
out.names.insert(name, r.path.clone());
}
out.router = out.router.route(&r.path, r.method_router);
}
out
}
#[must_use]
pub fn empty() -> Self {
Routes {
router: Router::new(),
names: HashMap::new(),
}
}
#[must_use]
pub fn merge(mut self, other: Routes<S>) -> Self {
for (name, path) in other.names {
self.names.insert(name, path);
}
self.router = self.router.merge(other.router);
self
}
#[must_use]
pub fn middleware<M>(mut self, middleware: M) -> Self
where
M: Middleware,
{
let m = middleware.clone();
self.router = self.router.layer(from_fn(move |request, next| {
let m = m.clone();
async move {
match m.handle(request, Next(next)).await {
Ok(response) => response,
Err(error) => error.into_response(),
}
}
}));
self
}
#[must_use]
pub fn layer<L>(mut self, layer: L) -> Self
where
L: tower::Layer<axum::routing::Route> + Clone + Send + Sync + 'static,
L::Service: tower::Service<Request, Error = std::convert::Infallible>
+ Clone
+ Send
+ Sync
+ 'static,
<L::Service as tower::Service<Request>>::Response: IntoResponse + 'static,
<L::Service as tower::Service<Request>>::Future: Send + 'static,
{
self.router = self.router.layer(layer);
self
}
#[must_use]
pub fn fallback<H, T>(mut self, handler: H) -> Self
where
H: AxumHandler<T, S> + Send + 'static,
T: 'static,
{
self.router = self.router.fallback(handler);
self
}
pub fn url_for(&self, name: &str, params: &[&str]) -> Result<String> {
let template = self
.names
.get(name)
.ok_or_else(|| Error::NotFound(format!("route `{name}` is not defined")))?;
render_path(template, params)
}
pub fn into_router(self) -> Router<S> {
self.router
}
pub fn router(&self) -> &Router<S> {
&self.router
}
pub fn named(&self) -> impl Iterator<Item = (&String, &String)> {
self.names.iter()
}
#[must_use]
pub fn table(&self) -> RouteTable {
self.names
.iter()
.map(|(name, template)| (name.as_str(), template.as_str()))
.collect()
}
}
impl<S: RouterState> Default for Routes<S> {
fn default() -> Self {
Self::empty()
}
}
pub trait Middleware: Clone + Send + Sync + 'static {
fn handle(
&self,
request: Request,
next: Next,
) -> Pin<Box<dyn Future<Output = Result<Response>> + Send>>;
}
pub struct Next(pub axum::middleware::Next);
impl Next {
pub async fn run(self, request: Request) -> Response {
self.0.run(request).await
}
}
fn middleware_layer<S, M>(middleware: M, method_router: MethodRouter<S>) -> MethodRouter<S>
where
S: RouterState,
M: Middleware,
{
method_router.layer(from_fn(move |request, next| {
let middleware = middleware.clone();
async move {
match middleware.handle(request, Next(next)).await {
Ok(response) => response,
Err(error) => error.into_response(),
}
}
}))
}
fn render_path(template: &str, params: &[&str]) -> Result<String> {
let mut out = String::with_capacity(template.len());
let mut param_idx = 0;
for (i, segment) in template.split('/').enumerate() {
if i > 0 {
out.push('/');
}
if let Some(rest) = segment.strip_prefix('{').and_then(|s| s.strip_suffix('}')) {
let param = params
.get(param_idx)
.ok_or_else(|| Error::BadRequest(format!("missing parameter `{rest}`")))?;
param_idx += 1;
out.push_str(param);
} else {
out.push_str(segment);
}
}
Ok(out)
}
fn join_path(prefix: &str, suffix: &str) -> String {
let prefix = prefix.trim_end_matches('/');
if suffix.starts_with('/') {
format!("{prefix}{suffix}")
} else if suffix.is_empty() {
prefix.to_string()
} else {
format!("{prefix}/{suffix}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn url_for_fills_params() {
let routes: Routes = Routes::new([
Route::get("/users/{id}", || async { "ok" }).name("users.show"),
Route::get("/users/{id}/posts/{post}", || async { "ok" }).name("users.posts.show"),
]);
assert_eq!(routes.url_for("users.show", &["42"]).unwrap(), "/users/42");
assert_eq!(
routes.url_for("users.posts.show", &["42", "7"]).unwrap(),
"/users/42/posts/7"
);
}
#[test]
fn group_prefixes_paths() {
let group = RouteGroup::new(
"/admin",
[Route::get("/users", || async { "ok" }).name("admin.users")],
);
let routes: Routes = Routes::new([group]);
assert_eq!(routes.url_for("admin.users", &[]).unwrap(), "/admin/users");
}
#[test]
fn missing_param_errors() {
let routes: Routes =
Routes::new([Route::get("/users/{id}", || async { "ok" }).name("users.show")]);
assert!(routes.url_for("users.show", &[]).is_err());
}
#[test]
fn join_path_cases() {
assert_eq!(join_path("/admin", "/users"), "/admin/users");
assert_eq!(join_path("/admin/", "users"), "/admin/users");
assert_eq!(join_path("/admin", ""), "/admin");
}
}