use std::sync::Arc;
use rivet_http::{Method, Request, Response};
pub type RouteHandler = Arc<dyn Fn(Request) -> Response + Send + Sync + 'static>;
fn default_handler(_req: Request) -> Response {
Response::ok("ok")
}
#[derive(Clone)]
pub struct Entry {
pub method: Method,
pub path: String,
handler: RouteHandler,
}
impl core::fmt::Debug for Entry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Entry")
.field("method", &self.method)
.field("path", &self.path)
.finish()
}
}
impl Entry {
pub fn invoke(&self, req: Request) -> Response {
(self.handler)(req)
}
}
#[derive(Debug, Default, Clone)]
pub struct Registry {
routes: Vec<Entry>,
}
#[derive(Debug, Clone)]
pub enum Match<'a> {
Matched {
route: &'a Entry,
head_fallback: bool,
},
MethodNotAllowed {
allow: Vec<Method>,
},
NotFound,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum RouteRegistryError {
#[error("duplicate route: {method:?} {path}")]
Duplicate { method: Method, path: String },
}
impl Registry {
pub fn new() -> Self {
Self { routes: Vec::new() }
}
pub fn add_route(
&mut self,
method: Method,
path: impl Into<String>,
) -> Result<(), RouteRegistryError> {
self.add_route_with_handler(method, path, default_handler)
}
pub fn add_route_with_handler<F>(
&mut self,
method: Method,
path: impl Into<String>,
handler: F,
) -> Result<(), RouteRegistryError>
where
F: Fn(Request) -> Response + Send + Sync + 'static,
{
let path = path.into();
if self
.routes
.iter()
.any(|r| r.method == method && r.path == path)
{
return Err(RouteRegistryError::Duplicate { method, path });
}
self.routes.push(Entry {
method,
path,
handler: Arc::new(handler),
});
Ok(())
}
pub fn get<F>(&mut self, path: impl Into<String>, handler: F) -> Result<(), RouteRegistryError>
where
F: Fn(Request) -> Response + Send + Sync + 'static,
{
self.add_route_with_handler(Method::Get, path, handler)
}
pub fn post<F>(&mut self, path: impl Into<String>, handler: F) -> Result<(), RouteRegistryError>
where
F: Fn(Request) -> Response + Send + Sync + 'static,
{
self.add_route_with_handler(Method::Post, path, handler)
}
pub fn put<F>(&mut self, path: impl Into<String>, handler: F) -> Result<(), RouteRegistryError>
where
F: Fn(Request) -> Response + Send + Sync + 'static,
{
self.add_route_with_handler(Method::Put, path, handler)
}
pub fn patch<F>(
&mut self,
path: impl Into<String>,
handler: F,
) -> Result<(), RouteRegistryError>
where
F: Fn(Request) -> Response + Send + Sync + 'static,
{
self.add_route_with_handler(Method::Patch, path, handler)
}
pub fn delete<F>(
&mut self,
path: impl Into<String>,
handler: F,
) -> Result<(), RouteRegistryError>
where
F: Fn(Request) -> Response + Send + Sync + 'static,
{
self.add_route_with_handler(Method::Delete, path, handler)
}
pub fn head<F>(&mut self, path: impl Into<String>, handler: F) -> Result<(), RouteRegistryError>
where
F: Fn(Request) -> Response + Send + Sync + 'static,
{
self.add_route_with_handler(Method::Head, path, handler)
}
pub fn options<F>(
&mut self,
path: impl Into<String>,
handler: F,
) -> Result<(), RouteRegistryError>
where
F: Fn(Request) -> Response + Send + Sync + 'static,
{
self.add_route_with_handler(Method::Options, path, handler)
}
pub fn routes(&self) -> &[Entry] {
&self.routes
}
pub fn match_request(&self, method: &Method, path: &str) -> Match<'_> {
let path_matches: Vec<&Entry> = self.routes.iter().filter(|r| r.path == path).collect();
if path_matches.is_empty() {
return Match::NotFound;
}
if let Some(route) = path_matches.iter().find(|r| r.method == *method) {
return Match::Matched {
route,
head_fallback: false,
};
}
if *method == Method::Head {
if let Some(route) = path_matches.iter().find(|r| r.method == Method::Get) {
return Match::Matched {
route,
head_fallback: true,
};
}
}
let mut allow = Vec::new();
for route in path_matches {
if !allow.contains(&route.method) {
allow.push(route.method.clone());
}
}
Match::MethodNotAllowed { allow }
}
pub fn len(&self) -> usize {
self.routes.len()
}
pub fn is_empty(&self) -> bool {
self.routes.is_empty()
}
}