use std::sync::Arc;
use serve_net::{Method, WorkClass};
use crate::{Cx, NoRender, Render, Result};
pub trait Handler<I, E: Render = NoRender>: Send + Sync + 'static {
fn call(&self, cx: &mut Cx<'_, I, E>) -> Result;
}
impl<I, E: Render, F> Handler<I, E> for F
where
F: Fn(&mut Cx<'_, I, E>) -> Result + Send + Sync + 'static,
{
#[inline]
fn call(&self, cx: &mut Cx<'_, I, E>) -> Result {
self(cx)
}
}
#[derive(Debug)]
pub(crate) enum Access<R> {
Undeclared,
Public,
Rule(Arc<R>),
}
impl<R> Clone for Access<R> {
fn clone(&self) -> Self {
match self {
Self::Undeclared => Self::Undeclared,
Self::Public => Self::Public,
Self::Rule(r) => Self::Rule(Arc::clone(r)),
}
}
}
pub(crate) type BoxHandler<I, E> = Arc<dyn Handler<I, E>>;
pub struct Route<I, R, E: Render = NoRender> {
pub(crate) pattern: String,
pub(crate) handlers: Vec<(Method, BoxHandler<I, E>)>,
pub(crate) access: Access<R>,
pub(crate) name: Option<&'static str>,
pub(crate) work: WorkClass,
pub(crate) template: Option<&'static str>,
pub(crate) csrf_exempt: Option<&'static str>,
pub(crate) form_limits: form_net::Limits,
pub(crate) cors: Option<Arc<crate::Cors>>,
pub(crate) cache: crate::CachePolicy,
}
impl<I: 'static, R, E: Render> Route<I, R, E> {
#[must_use]
pub fn at(pattern: impl Into<String>) -> Self {
Self {
pattern: pattern.into(),
handlers: Vec::new(),
access: Access::Undeclared,
name: None,
work: WorkClass::Quick,
template: None,
csrf_exempt: None,
form_limits: form_net::Limits::DEFAULT,
cors: None,
cache: crate::CachePolicy::Unset,
}
}
#[must_use]
pub fn on(mut self, method: Method, handler: impl Handler<I, E>) -> Self {
self.handlers.push((method, Arc::new(handler)));
self
}
#[must_use]
pub fn get<F>(self, f: F) -> Self
where
F: Fn(&mut Cx<'_, I, E>) -> Result + Send + Sync + 'static,
{
self.on(Method::Get, f)
}
#[must_use]
pub fn post<F>(self, f: F) -> Self
where
F: Fn(&mut Cx<'_, I, E>) -> Result + Send + Sync + 'static,
{
self.on(Method::Post, f)
}
#[must_use]
pub fn put<F>(self, f: F) -> Self
where
F: Fn(&mut Cx<'_, I, E>) -> Result + Send + Sync + 'static,
{
self.on(Method::Put, f)
}
#[must_use]
pub fn patch<F>(self, f: F) -> Self
where
F: Fn(&mut Cx<'_, I, E>) -> Result + Send + Sync + 'static,
{
self.on(Method::Patch, f)
}
#[must_use]
pub fn delete<F>(self, f: F) -> Self
where
F: Fn(&mut Cx<'_, I, E>) -> Result + Send + Sync + 'static,
{
self.on(Method::Delete, f)
}
#[must_use]
pub fn public(mut self) -> Self {
self.access = Access::Public;
self
}
#[must_use]
pub fn allow(mut self, rule: R) -> Self {
self.access = Access::Rule(Arc::new(rule));
self
}
#[must_use]
pub fn name(mut self, name: &'static str) -> Self {
self.name = Some(name);
self
}
#[must_use]
pub fn work(mut self, work: WorkClass) -> Self {
self.work = work;
self
}
#[must_use]
pub fn csrf_exempt(mut self, reason: &'static str) -> Self {
self.csrf_exempt = Some(reason);
self
}
#[must_use]
pub fn form_limits(mut self, limits: form_net::Limits) -> Self {
self.form_limits = limits;
self
}
#[must_use]
pub fn cors(mut self, cors: crate::Cors) -> Self {
self.cors = Some(Arc::new(cors));
self
}
#[must_use]
pub fn cache(mut self, policy: crate::CachePolicy) -> Self {
self.cache = policy;
self
}
#[must_use]
pub fn template(mut self, name: &'static str) -> Self {
self.template = Some(name);
self
}
}
impl<I, R, E: Render> std::fmt::Debug for Route<I, R, E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Route")
.field("pattern", &self.pattern)
.field(
"methods",
&self.handlers.iter().map(|(m, _)| *m).collect::<Vec<_>>(),
)
.field("name", &self.name)
.finish_non_exhaustive()
}
}
macro_rules! method_fn {
($(#[$doc:meta])* $fn:ident, $m:ident) => {
$(#[$doc])*
#[must_use]
pub fn $fn<I: 'static, R, E: Render, F>(pattern: impl Into<String>, f: F) -> Route<I, R, E>
where
F: Fn(&mut Cx<'_, I, E>) -> Result + Send + Sync + 'static,
{
Route::at(pattern).on(Method::$m, f)
}
};
}
method_fn!(
get, Get
);
method_fn!(
post, Post
);
method_fn!(
put, Put
);
method_fn!(
patch, Patch
);
method_fn!(
delete, Delete
);
pub struct Group<I, R, E: Render = NoRender> {
pub(crate) prefix: String,
pub(crate) access: Access<R>,
pub(crate) routes: Vec<Route<I, R, E>>,
pub(crate) cors: Option<Arc<crate::Cors>>,
pub(crate) cache: crate::CachePolicy,
}
impl<I: 'static, R, E: Render> Group<I, R, E> {
#[must_use]
pub fn new(prefix: impl Into<String>) -> Self {
Self {
prefix: prefix.into(),
access: Access::Undeclared,
routes: Vec::new(),
cors: None,
cache: crate::CachePolicy::Unset,
}
}
#[must_use]
pub fn cors(mut self, cors: crate::Cors) -> Self {
self.cors = Some(Arc::new(cors));
self
}
#[must_use]
pub fn cache(mut self, policy: crate::CachePolicy) -> Self {
self.cache = policy;
self
}
#[must_use]
pub fn public(mut self) -> Self {
self.access = Access::Public;
self
}
#[must_use]
pub fn allow(mut self, rule: R) -> Self {
self.access = Access::Rule(Arc::new(rule));
self
}
#[must_use]
pub fn route(mut self, route: Route<I, R, E>) -> Self {
self.routes.push(route);
self
}
#[must_use]
pub fn group(mut self, group: Group<I, R, E>) -> Self {
let inherited = match &group.access {
Access::Undeclared => self.access.clone(),
a => a.clone(),
};
let cors = group.cors.or_else(|| self.cors.clone());
let cache = if group.cache == crate::CachePolicy::Unset {
self.cache
} else {
group.cache
};
for mut r in group.routes {
r.pattern = join(&group.prefix, &r.pattern);
if matches!(r.access, Access::Undeclared) {
r.access = inherited.clone();
}
if r.cors.is_none() {
r.cors.clone_from(&cors);
}
if r.cache == crate::CachePolicy::Unset {
r.cache = cache;
}
self.routes.push(r);
}
self
}
pub(crate) fn into_routes(self) -> impl Iterator<Item = Route<I, R, E>> {
let prefix = self.prefix;
let access = self.access;
let cors = self.cors;
let cache = self.cache;
self.routes.into_iter().map(move |mut r| {
r.pattern = join(&prefix, &r.pattern);
if matches!(r.access, Access::Undeclared) {
r.access = access.clone();
}
if r.cors.is_none() {
r.cors.clone_from(&cors);
}
if r.cache == crate::CachePolicy::Unset {
r.cache = cache;
}
r
})
}
}
impl<I, R, E: Render> std::fmt::Debug for Group<I, R, E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Group")
.field("prefix", &self.prefix)
.field("routes", &self.routes)
.finish_non_exhaustive()
}
}
pub(crate) fn join(prefix: &str, pattern: &str) -> String {
let p = prefix.trim_end_matches('/');
if pattern == "/" || pattern.is_empty() {
if p.is_empty() { "/".into() } else { p.into() }
} else if pattern.starts_with('/') {
format!("{p}{pattern}")
} else {
format!("{p}/{pattern}")
}
}
#[cfg(test)]
mod tests {
use super::join;
#[test]
fn test_join() {
assert_eq!(join("/admin", "/"), "/admin");
assert_eq!(join("/admin/", "/users"), "/admin/users");
assert_eq!(join("/", "/x"), "/x");
assert_eq!(join("", "/"), "/");
assert_eq!(join("/a", "b"), "/a/b");
}
}