use crate::handler::{boxed, BoxHandler, IntoHandler};
use http::Method;
use std::collections::HashMap;
pub fn allow_header_value(mut methods: Vec<Method>) -> Option<String> {
if methods.is_empty() {
return None;
}
if methods.contains(&Method::GET) && !methods.contains(&Method::HEAD) {
methods.push(Method::HEAD);
}
if !methods.contains(&Method::OPTIONS) {
methods.push(Method::OPTIONS);
}
methods.sort_by(|a, b| a.as_str().cmp(b.as_str()));
methods.dedup();
Some(
methods
.iter()
.map(Method::as_str)
.collect::<Vec<_>>()
.join(", "),
)
}
#[non_exhaustive]
pub enum Match {
Found {
handler: BoxHandler,
params: crate::call::Params,
},
MethodNotAllowed {
allow: Vec<Method>,
},
NotFound,
BadPath,
}
#[derive(Default)]
struct Node {
statics: HashMap<String, Node>,
param: Option<(String, Box<Node>)>, wildcard: Option<(String, BoxHandlers)>, handlers: BoxHandlers,
}
struct Candidate {
guards: Vec<crate::guard::BoxGuard>,
handler: BoxHandler,
}
#[derive(Default)]
struct BoxHandlers(HashMap<Method, Vec<Candidate>>);
impl BoxHandlers {
fn pick(&self, method: &Method, call: &crate::call::Call) -> Option<&BoxHandler> {
self.0
.get(method)?
.iter()
.find_map(|c| c.guards.iter().all(|g| g.check(call)).then_some(&c.handler))
}
fn methods(&self) -> Vec<Method> {
self.0.keys().cloned().collect()
}
fn methods_matching(&self, call: &crate::call::Call) -> Vec<Method> {
self.0
.iter()
.filter(|(_, cands)| cands.iter().any(|c| c.guards.iter().all(|g| g.check(call))))
.map(|(m, _)| m.clone())
.collect()
}
fn push(&mut self, method: Method, pattern: &str, handler: BoxHandler) {
let entries = self.0.entry(method.clone()).or_default();
if entries.iter().any(|c| c.guards.is_empty()) {
panic!("duplicate route: {method} {pattern} is already registered");
}
entries.push(Candidate {
guards: Vec::new(),
handler,
});
}
}
impl std::fmt::Debug for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Node").finish_non_exhaustive()
}
}
#[derive(Debug, Default)]
pub struct Router {
root: Node,
inventory: Vec<(Method, String)>,
}
impl Router {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, method: Method, pattern: &str, handler: BoxHandler) {
self.inventory.push((method.clone(), pattern.to_string()));
let mut node = &mut self.root;
let segments: Vec<&str> = split_segments(pattern);
for (i, seg) in segments.iter().enumerate() {
if let Some(name) = seg.strip_prefix('{').and_then(|s| s.strip_suffix("...}")) {
assert!(
i == segments.len() - 1,
"wildcard `{{{name}...}}` must be last segment"
);
let entry = node
.wildcard
.get_or_insert_with(|| (name.to_string(), BoxHandlers::default()));
entry.1.push(method, pattern, handler);
return;
} else if let Some(name) = seg.strip_prefix('{').and_then(|s| s.strip_suffix('}')) {
let entry = node
.param
.get_or_insert_with(|| (name.to_string(), Box::new(Node::default())));
assert!(
entry.0 == name,
"conflicting path parameter names at the same position: \
`{{{}}}` and `{{{name}}}` (registering {method} {pattern})",
entry.0
);
node = entry.1.as_mut();
} else {
node = node.statics.entry(seg.to_string()).or_default();
}
}
node.handlers.push(method, pattern, handler);
}
pub fn route(&self, method: &Method, path: &str, call: &crate::call::Call) -> Match {
let decoded = match decode_segments(path) {
Some(d) => d,
None => return Match::BadPath,
};
let segments: Vec<&str> = decoded.iter().map(String::as_str).collect();
let mut params = crate::call::Params::new();
let found = Self::walk_matching(&self.root, &segments, 0, &mut params, &mut |node, p| {
node.handlers
.pick(method, call)
.map(|h| (h.clone(), p.clone()))
});
if let Some((handler, params)) = found {
return Match::Found { handler, params };
}
let mut exact_allow: Vec<Method> = Vec::new();
params.clear();
Self::walk_matching(&self.root, &segments, 0, &mut params, &mut |node,
_|
-> Option<
(),
> {
for m in node.handlers.methods_matching(call) {
if !exact_allow.contains(&m) {
exact_allow.push(m);
}
}
None
});
params.clear();
match Self::walk_wildcard(&self.root, &segments, 0, method, call, &mut params) {
Some(found @ Match::Found { .. }) => found,
Some(Match::MethodNotAllowed { allow: wild_allow }) => {
let mut allow = exact_allow;
for m in wild_allow {
if !allow.contains(&m) {
allow.push(m);
}
}
Match::MethodNotAllowed { allow }
}
_ if !exact_allow.is_empty() => Match::MethodNotAllowed { allow: exact_allow },
_ => Match::NotFound,
}
}
fn attach_guard(&mut self, method: &Method, pattern: &str, g: crate::guard::BoxGuard) {
let mut node = &mut self.root;
let segments: Vec<&str> = split_segments(pattern);
for (i, seg) in segments.iter().enumerate() {
if let Some(_name) = seg.strip_prefix('{').and_then(|s| s.strip_suffix("...}")) {
debug_assert!(i == segments.len() - 1);
if let Some(entry) = node.wildcard.as_mut() {
if let Some(c) = entry.1 .0.get_mut(method).and_then(|v| v.last_mut()) {
c.guards.push(g);
}
}
return;
} else if seg.starts_with('{') && seg.ends_with('}') {
match node.param.as_mut() {
Some(entry) => node = entry.1.as_mut(),
None => return,
}
} else {
match node.statics.get_mut(*seg) {
Some(child) => node = child,
None => return,
}
}
}
if let Some(c) = node.handlers.0.get_mut(method).and_then(|v| v.last_mut()) {
c.guards.push(g);
}
}
fn attach_limit(&mut self, method: &Method, pattern: &str, max_body_bytes: usize) {
let mut node = &mut self.root;
let segments: Vec<&str> = split_segments(pattern);
for (i, seg) in segments.iter().enumerate() {
if seg
.strip_prefix('{')
.and_then(|s| s.strip_suffix("...}"))
.is_some()
{
debug_assert!(i == segments.len() - 1);
if let Some(entry) = node.wildcard.as_mut() {
if let Some(c) = entry.1 .0.get_mut(method).and_then(|v| v.last_mut()) {
let inner = c.handler.clone();
c.handler = std::sync::Arc::new(LimitedHandler {
max_body_bytes,
inner,
});
}
}
return;
} else if seg.starts_with('{') && seg.ends_with('}') {
match node.param.as_mut() {
Some(entry) => node = entry.1.as_mut(),
None => return,
}
} else {
match node.statics.get_mut(*seg) {
Some(child) => node = child,
None => return,
}
}
}
if let Some(c) = node.handlers.0.get_mut(method).and_then(|v| v.last_mut()) {
let inner = c.handler.clone();
c.handler = std::sync::Arc::new(LimitedHandler {
max_body_bytes,
inner,
});
}
}
pub fn methods_for(&self, path: &str) -> Vec<Method> {
let decoded = match decode_segments(path) {
Some(d) => d,
None => return Vec::new(),
};
let segments: Vec<&str> = decoded.iter().map(String::as_str).collect();
let mut params = crate::call::Params::new();
let mut out: Vec<Method> = Vec::new();
Self::walk_matching(&self.root, &segments, 0, &mut params, &mut |node,
_|
-> Option<
(),
> {
for m in node.handlers.methods() {
if !out.contains(&m) {
out.push(m);
}
}
None
});
Self::collect_wildcard_methods(&self.root, &segments, 0, &mut out);
out
}
fn collect_wildcard_methods(node: &Node, segs: &[&str], i: usize, out: &mut Vec<Method>) {
if i < segs.len() {
if let Some(child) = node.statics.get(segs[i]) {
Self::collect_wildcard_methods(child, segs, i + 1, out);
}
if let Some((_, child)) = &node.param {
Self::collect_wildcard_methods(child, segs, i + 1, out);
}
}
if let Some((_, handlers)) = &node.wildcard {
if segs[i..]
.iter()
.any(|s| s.contains('/') || s.contains('\\'))
{
return;
}
for m in handlers.methods() {
if !out.contains(&m) {
out.push(m);
}
}
}
}
pub fn all_methods(&self) -> Vec<Method> {
let mut out = Vec::new();
Self::collect_methods(&self.root, &mut out);
out
}
pub fn routes(&self) -> &[(Method, String)] {
&self.inventory
}
fn collect_methods(node: &Node, out: &mut Vec<Method>) {
for m in node.handlers.methods() {
if !out.contains(&m) {
out.push(m);
}
}
if let Some((_, handlers)) = &node.wildcard {
for m in handlers.methods() {
if !out.contains(&m) {
out.push(m);
}
}
}
for child in node.statics.values() {
Self::collect_methods(child, out);
}
if let Some((_, child)) = &node.param {
Self::collect_methods(child, out);
}
}
fn walk_matching<'a, T>(
node: &'a Node,
segs: &[&str],
i: usize,
params: &mut crate::call::Params,
accept: &mut impl FnMut(&'a Node, &crate::call::Params) -> Option<T>,
) -> Option<T> {
if i == segs.len() {
return accept(node, params);
}
let seg = segs[i];
if let Some(child) = node.statics.get(seg) {
if let Some(found) = Self::walk_matching(child, segs, i + 1, params, accept) {
return Some(found);
}
}
if let Some((name, child)) = &node.param {
let previous = params.get(name).map(str::to_string);
params.insert(name.clone(), seg.to_string());
if let Some(found) = Self::walk_matching(child, segs, i + 1, params, accept) {
return Some(found);
}
match previous {
Some(v) => params.insert(name.clone(), v),
None => params.remove(name),
}
}
None
}
#[allow(clippy::too_many_arguments)]
fn walk_wildcard(
node: &Node,
segs: &[&str],
i: usize,
method: &Method,
call: &crate::call::Call,
params: &mut crate::call::Params,
) -> Option<Match> {
let mut method_mismatch: Option<Match> = None;
if i < segs.len() {
if let Some(child) = node.statics.get(segs[i]) {
match Self::walk_wildcard(child, segs, i + 1, method, call, params) {
Some(found @ Match::Found { .. }) => return Some(found),
Some(other) => method_mismatch = Some(other),
None => {}
}
}
if let Some((pname, child)) = &node.param {
params.insert(pname.clone(), segs[i].to_string());
match Self::walk_wildcard(child, segs, i + 1, method, call, params) {
Some(found @ Match::Found { .. }) => return Some(found),
Some(other) => {
params.remove(pname);
method_mismatch = Some(other);
}
None => params.remove(pname),
}
}
}
if let Some((name, handlers)) = &node.wildcard {
if segs[i..]
.iter()
.any(|s| s.contains('/') || s.contains('\\'))
{
return Some(Match::BadPath);
}
let rest = segs[i..].join("/");
params.insert(name.clone(), rest);
return Some(match handlers.pick(method, call) {
Some(h) => Match::Found {
handler: h.clone(),
params: std::mem::take(params),
},
None => {
params.remove(name);
let mut allow = handlers.methods_matching(call);
if let Some(Match::MethodNotAllowed { allow: deeper }) = method_mismatch {
for m in deeper {
if !allow.contains(&m) {
allow.push(m);
}
}
}
Match::MethodNotAllowed { allow }
}
});
}
method_mismatch
}
}
struct LimitedHandler {
max_body_bytes: usize,
inner: BoxHandler,
}
impl crate::handler::Handler for LimitedHandler {
fn handle(&self, mut call: crate::call::Call) -> crate::handler::HandlerFuture {
call.insert(crate::extract::RouteBodyLimit(self.max_body_bytes));
let inner = self.inner.clone();
Box::pin(async move { inner.handle(call).await })
}
}
struct ScopedHandler {
chain: Vec<std::sync::Arc<dyn crate::pipeline::Middleware>>,
inner: BoxHandler,
}
impl crate::handler::Handler for ScopedHandler {
fn handle(&self, call: crate::call::Call) -> crate::handler::HandlerFuture {
let inner = self.inner.clone();
let endpoint: crate::pipeline::Endpoint = std::sync::Arc::new(move |call| {
let inner = inner.clone();
Box::pin(async move { inner.handle(call).await })
});
let chain: std::collections::VecDeque<_> = self.chain.iter().cloned().collect();
Box::pin(crate::pipeline::Next::new(chain, endpoint).run(call))
}
}
fn split_segments(path: &str) -> Vec<&str> {
path.split('/').filter(|s| !s.is_empty()).collect()
}
fn decode_segments(path: &str) -> Option<Vec<String>> {
split_segments(path)
.into_iter()
.map(crate::path::decode_path_segment)
.collect()
}
pub struct RouteBuilder<'r> {
router: &'r mut Router,
prefix: String,
chain: Vec<std::sync::Arc<dyn crate::pipeline::Middleware>>,
last: Option<(Method, String)>,
}
impl<'r> RouteBuilder<'r> {
pub(crate) fn new(router: &'r mut Router) -> Self {
Self {
router,
prefix: String::new(),
chain: Vec::new(),
last: None,
}
}
pub fn intercept<M: crate::pipeline::Middleware>(&mut self, mw: M) -> &mut Self {
self.chain.push(std::sync::Arc::new(mw));
self
}
fn full(&self, path: &str) -> String {
let mut p = self.prefix.clone();
if !path.starts_with('/') {
p.push('/');
}
p.push_str(path);
p
}
pub fn method<Marker, H>(&mut self, method: Method, path: &str, handler: H) -> &mut Self
where
H: IntoHandler<Marker>,
{
let full = self.full(path);
let h = boxed(handler.into_handler());
let h = if self.chain.is_empty() {
h
} else {
std::sync::Arc::new(ScopedHandler {
chain: self.chain.clone(),
inner: h,
}) as BoxHandler
};
self.router.add(method.clone(), &full, h);
self.last = Some((method, full));
self
}
pub fn guard(&mut self, g: crate::guard::BoxGuard) -> &mut Self {
let (method, path) = self
.last
.clone()
.expect("guard() must follow a route registration");
self.router.attach_guard(&method, &path, g);
self
}
pub fn max_body_bytes(&mut self, n: usize) -> &mut Self {
let (method, path) = self
.last
.clone()
.expect("max_body_bytes() must follow a route registration");
self.router.attach_limit(&method, &path, n);
self
}
pub fn get<Marker, H>(&mut self, path: &str, handler: H) -> &mut Self
where
H: IntoHandler<Marker>,
{
self.method(Method::GET, path, handler)
}
pub fn post<Marker, H>(&mut self, path: &str, handler: H) -> &mut Self
where
H: IntoHandler<Marker>,
{
self.method(Method::POST, path, handler)
}
pub fn put<Marker, H>(&mut self, path: &str, handler: H) -> &mut Self
where
H: IntoHandler<Marker>,
{
self.method(Method::PUT, path, handler)
}
pub fn delete<Marker, H>(&mut self, path: &str, handler: H) -> &mut Self
where
H: IntoHandler<Marker>,
{
self.method(Method::DELETE, path, handler)
}
pub fn route(&mut self, path: &str, f: impl FnOnce(&mut RouteBuilder)) -> &mut Self {
let prefix = self.full(path);
let mut child = RouteBuilder {
router: self.router,
prefix,
chain: self.chain.clone(),
last: None,
};
f(&mut child);
self.last = None;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::call::Call;
use bytes::Bytes;
use http::{HeaderMap, StatusCode, Uri};
fn build() -> Router {
let mut r = Router::new();
{
let mut b = RouteBuilder::new(&mut r);
b.get("/", |_c: Call| async { "root" });
b.route("/users", |b| {
b.get("/{id}", |c: Call| async move {
format!("user {}", c.param_raw("id").unwrap())
});
b.post("/", |_c: Call| async { (StatusCode::CREATED, "created") });
});
b.get("/files/{path...}", |c: Call| async move {
format!("file {}", c.param_raw("path").unwrap())
});
}
r
}
fn probe(path: &str) -> Call {
Call::new(
Method::GET,
path.parse::<Uri>().unwrap_or_else(|_| "/".parse().unwrap()),
HeaderMap::new(),
Bytes::new(),
)
}
fn run(r: &Router, m: Method, path: &str) -> Match {
r.route(&m, path, &probe(path))
}
#[tokio::test]
async fn matches_static_and_param() {
let r = build();
match run(&r, Method::GET, "/users/7") {
Match::Found { handler, params } => {
assert_eq!(params.get("id").unwrap(), "7");
let mut c = Call::new(
Method::GET,
"/users/7".parse::<Uri>().unwrap(),
HeaderMap::new(),
Bytes::new(),
);
c.set_params(params);
let res = handler.handle(c).await;
assert_eq!(res.body, Bytes::from("user 7"));
}
_ => panic!("expected Found"),
}
}
fn build_shadowed() -> Router {
let mut r = Router::new();
{
let mut b = RouteBuilder::new(&mut r);
b.get("/files/{path...}", |c: Call| async move {
format!("wild:{}", c.param_raw("path").unwrap_or(""))
});
b.get("/files/special/x", |_c: Call| async { "static" });
b.post("/files/only-post", |_c: Call| async { "posted" });
}
r
}
#[test]
fn path_params_are_percent_decoded() {
let mut r = Router::new();
{
let mut b = RouteBuilder::new(&mut r);
b.get("/u/{name}", |_c: Call| async { "" });
}
match r.route(&Method::GET, "/u/John%20Doe", &probe("/u/John%20Doe")) {
Match::Found { params, .. } => assert_eq!(params.get("name").unwrap(), "John Doe"),
_ => panic!("expected a match"),
}
}
#[test]
fn encoded_slash_does_not_create_a_segment() {
let mut r = Router::new();
{
let mut b = RouteBuilder::new(&mut r);
b.get("/u/{name}", |_c: Call| async { "" });
}
match r.route(&Method::GET, "/u/a%2Fb", &probe("/u/a%2Fb")) {
Match::Found { params, .. } => assert_eq!(params.get("name").unwrap(), "a/b"),
_ => panic!("%2F must not manufacture a separator"),
}
}
#[test]
fn a_route_with_a_literal_space_is_reachable_encoded() {
let mut r = Router::new();
{
let mut b = RouteBuilder::new(&mut r);
b.get("/a b", |_c: Call| async { "" });
}
assert!(matches!(
r.route(&Method::GET, "/a%20b", &probe("/a%20b")),
Match::Found { .. }
));
}
#[test]
fn malformed_encoding_is_a_bad_path() {
let mut r = Router::new();
{
let mut b = RouteBuilder::new(&mut r);
b.get("/u/{name}", |_c: Call| async { "" });
}
assert!(matches!(
r.route(&Method::GET, "/u/%zz", &probe("/u/%zz")),
Match::BadPath
));
assert!(matches!(
r.route(&Method::GET, "/u/%FF", &probe("/u/%FF")),
Match::BadPath
));
}
#[test]
fn wildcard_captures_decoded_segments() {
let mut r = Router::new();
{
let mut b = RouteBuilder::new(&mut r);
b.get("/f/{rest...}", |_c: Call| async { "" });
}
match r.route(&Method::GET, "/f/a%20b/c", &probe("/f/a%20b/c")) {
Match::Found { params, .. } => assert_eq!(params.get("rest").unwrap(), "a b/c"),
_ => panic!("expected the wildcard to match"),
}
}
#[test]
fn wildcard_is_reachable_through_a_static_sibling() {
let r = build_shadowed();
match run(&r, Method::GET, "/files/special") {
Match::Found { params, .. } => {
assert_eq!(params.get("path").unwrap(), "special");
}
_ => panic!("wildcard should serve /files/special"),
}
}
#[test]
fn exact_match_still_wins_over_wildcard() {
let r = build_shadowed();
match run(&r, Method::GET, "/files/special/x") {
Match::Found { params, .. } => {
assert!(
!params.contains_key("path"),
"the static route captured a wildcard param"
);
}
_ => panic!("expected the static route"),
}
}
#[test]
fn allow_header_unions_exact_and_wildcard_methods() {
let r = build_shadowed();
match run(&r, Method::DELETE, "/files/only-post") {
Match::MethodNotAllowed { allow } => {
assert!(allow.contains(&Method::POST), "missing the exact method");
assert!(allow.contains(&Method::GET), "missing the wildcard method");
}
_ => panic!("expected 405"),
}
}
#[test]
fn abandoned_branch_params_do_not_leak_into_the_wildcard() {
let mut r = Router::new();
{
let mut b = RouteBuilder::new(&mut r);
b.post("/u/{id}/edit", |_c: Call| async { "edit" });
b.get("/u/{rest...}", |_c: Call| async { "wild" });
}
match r.route(&Method::GET, "/u/7/edit", &probe("/u/7/edit")) {
Match::Found { params, .. } => {
assert_eq!(params.get("rest").unwrap(), "7/edit");
assert!(
!params.contains_key("id"),
"stale `id` leaked from the abandoned walk"
);
}
_ => panic!("the wildcard should have matched"),
}
}
#[test]
fn an_encoded_separator_in_a_wildcard_tail_does_not_override_a_matching_sibling() {
let mut r = Router::new();
{
let mut b = RouteBuilder::new(&mut r);
b.get("/files/{p...}", |_c: Call| async { "wild" });
b.post("/files/{n}", |_c: Call| async { "one" });
}
match run(&r, Method::GET, "/files/a%2Fb") {
Match::MethodNotAllowed { allow } => assert_eq!(allow, vec![Method::POST]),
_ => panic!("expected the sibling param route to answer"),
}
}
#[test]
fn unknown_path_is_not_found() {
let r = build();
assert!(matches!(run(&r, Method::GET, "/nope"), Match::NotFound));
}
#[test]
fn known_path_wrong_method_is_405() {
let r = build();
match run(&r, Method::DELETE, "/users/7") {
Match::MethodNotAllowed { allow } => assert!(allow.contains(&Method::GET)),
_ => panic!("expected 405"),
}
}
#[test]
fn wildcard_captures_rest() {
let r = build();
match run(&r, Method::GET, "/files/a/b/c.txt") {
Match::Found { params, .. } => assert_eq!(params.get("path").unwrap(), "a/b/c.txt"),
_ => panic!("expected wildcard Found"),
}
}
#[tokio::test]
async fn route_builder_accepts_boxed_handler() {
let pre: BoxHandler = boxed((|_c: Call| async { "pre-boxed" }).into_handler());
let mut r = Router::new();
{
let mut b = RouteBuilder::new(&mut r);
b.get("/pre", pre);
}
match run(&r, Method::GET, "/pre") {
Match::Found { handler, .. } => {
let c = Call::new(
Method::GET,
"/pre".parse::<Uri>().unwrap(),
HeaderMap::new(),
Bytes::new(),
);
let res = handler.handle(c).await;
assert_eq!(res.body, Bytes::from("pre-boxed"));
}
_ => panic!("expected Found"),
}
}
}