use crate::{Method, Middleware, Request, Response, Result};
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
#[derive(Debug, Clone, PartialEq)]
pub enum Segment {
Static(String),
Dynamic(String),
}
#[derive(Debug, Clone)]
pub struct PathPattern {
segments: Vec<Segment>,
raw: String,
}
impl PathPattern {
pub fn parse(pattern: &str) -> Self {
let segments = pattern
.split('/')
.filter(|s| !s.is_empty())
.map(|segment| {
if segment.starts_with(':') {
Segment::Dynamic(segment[1..].to_string())
} else {
Segment::Static(segment.to_string())
}
})
.collect();
Self {
segments,
raw: pattern.to_string(),
}
}
pub fn matches(&self, path: &str) -> Option<HashMap<String, String>> {
let path_segments: Vec<&str> = path
.split('/')
.filter(|s| !s.is_empty())
.collect();
if path_segments.len() != self.segments.len() {
return None;
}
let mut params = HashMap::new();
for (pattern_seg, path_seg) in self.segments.iter().zip(path_segments.iter()) {
match pattern_seg {
Segment::Static(expected) => {
if expected != path_seg {
return None;
}
}
Segment::Dynamic(param_name) => {
let decoded = urlencoding::decode(path_seg)
.unwrap_or_else(|_| std::borrow::Cow::Borrowed(*path_seg));
params.insert(param_name.clone(), decoded.into_owned());
}
}
}
Some(params)
}
pub fn raw(&self) -> &str {
&self.raw
}
pub fn segments(&self) -> &[Segment] {
&self.segments
}
}
pub type HandlerFn = std::sync::Arc<
dyn Fn(Request) -> Pin<Box<dyn Future<Output = Result<Response>> + Send>>
+ Send
+ Sync
+ 'static,
>;
pub struct Route {
method: Method,
pattern: PathPattern,
handler: HandlerFn,
}
impl Route {
pub fn new<F, Fut>(method: Method, pattern: &str, handler: F) -> Self
where
F: Fn(Request) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Response>> + Send + 'static,
{
let handler_fn = std::sync::Arc::new(move |req: Request| {
Box::pin(handler(req)) as Pin<Box<dyn Future<Output = Result<Response>> + Send>>
});
Self {
method,
pattern: PathPattern::parse(pattern),
handler: handler_fn,
}
}
pub fn method(&self) -> &Method {
&self.method
}
pub fn pattern(&self) -> &PathPattern {
&self.pattern
}
pub fn matches(&self, method: &Method, path: &str) -> Option<HashMap<String, String>> {
if self.method == *method {
self.pattern.matches(path)
} else {
None
}
}
pub async fn handle(&self, req: Request) -> Result<Response> {
(self.handler)(req).await
}
pub fn handler_fn(&self) -> HandlerFn {
self.handler.clone()
}
}
pub struct Router {
prefix: String,
routes: Vec<Route>,
middleware: Vec<std::sync::Arc<dyn Middleware>>,
}
impl Router {
pub fn new(prefix: &str) -> Self {
Self {
prefix: prefix.to_string(),
routes: Vec::new(),
middleware: Vec::new(),
}
}
pub fn get<F, Fut>(&mut self, path: &str, handler: F) -> &mut Self
where
F: Fn(Request) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Response>> + Send + 'static,
{
let full_path = format!("{}{}", self.prefix, path);
self.routes.push(Route::new(Method::GET, &full_path, handler));
self
}
pub fn post<F, Fut>(&mut self, path: &str, handler: F) -> &mut Self
where
F: Fn(Request) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Response>> + Send + 'static,
{
let full_path = format!("{}{}", self.prefix, path);
self.routes.push(Route::new(Method::POST, &full_path, handler));
self
}
pub fn put<F, Fut>(&mut self, path: &str, handler: F) -> &mut Self
where
F: Fn(Request) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Response>> + Send + 'static,
{
let full_path = format!("{}{}", self.prefix, path);
self.routes.push(Route::new(Method::PUT, &full_path, handler));
self
}
pub fn delete<F, Fut>(&mut self, path: &str, handler: F) -> &mut Self
where
F: Fn(Request) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Response>> + Send + 'static,
{
let full_path = format!("{}{}", self.prefix, path);
self.routes.push(Route::new(Method::DELETE, &full_path, handler));
self
}
pub fn patch<F, Fut>(&mut self, path: &str, handler: F) -> &mut Self
where
F: Fn(Request) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Response>> + Send + 'static,
{
let full_path = format!("{}{}", self.prefix, path);
self.routes.push(Route::new(Method::PATCH, &full_path, handler));
self
}
pub fn use_middleware(&mut self, middleware: std::sync::Arc<dyn Middleware>) -> &mut Self {
self.middleware.push(middleware);
self
}
pub fn routes(&self) -> &[Route] {
&self.routes
}
pub fn find_route(&self, method: &Method, path: &str) -> Option<(&Route, HashMap<String, String>)> {
for route in &self.routes {
if let Some(params) = route.matches(method, path) {
return Some((route, params));
}
}
None
}
pub fn path_exists(&self, path: &str) -> bool {
self.routes.iter().any(|route| {
route.pattern.matches(path).is_some()
})
}
pub fn allowed_methods(&self, path: &str) -> Vec<Method> {
self.routes
.iter()
.filter(|route| route.pattern.matches(path).is_some())
.map(|route| *route.method())
.collect()
}
pub fn prefix(&self) -> &str {
&self.prefix
}
pub fn middleware(&self) -> &[std::sync::Arc<dyn Middleware>] {
&self.middleware
}
pub fn collect_routes(self) -> Vec<Route> {
self.routes
}
pub fn mount(&mut self, mount_prefix: &str, mut router: Router) -> &mut Self {
for route in router.routes.drain(..) {
let combined_prefix = if self.prefix.is_empty() && mount_prefix.is_empty() {
String::new()
} else if self.prefix.is_empty() {
mount_prefix.to_string()
} else if mount_prefix.is_empty() {
self.prefix.clone()
} else {
format!("{}{}", self.prefix, mount_prefix)
};
let new_pattern = if combined_prefix.is_empty() {
route.pattern.raw().to_string()
} else {
format!("{}{}", combined_prefix, route.pattern.raw())
};
let new_route = Route {
method: route.method,
pattern: PathPattern::parse(&new_pattern),
handler: route.handler,
};
self.routes.push(new_route);
}
for middleware in router.middleware.drain(..) {
self.middleware.push(middleware);
}
self
}
}