use std::{fmt, sync::Arc};
#[cfg(all(feature = "openapi", not(target_arch = "wasm32")))]
use crate::openapi::RouteOpenApiEntry;
#[cfg(feature = "ws")]
use crate::websocket::{MaybeSend, MaybeSync, WebSocket};
use crate::{handler, handler::Handler, middleware::Middleware, openapi, openapi::OpenApi};
use http_kit::endpoint::AnyEndpoint;
use http_kit::{Endpoint, Method};
use skyzen_core::{
middleware::boxed, middleware::BoxMiddleware, Extractor, Requirement, Responder,
};
#[cfg(feature = "ws")]
use std::future::Future;
pub type BoxEndpoint = AnyEndpoint;
pub(crate) type EndpointFactory = Arc<dyn Fn() -> BoxEndpoint + Send + Sync>;
mod param;
pub use param::{MissingParam, Params};
mod router;
pub use router::{build, AllowedMethods, NotFound, RouteBuildError, Router};
mod nest;
pub use nest::{NestedPathError, NestedRouter};
pub trait ServedRoutes {
fn served_routes(&self) -> &[(MethodFilter, String)];
}
impl ServedRoutes for Router {
fn served_routes(&self) -> &[(MethodFilter, String)] {
self.routes()
}
}
impl<E: ServedRoutes> ServedRoutes for crate::middleware::Layered<E> {
fn served_routes(&self) -> &[(MethodFilter, String)] {
self.endpoint().served_routes()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MethodFilter {
Exact(Method),
Any,
}
impl fmt::Display for MethodFilter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Exact(method) => f.write_str(method.as_str()),
Self::Any => f.write_str("ANY"),
}
}
}
struct EndpointEntry {
factory: EndpointFactory,
requirements: Vec<Requirement>,
}
impl fmt::Debug for EndpointEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EndpointEntry")
.field("requirements", &self.requirements.len())
.finish_non_exhaustive()
}
}
pub struct Route {
nodes: Vec<RouteNode>,
layers: Vec<BoxMiddleware>,
fallback: Option<EndpointEntry>,
method_not_allowed: Option<EndpointEntry>,
}
impl fmt::Debug for Route {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Route")
.field("nodes", &self.nodes)
.field("layers", &self.layers.len())
.field("has_fallback", &self.fallback.is_some())
.field("has_method_not_allowed", &self.method_not_allowed.is_some())
.finish()
}
}
#[derive(Debug)]
pub struct RouteNode {
path: String,
node_type: RouteNodeType,
}
pub enum RouteNodeType {
Route(Route),
Endpoint {
endpoint_factory: EndpointFactory,
method: MethodFilter,
openapi: Option<openapi::RouteHandlerDoc>,
requirements: Vec<Requirement>,
middleware: Vec<BoxMiddleware>,
},
Nested {
router: Router,
middleware: Vec<BoxMiddleware>,
},
}
impl fmt::Debug for RouteNodeType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Route(route) => f.debug_tuple("Route").field(route).finish(),
Self::Endpoint {
method, middleware, ..
} => f
.debug_struct("Endpoint")
.field("method", method)
.field("middleware", &middleware.len())
.finish(),
Self::Nested { router, middleware } => f
.debug_struct("Nested")
.field("router", router)
.field("middleware", &middleware.len())
.finish(),
}
}
}
impl Route {
#[must_use]
pub fn new(nodes: impl Routes) -> Self {
Self {
nodes: nodes.into_route_nodes(),
layers: Vec::new(),
fallback: None,
method_not_allowed: None,
}
}
#[must_use]
pub fn on_alarm<H, T, R>(self, handler: H) -> RouteWithAlarm
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
let endpoint = handler::into_endpoint(handler);
RouteWithAlarm {
route: self,
alarm_endpoint: Arc::new(move || AnyEndpoint::new(endpoint.clone())),
}
}
#[must_use]
pub fn middleware<M: Middleware>(mut self, middleware: M) -> Self {
self.apply_middleware(&boxed(middleware));
self
}
#[must_use]
pub fn with<M: Middleware>(self, middleware: M) -> Self {
self.middleware(middleware)
}
#[must_use]
pub fn layer<M: Middleware>(mut self, middleware: M) -> Self {
self.layers.push(boxed(middleware));
self
}
#[must_use]
pub fn fallback<H, T, R>(mut self, handler: H) -> Self
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.fallback = Some(endpoint_entry(handler));
self
}
#[must_use]
pub fn method_not_allowed<H, T, R>(mut self, handler: H) -> Self
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.method_not_allowed = Some(endpoint_entry(handler));
self
}
fn apply_middleware(&mut self, middleware: &BoxMiddleware) {
for node in &mut self.nodes {
node.apply_middleware(middleware);
}
}
fn into_mounted_nodes(mut self) -> Vec<RouteNode> {
assert!(
self.fallback.is_none() && self.method_not_allowed.is_none(),
"`fallback` and `method_not_allowed` belong to the router as a whole; register them \
on the outermost `Route` rather than on one that is mounted inside another"
);
let layers = std::mem::take(&mut self.layers);
for layer in layers.iter().rev() {
self.apply_middleware(layer);
}
self.nodes
}
#[must_use]
pub fn build(self) -> Router {
self.try_build().unwrap_or_else(|error| panic!("{error}"))
}
pub fn try_build(self) -> Result<Router, RouteBuildError> {
build(self)
}
#[must_use]
pub fn openapi(&self) -> OpenApi {
#[cfg(all(feature = "openapi", not(target_arch = "wasm32")))]
{
let mut entries = Vec::new();
collect_openapi_entries("", &self.nodes, &mut entries);
OpenApi::from_entries(&entries)
}
#[cfg(not(all(feature = "openapi", not(target_arch = "wasm32"))))]
{
OpenApi::default()
}
}
#[must_use]
pub fn enable_api_doc(mut self) -> Self {
let openapi = self.openapi();
self.nodes
.push(openapi.redoc_route(openapi::DEFAULT_API_DOCS_MOUNT));
self
}
}
pub struct RouteWithAlarm {
route: Route,
alarm_endpoint: EndpointFactory,
}
#[allow(clippy::missing_fields_in_debug)]
impl fmt::Debug for RouteWithAlarm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RouteWithAlarm")
.field("route", &self.route)
.field("has_alarm", &true)
.finish()
}
}
impl RouteWithAlarm {
#[must_use]
pub fn middleware<M: Middleware>(mut self, middleware: M) -> Self {
self.route = self.route.middleware(middleware);
self
}
#[must_use]
pub fn with<M: Middleware>(self, middleware: M) -> Self {
self.middleware(middleware)
}
#[must_use]
pub fn layer<M: Middleware>(mut self, middleware: M) -> Self {
self.route = self.route.layer(middleware);
self
}
#[must_use]
pub fn build(self) -> Router {
let alarm_factory = self.alarm_endpoint;
let mut router = self.route.build();
router.alarm_handler = Some(alarm_factory);
router
}
#[must_use]
pub fn enable_api_doc(mut self) -> Self {
let openapi = self.route.openapi();
self.route
.nodes
.push(openapi.redoc_route(openapi::DEFAULT_API_DOCS_MOUNT));
self
}
}
impl RouteNode {
#[must_use]
pub(crate) fn new_endpoint<E>(
path: impl Into<String>,
method: MethodFilter,
endpoint: E,
openapi: Option<openapi::RouteHandlerDoc>,
requirements: Vec<Requirement>,
) -> Self
where
E: Endpoint + Clone + Send + Sync + 'static,
{
let endpoint_factory: EndpointFactory =
Arc::new(move || AnyEndpoint::new(endpoint.clone()));
Self {
path: path.into(),
node_type: RouteNodeType::Endpoint {
endpoint_factory,
method,
openapi,
requirements,
middleware: Vec::new(),
},
}
}
#[must_use]
pub(crate) fn new_route(path: impl Into<String>, route: Route) -> Self {
Self {
path: path.into(),
node_type: RouteNodeType::Route(route),
}
}
#[must_use]
pub(crate) fn new_nested(path: impl Into<String>, router: Router) -> Self {
Self {
path: path.into(),
node_type: RouteNodeType::Nested {
router,
middleware: Vec::new(),
},
}
}
fn apply_middleware(&mut self, middleware: &BoxMiddleware) {
match &mut self.node_type {
RouteNodeType::Route(route) => route.apply_middleware(middleware),
RouteNodeType::Endpoint {
middleware: stack, ..
}
| RouteNodeType::Nested {
middleware: stack, ..
} => stack.insert(0, Arc::clone(middleware)),
}
}
#[must_use]
pub fn with<M: Middleware>(mut self, middleware: M) -> Self {
self.apply_middleware(&boxed(middleware));
self
}
}
impl RouteNode {
#[must_use]
pub fn at<H, T, R>(self, handler: H) -> Self
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.on(Method::GET, handler)
}
#[must_use]
pub fn get<H, T, R>(self, handler: H) -> Self
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.at(handler)
}
#[must_use]
pub fn post<H, T, R>(self, handler: H) -> Self
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.on(Method::POST, handler)
}
#[must_use]
pub fn patch<H, T, R>(self, handler: H) -> Self
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.on(Method::PATCH, handler)
}
#[must_use]
pub fn put<H, T, R>(self, handler: H) -> Self
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.on(Method::PUT, handler)
}
#[must_use]
pub fn delete<H, T, R>(self, handler: H) -> Self
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.on(Method::DELETE, handler)
}
#[must_use]
pub fn head<H, T, R>(self, handler: H) -> Self
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.on(Method::HEAD, handler)
}
#[must_use]
pub fn options<H, T, R>(self, handler: H) -> Self
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.on(Method::OPTIONS, handler)
}
#[must_use]
pub fn trace<H, T, R>(self, handler: H) -> Self
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.on(Method::TRACE, handler)
}
#[must_use]
pub fn on<H, T, R>(self, method: Method, handler: H) -> Self
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.with_handler(MethodFilter::Exact(method), handler)
}
#[must_use]
pub fn any<H, T, R>(self, handler: H) -> Self
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.with_handler(MethodFilter::Any, handler)
}
#[must_use]
pub fn endpoint<E>(self, method: Method, endpoint: E) -> Self
where
E: Endpoint + Clone + Send + Sync + 'static,
{
self.extend_with_nodes(vec![Self::new_endpoint(
"",
MethodFilter::Exact(method),
endpoint,
None,
Vec::new(),
)])
}
#[must_use]
pub fn route(self, routes: impl Routes) -> Self {
self.extend_with_nodes(routes.into_route_nodes())
}
#[cfg(feature = "ws")]
#[must_use]
pub fn ws<F, Fut>(self, session: F) -> Self
where
F: Fn(WebSocket) -> Fut + Clone + MaybeSend + MaybeSync + 'static,
Fut: Future + MaybeSend + 'static,
Fut::Output: crate::websocket::IntoWebSocketOutcome + 'static,
{
self.at(crate::websocket::session_handler(session))
}
fn with_handler<H, T, R>(self, method: MethodFilter, handler: H) -> Self
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
let endpoint = endpoint_node_from_handler("", method, handler);
self.extend_with_nodes(vec![endpoint])
}
#[must_use]
pub fn nest(self, router: Router) -> Self {
self.extend_with_nodes(vec![Self::new_nested("", router)])
}
fn extend_with_nodes(self, mut additional: Vec<Self>) -> Self {
let path = self.path;
let mut nodes = match self.node_type {
RouteNodeType::Route(route) => route.into_mounted_nodes(),
terminal @ (RouteNodeType::Endpoint { .. } | RouteNodeType::Nested { .. }) => {
vec![Self {
path: String::new(),
node_type: terminal,
}]
}
};
nodes.append(&mut additional);
Self {
path,
node_type: RouteNodeType::Route(Route::new(nodes)),
}
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` is not a route tree",
label = "not `Routes`",
note = "pass a tuple of route nodes — note the trailing comma for a single node: `Route::new((\"/ping\".at(ping),))`",
note = "a tuple holds at most 15 nodes; beyond that use `vec![..]` of route nodes, or group them into nested `Route`s",
note = "a built `Router` is mounted with `\"/prefix\".nest(router)` rather than passed here"
)]
pub trait Routes {
fn into_route_nodes(self) -> Vec<RouteNode>;
}
pub trait IntoRouteNode {
fn into_route_node(self) -> RouteNode;
}
impl IntoRouteNode for RouteNode {
fn into_route_node(self) -> RouteNode {
self
}
}
impl IntoRouteNode for Route {
fn into_route_node(self) -> RouteNode {
RouteNode::new_route("", Self::new(self.into_mounted_nodes()))
}
}
impl<T> Routes for Vec<T>
where
T: IntoRouteNode,
{
fn into_route_nodes(self) -> Vec<RouteNode> {
self.into_iter()
.map(IntoRouteNode::into_route_node)
.collect()
}
}
impl Routes for RouteNode {
fn into_route_nodes(self) -> Vec<RouteNode> {
vec![self]
}
}
impl Routes for Route {
fn into_route_nodes(self) -> Vec<RouteNode> {
self.into_mounted_nodes()
}
}
impl Routes for () {
fn into_route_nodes(self) -> Vec<RouteNode> {
Vec::new()
}
}
macro_rules! impl_routes_tuple {
() => {};
($($ty:ident),+) => {
#[allow(non_snake_case)]
impl<$($ty,)+> Routes for ($($ty,)+)
where
$($ty: IntoRouteNode,)+
{
fn into_route_nodes(self) -> Vec<RouteNode> {
let ($($ty,)+) = self;
vec![$($ty.into_route_node(),)+]
}
}
};
}
tuples!(impl_routes_tuple);
fn endpoint_entry<H, T, R>(handler: H) -> EndpointEntry
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
let endpoint = handler::into_endpoint(handler);
EndpointEntry {
factory: Arc::new(move || AnyEndpoint::new(endpoint.clone())),
requirements: T::requirements(),
}
}
fn endpoint_node_from_handler<P, H, T, R>(path: P, method: MethodFilter, handler: H) -> RouteNode
where
P: Into<String>,
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
let handler_doc = openapi::describe_handler::<H>();
let endpoint = handler::into_endpoint(handler);
RouteNode::new_endpoint(
path.into(),
method,
endpoint,
Some(handler_doc),
T::requirements(),
)
}
pub(crate) fn join_path(prefix: &str, segment: &str) -> String {
let mut joined = String::with_capacity(prefix.len() + segment.len());
joined.push_str(prefix);
joined.push_str(segment);
if !joined.contains("//") {
return joined;
}
let mut collapsed = String::with_capacity(joined.len());
let mut previous_slash = false;
for character in joined.chars() {
let is_slash = character == '/';
if is_slash && previous_slash {
continue;
}
previous_slash = is_slash;
collapsed.push(character);
}
collapsed
}
#[cfg(all(feature = "openapi", not(target_arch = "wasm32")))]
fn collect_openapi_entries(
path_prefix: &str,
nodes: &[RouteNode],
buf: &mut Vec<RouteOpenApiEntry>,
) {
for node in nodes {
let path = join_path(path_prefix, &node.path);
match &node.node_type {
RouteNodeType::Route(route) => {
collect_openapi_entries(&path, &route.nodes, buf);
}
RouteNodeType::Endpoint {
method, openapi, ..
} => {
if let (Some(openapi), MethodFilter::Exact(method)) = (openapi, method) {
buf.push(RouteOpenApiEntry::new(path, method.clone(), *openapi));
}
}
RouteNodeType::Nested { router, .. } => {
buf.extend(prefixed_openapi_entries(&path, router));
}
}
}
}
#[cfg(all(feature = "openapi", not(target_arch = "wasm32")))]
pub(crate) fn prefixed_openapi_entries(prefix: &str, router: &Router) -> Vec<RouteOpenApiEntry> {
router
.openapi_entries()
.iter()
.map(|entry| {
RouteOpenApiEntry::new(
join_path(prefix, &entry.path),
entry.method.clone(),
entry.handler,
)
})
.collect()
}
pub trait CreateRouteNode: Sized {
fn at<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder;
fn get<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.at(handler)
}
fn post<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder;
fn patch<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder;
fn put<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder;
fn delete<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder;
fn head<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder;
fn options<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder;
fn trace<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder;
fn on<H, T, R>(self, method: Method, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder;
fn any<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder;
fn route(self, routes: impl Routes) -> RouteNode;
fn nest(self, router: Router) -> RouteNode;
fn endpoint<E>(self, method: Method, endpoint: E) -> RouteNode
where
E: Endpoint + Clone + Send + Sync + 'static;
#[cfg(feature = "ws")]
fn ws<F, Fut>(self, session: F) -> RouteNode
where
F: Fn(WebSocket) -> Fut + Clone + MaybeSend + MaybeSync + 'static,
Fut: Future + MaybeSend + 'static,
Fut::Output: crate::websocket::IntoWebSocketOutcome + 'static,
{
self.at(crate::websocket::session_handler(session))
}
}
impl<P> CreateRouteNode for P
where
P: Into<String>,
{
fn at<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.on(Method::GET, handler)
}
fn post<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.on(Method::POST, handler)
}
fn patch<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.on(Method::PATCH, handler)
}
fn put<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.on(Method::PUT, handler)
}
fn delete<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.on(Method::DELETE, handler)
}
fn head<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.on(Method::HEAD, handler)
}
fn options<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.on(Method::OPTIONS, handler)
}
fn trace<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.on(Method::TRACE, handler)
}
fn on<H, T, R>(self, method: Method, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
endpoint_node_from_handler(self, MethodFilter::Exact(method), handler)
}
fn any<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
endpoint_node_from_handler(self, MethodFilter::Any, handler)
}
fn endpoint<E>(self, method: Method, endpoint: E) -> RouteNode
where
E: Endpoint + Clone + Send + Sync + 'static,
{
RouteNode::new_endpoint(
self,
MethodFilter::Exact(method),
endpoint,
None,
Vec::new(),
)
}
fn route(self, routes: impl Routes) -> RouteNode {
RouteNode::new_route(self.into(), Route::new(routes))
}
fn nest(self, router: Router) -> RouteNode {
RouteNode::new_nested(self.into(), router)
}
}