#[cfg(feature = "ws")]
use std::future::Future;
use std::{fmt, sync::Arc};
#[cfg(all(feature = "openapi", not(target_arch = "wasm32")))]
use crate::openapi::RouteOpenApiEntry;
#[cfg(feature = "ws")]
use crate::websocket::{WebSocket, WebSocketUpgrade};
use crate::{handler, handler::Handler, openapi, openapi::OpenApi, Middleware};
use http_kit::endpoint::{AnyEndpoint, WithMiddleware};
use http_kit::{Endpoint, Method};
use skyzen_core::{Extractor, Responder};
pub type BoxEndpoint = AnyEndpoint;
pub(crate) type EndpointFactory = Arc<dyn Fn() -> BoxEndpoint + Send + Sync>;
mod param;
pub use param::Params;
mod router;
pub use router::{build, RouteBuildError, Router};
#[derive(Debug)]
pub struct Route {
nodes: Vec<RouteNode>,
}
#[derive(Debug)]
pub struct RouteNode {
path: String,
node_type: RouteNodeType,
}
pub enum RouteNodeType {
Route(Route),
Endpoint {
endpoint_factory: EndpointFactory,
method: Method,
openapi: Option<openapi::RouteHandlerDoc>,
},
}
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, .. } => {
f.debug_struct("Endpoint").field("method", method).finish()
}
}
}
}
impl Route {
#[must_use]
pub fn new(nodes: impl Routes) -> Self {
Self {
nodes: nodes.into_route_nodes(),
}
}
#[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>(mut self, middleware: M) -> Self
where
M: Middleware + Sync + Clone + 'static,
{
self.apply_middleware(middleware);
self
}
#[must_use]
pub fn with<M>(self, middleware: M) -> Self
where
M: Middleware + Sync + Clone + 'static,
{
self.middleware(middleware)
}
#[allow(clippy::needless_pass_by_value)]
fn apply_middleware<M>(&mut self, middleware: M)
where
M: Middleware + Sync + Clone + 'static,
{
for node in &mut self.nodes {
node.apply_middleware(middleware.clone());
}
}
#[must_use]
pub fn build(self) -> Router {
build(self).expect("Failed to build router")
}
#[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>(mut self, middleware: M) -> Self
where
M: Middleware + Sync + Clone + 'static,
{
self.route.apply_middleware(middleware);
self
}
#[must_use]
pub fn with<M>(self, middleware: M) -> Self
where
M: Middleware + Sync + Clone + 'static,
{
self.middleware(middleware)
}
#[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: Method,
endpoint: E,
openapi: Option<openapi::RouteHandlerDoc>,
) -> 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,
},
}
}
#[must_use]
pub(crate) fn new_route(path: impl Into<String>, route: Route) -> Self {
Self {
path: path.into(),
node_type: RouteNodeType::Route(route),
}
}
fn apply_middleware<M>(&mut self, middleware: M)
where
M: Middleware + Sync + Clone + 'static,
{
match &mut self.node_type {
RouteNodeType::Route(route) => route.apply_middleware(middleware),
RouteNodeType::Endpoint {
endpoint_factory, ..
} => {
let factory = Arc::clone(endpoint_factory);
*endpoint_factory = wrap_endpoint_factory(factory, middleware);
}
}
}
}
fn wrap_endpoint_factory<M>(factory: EndpointFactory, middleware: M) -> EndpointFactory
where
M: Middleware + Sync + Clone + 'static,
{
Arc::new(move || {
let endpoint = factory();
let middleware = middleware.clone();
AnyEndpoint::new(WithMiddleware::new(endpoint, middleware))
})
}
impl RouteNode {
#[must_use]
pub fn at<H, T, R>(self, handler: H) -> Self
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
self.with_handler(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.with_handler(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.with_handler(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.with_handler(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.with_handler(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.with_handler(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.with_handler(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.with_handler(Method::TRACE, 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("", method, endpoint, None)])
}
#[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, handler: F) -> Self
where
F: Fn(WebSocket) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
let builder = move |upgrade: WebSocketUpgrade| {
let callback = handler.clone();
async move { upgrade.on_upgrade(callback) }
};
self.at(builder)
}
fn with_handler<H, T, R>(self, method: Method, 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])
}
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.nodes,
RouteNodeType::Endpoint {
endpoint_factory,
method,
openapi,
} => vec![Self {
path: String::new(),
node_type: RouteNodeType::Endpoint {
endpoint_factory,
method,
openapi,
},
}],
};
nodes.append(&mut additional);
Self {
path,
node_type: RouteNodeType::Route(Route { nodes }),
}
}
}
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<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.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_node_from_handler<P, H, T, R>(path: P, method: Method, 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))
}
#[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 = format!("{}{}", 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) = openapi {
buf.push(RouteOpenApiEntry::new(path, method.clone(), *openapi));
}
}
}
}
}
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 route(self, routes: impl Routes) -> 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, handler: F) -> RouteNode
where
F: Fn(WebSocket) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
let builder = move |upgrade: WebSocketUpgrade| {
let callback = handler.clone();
async move { upgrade.on_upgrade(callback) }
};
self.at(builder)
}
}
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,
{
endpoint_node_from_handler(self, Method::GET, handler)
}
fn post<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
endpoint_node_from_handler(self, Method::POST, handler)
}
fn patch<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
endpoint_node_from_handler(self, Method::PATCH, handler)
}
fn put<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
endpoint_node_from_handler(self, Method::PUT, handler)
}
fn delete<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
endpoint_node_from_handler(self, Method::DELETE, handler)
}
fn head<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
endpoint_node_from_handler(self, Method::HEAD, handler)
}
fn options<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
endpoint_node_from_handler(self, Method::OPTIONS, handler)
}
fn trace<H, T, R>(self, handler: H) -> RouteNode
where
H: Handler<T, R>,
T: Extractor,
R: Responder,
{
endpoint_node_from_handler(self, Method::TRACE, handler)
}
fn endpoint<E>(self, method: Method, endpoint: E) -> RouteNode
where
E: Endpoint + Clone + Send + Sync + 'static,
{
RouteNode::new_endpoint(self, method, endpoint, None)
}
fn route(self, routes: impl Routes) -> RouteNode {
RouteNode::new_route(self.into(), Route::new(routes))
}
}