use router::{Param, Params};
use std::mem;
use std::str;
fn min(a: usize, b: usize) -> usize {
if a <= b {
return a;
}
b
}
fn count_params(path: &[u8]) -> u8 {
let mut n = 0;
for &c in path {
if c != b':' && c != b'*' {
continue;
}
n += 1;
}
if n > 255 {
return 255;
}
n as u8
}
#[derive(PartialEq, Clone, Debug, PartialOrd)]
pub enum NodeType {
Static,
Root,
Param,
CatchAll,
}
#[derive(Debug, Clone)]
pub struct Node<T> {
path: Vec<u8>,
wild_child: bool,
n_type: NodeType,
max_params: u8,
indices: Vec<u8>,
children: Vec<Box<Node<T>>>,
handle: Option<T>,
priority: u32,
}
impl<T> Node<T> {
pub fn new() -> Node<T> {
Node {
path: Vec::new(),
wild_child: false,
n_type: NodeType::Static,
max_params: 0,
indices: Vec::new(),
children: Vec::new(),
handle: None,
priority: 0,
}
}
fn increment_child_prio(&mut self, pos: usize) -> usize {
self.children[pos].priority += 1;
let prio = self.children[pos].priority;
let mut new_pos = pos;
while new_pos > 0 && self.children[new_pos - 1].priority < prio {
self.children.swap(new_pos - 1, new_pos);
new_pos -= 1;
}
if new_pos != pos {
self.indices = [
&self.indices[..new_pos], &self.indices[pos..pos + 1], &self.indices[new_pos..pos], &self.indices[pos + 1..], ].concat();
}
new_pos
}
pub fn add_route(&mut self, path: &str, handle: T) {
let full_path = path.clone();
let path = path.as_ref();
self.priority += 1;
let num_params = count_params(path);
if self.path.len() > 0 || self.children.len() > 0 {
self.add_route_loop(num_params, path, full_path, handle);
} else {
self.insert_child(num_params, path, full_path, handle);
self.n_type = NodeType::Root;
}
}
fn add_route_loop(&mut self, num_params: u8, mut path: &[u8], full_path: &str, handle: T) {
if num_params > self.max_params {
self.max_params = num_params;
}
let mut i = 0;
let max = min(path.len(), self.path.len());
while i < max && path[i] == self.path[i] {
i += 1;
}
if i < self.path.len() {
let mut child = Node {
path: self.path[i..].to_vec(),
wild_child: self.wild_child,
n_type: NodeType::Static,
indices: self.indices.clone(),
children: Vec::new(),
handle: self.handle.take(),
priority: self.priority - 1,
max_params: 0,
};
mem::swap(&mut self.children, &mut child.children);
for c in &child.children {
if c.max_params > child.max_params {
child.max_params = c.max_params;
}
}
self.children = vec![Box::new(child)];
self.indices = vec![self.path[i]];
self.path = path[..i].to_vec();
self.wild_child = false;
}
if i < path.len() {
path = &path[i..];
if self.wild_child {
return self.children[0].is_wild_child(num_params, path, full_path, handle);
}
let c = path[0];
if self.n_type == NodeType::Param && c == b'/' && self.children.len() == 1 {
self.children[0].priority += 1;
return self.children[0].add_route_loop(num_params, path, full_path, handle);
}
for mut i in 0..self.indices.len() {
if c == self.indices[i] {
i = self.increment_child_prio(i);
return self.children[i].add_route_loop(num_params, path, full_path, handle);
}
}
if c != b':' && c != b'*' {
self.indices.push(c);
let len = self.indices.len();
let child: Box<Node<T>> = Box::new(Node {
path: Vec::new(),
wild_child: false,
n_type: NodeType::Static,
max_params: num_params,
indices: Vec::new(),
children: Vec::new(),
handle: None,
priority: 0,
});
self.children.push(child);
let i = self.increment_child_prio(len - 1);
return self.children[i].insert_child(num_params, path, full_path, handle);
}
return self.insert_child(num_params, path, full_path, handle);
} else if i == path.len() {
if self.handle.is_some() {
panic!("a handle is already registered for path '{}'", full_path);
}
self.handle = Some(handle);
}
return;
}
fn is_wild_child(&mut self, mut num_params: u8, path: &[u8], full_path: &str, handle: T) {
self.priority += 1;
if num_params > self.max_params {
self.max_params = num_params;
}
num_params -= 1;
if path.len() >= self.path.len()
&& self.path == &path[..self.path.len()]
&& (self.path.len() >= path.len() || path[self.path.len()] == b'/')
{
self.add_route_loop(num_params, path, full_path, handle);
} else {
let path_seg = if self.n_type == NodeType::CatchAll {
str::from_utf8(path).unwrap()
} else {
str::from_utf8(path)
.unwrap()
.splitn(2, '/')
.into_iter()
.next()
.unwrap()
};
let prefix = [
&full_path[..full_path.find(path_seg).unwrap()],
str::from_utf8(&self.path).unwrap(),
].concat();
panic!("'{}' in new path '{}' conflicts with existing wildcard '{}' in existing prefix '{}'", path_seg, full_path, str::from_utf8(&self.path).unwrap(), prefix);
}
}
fn insert_child(&mut self, num_params: u8, path: &[u8], full_path: &str, handle: T) {
self.insert_child_loop(0, 0, num_params, path, full_path, handle);
}
fn insert_child_loop(
&mut self,
mut offset: usize,
mut i: usize,
mut num_params: u8,
path: &[u8],
full_path: &str,
handle: T,
) {
if num_params > 0 {
let max = path.len();
let c = path[i];
if c != b':' && c != b'*' {
return self.insert_child_loop(offset, i + 1, num_params, path, full_path, handle);
}
let mut end = i + 1;
while end < max && path[end] != b'/' {
match path[end] {
b':' | b'*' => panic!(
"only one wildcard per path segment is allowed, has: '{}' in path '{}'",
str::from_utf8(&path[i..]).unwrap(),
full_path
),
_ => end += 1,
}
}
if self.children.len() > 0 {
panic!(
"wildcard route '{}' conflicts with existing children in path '{}'",
str::from_utf8(&path[i..end]).unwrap(),
full_path
)
}
if end - i < 2 {
panic!(
"wildcards must be named with a non-empty name in path '{}'",
full_path
);
}
if c == b':' {
if i > 0 {
self.path = path[offset..i].to_vec();
offset = i;
}
let child = Box::new(Node {
path: Vec::new(),
wild_child: false,
n_type: NodeType::Param,
max_params: num_params,
indices: Vec::new(),
children: Vec::new(),
handle: None,
priority: 0,
});
self.children = vec![child];
self.wild_child = true;
self.children[0].priority += 1;
num_params -= 1;
if end < max {
self.children[0].path = path[offset..end].to_vec();
offset = end;
let child: Box<Node<T>> = Box::new(Node {
path: Vec::new(),
wild_child: false,
n_type: NodeType::Static,
max_params: num_params,
indices: Vec::new(),
children: Vec::new(),
handle: None,
priority: 1,
});
self.children[0].children.push(child);
self.children[0].children[0].insert_child_loop(
offset,
i + 1,
num_params,
path,
full_path,
handle,
);
} else {
self.children[0].insert_child_loop(
offset,
i + 1,
num_params,
path,
full_path,
handle,
);
}
} else {
if end != max || num_params > 1 {
panic!(
"catch-all routes are only allowed at the end of the path in path '{}'",
full_path
);
}
if self.path.len() > 0 && self.path[self.path.len() - 1] == b'/' {
panic!(
"catch-all conflicts with existing handle for the path segment root in path '{}'",
full_path
);
}
i -= 1;
if path[i] != b'/' {
panic!("no / before catch-all in path '{}'", full_path);
}
self.path = path[offset..i].to_vec();
let child = Box::new(Node {
path: Vec::new(),
wild_child: true,
n_type: NodeType::CatchAll,
max_params: 1,
indices: Vec::new(),
children: Vec::new(),
handle: None,
priority: 0,
});
self.children = vec![child];
self.indices = vec![path[i]];
self.children[0].priority += 1;
let child: Box<Node<T>> = Box::new(Node {
path: path[i..].to_vec(),
wild_child: false,
n_type: NodeType::CatchAll,
max_params: 1,
indices: Vec::new(),
children: Vec::new(),
handle: Some(handle),
priority: 1,
});
self.children[0].children.push(child);
return;
}
} else {
self.path = path[offset..].to_vec();
self.handle = Some(handle);
}
}
pub fn get_value(&self, path: &str) -> (Option<&T>, Params, bool) {
self.get_value_loop(path.as_ref(), Params::new())
}
fn get_value_loop(&self, mut path: &[u8], p: Params) -> (Option<&T>, Params, bool) {
if path.len() > self.path.len() {
if self.path == &path[..self.path.len()] {
path = &path[self.path.len()..];
if !self.wild_child {
let c = path[0];
for i in 0..self.indices.len() {
if c == self.indices[i] {
return self.children[i].get_value_loop(path, p);
}
}
let tsr = path == [b'/'] && self.handle.is_some();
return (None, p, tsr);
}
return self.children[0].handle_wildcard_child(path, p);
}
} else if self.path == path {
if self.handle.is_some() {
return (self.handle.as_ref(), p, false);
}
if path == [b'/'] && self.wild_child && self.n_type != NodeType::Root {
return (self.handle.as_ref(), p, true);
}
for i in 0..self.indices.len() {
if self.indices[i] == b'/' {
let tsr = (self.path.len() == 1 && self.children[i].handle.is_some())
|| (self.children[i].n_type == NodeType::CatchAll
&& self.children[i].children[0].handle.is_some());
return (self.handle.as_ref(), p, tsr);
}
}
return (self.handle.as_ref(), p, false);
}
let tsr = (path == [b'/'])
|| (self.path.len() == path.len() + 1
&& self.path[path.len()] == b'/'
&& path == &self.path[..self.path.len() - 1]
&& self.handle.is_some());
return (None, p, tsr);
}
fn handle_wildcard_child(&self, mut path: &[u8], mut p: Params) -> (Option<&T>, Params, bool) {
match self.n_type {
NodeType::Param => {
let mut end = 0;
while end < path.len() && path[end] != b'/' {
end += 1;
}
if p.is_empty() {
p = Params(Vec::with_capacity(self.max_params as usize));
}
p.push(Param {
key: String::from_utf8(self.path[1..].to_vec()).unwrap(),
value: String::from_utf8(path[..end].to_vec()).unwrap(),
});
if end < path.len() {
if self.children.len() > 0 {
path = &path[end..];
return self.children[0].get_value_loop(path, p);
}
let tsr = path.len() == end + 1;
return (None, p, tsr);
}
if self.handle.is_some() {
return (self.handle.as_ref(), p, false);
} else if self.children.len() == 1 {
let tsr = self.children[0].path == &[b'/'] && self.children[0].handle.is_some();
return (None, p, tsr);
}
return (None, p, false);
}
NodeType::CatchAll => {
if p.is_empty() {
p = Params(Vec::with_capacity(self.max_params as usize));
}
p.push(Param {
key: String::from_utf8(self.path[2..].to_vec()).unwrap(),
value: String::from_utf8(path.to_vec()).unwrap(),
});
return (self.handle.as_ref(), p, false);
}
_ => panic!("invalid node type"),
}
}
pub fn find_case_insensitive_path(
&self,
path: &str,
fix_trailing_slash: bool,
) -> (String, bool) {
let mut ci_path = Vec::with_capacity(path.len() + 1);
let found = self.find_case_insensitive_path_rec(
path.as_bytes(),
path.to_ascii_lowercase().as_bytes(),
&mut ci_path,
[0; 4],
fix_trailing_slash,
);
(String::from_utf8(ci_path).unwrap(), found)
}
fn find_case_insensitive_path_rec(
&self,
mut path: &[u8],
mut lo_path: &[u8],
ci_path: &mut Vec<u8>,
mut rb: [u8; 4],
fix_trailing_slash: bool,
) -> bool {
let lo_n_path: Vec<u8> = self.path.iter().map(|u| u.to_ascii_lowercase()).collect();
if lo_path.len() >= lo_n_path.len()
&& (lo_n_path.len() == 0 || lo_path[1..lo_n_path.len()] == lo_n_path[1..])
{
ci_path.append(&mut self.path.clone());
path = &path[self.path.len()..];
if path.len() > 0 {
let lo_old = lo_path.clone();
lo_path = &lo_path[lo_n_path.len()..];
if !self.wild_child {
rb = shift_n_rune_bytes(rb, lo_n_path.len());
if rb[0] != 0 {
for i in 0..self.indices.len() {
if self.indices[i] == rb[0] {
return self.children[i].find_case_insensitive_path_rec(
path,
lo_path,
ci_path,
rb,
fix_trailing_slash,
);
}
}
} else {
let mut rv = 0 as char;
let mut off = 0;
for j in 0..min(lo_n_path.len(), 3) {
let i = lo_n_path.len() - j;
if rune_start(lo_old[i]) {
rv = str::from_utf8(&lo_old[i..])
.unwrap()
.chars()
.next()
.unwrap();
off = j;
break;
}
}
rv.encode_utf8(&mut rb);
rb = shift_n_rune_bytes(rb, off);
for i in 0..self.indices.len() {
if self.indices[i] == rb[0] {
let found = self.children[i].find_case_insensitive_path_rec(
path,
lo_path,
ci_path,
rb,
fix_trailing_slash,
);
if found {
return true;
}
if ci_path.len() > self.children[i].path.len() {
let prev_len = ci_path.len() - self.children[i].path.len();
ci_path.truncate(prev_len);
}
break;
}
}
let up = rv.to_ascii_uppercase();
if up != rv {
up.encode_utf8(&mut rb);
rb = shift_n_rune_bytes(rb, off);
for i in 0..self.indices.len() {
if self.indices[i] == rb[0] {
return self.children[i].find_case_insensitive_path_rec(
path,
lo_path,
ci_path,
rb,
fix_trailing_slash,
);
}
}
}
}
return fix_trailing_slash && path == [b'/'] && self.handle.is_some();
}
return self.children[0].find_case_insensitive_path_rec_match(
path,
lo_path,
ci_path,
rb,
fix_trailing_slash,
);
} else {
if self.handle.is_some() {
return true;
}
if fix_trailing_slash {
for i in 0..self.indices.len() {
if self.indices[i] == b'/' {
if (self.children[i].path.len() == 1
&& self.children[i].handle.is_some())
|| (self.children[i].n_type == NodeType::CatchAll
&& self.children[i].children[0].handle.is_some())
{
ci_path.push(b'/');
return true;
}
return false;
}
}
}
return false;
}
}
if fix_trailing_slash {
if path == [b'/'] {
return true;
}
if lo_path.len() + 1 == lo_n_path.len()
&& lo_n_path[lo_path.len()] == b'/'
&& lo_path[1..] == lo_n_path[1..lo_path.len()]
&& self.handle.is_some()
{
ci_path.append(&mut self.path.clone());
return true;
}
}
false
}
fn find_case_insensitive_path_rec_match(
&self,
mut path: &[u8],
mut lo_path: &[u8],
ci_path: &mut Vec<u8>,
rb: [u8; 4],
fix_trailing_slash: bool,
) -> bool {
match self.n_type {
NodeType::Param => {
let mut k = 0;
while k < path.len() && path[k] != b'/' {
k += 1;
}
let mut path_k = path[..k].to_vec();
ci_path.append(&mut path_k);
if k < path.len() {
if self.children.len() > 0 {
lo_path = &lo_path[k..];
path = &path[k..];
return self.children[0].find_case_insensitive_path_rec(
path,
lo_path,
ci_path,
rb,
fix_trailing_slash,
);
}
if fix_trailing_slash && path.len() == k + 1 {
return true;
}
return false;
}
if self.handle.is_some() {
return true;
} else if fix_trailing_slash && self.children.len() == 1 {
if self.children[0].path == [b'/'] && self.children[0].handle.is_some() {
ci_path.push(b'/');
return true;
}
}
return false;
}
NodeType::CatchAll => {
ci_path.append(&mut path.to_vec());
return true;
}
_ => panic!("invalid node type"),
}
}
}
fn shift_n_rune_bytes(rb: [u8; 4], n: usize) -> [u8; 4] {
match n {
0 => rb,
1 => [rb[1], rb[2], rb[3], 0],
2 => [rb[2], rb[3], 0, 0],
3 => [rb[3], 0, 0, 0],
_ => [0; 4],
}
}
fn rune_start(b: u8) -> bool {
b & 0xC0 != 0x80
}
#[cfg(test)]
mod tests {
use super::*;
use router::Params;
use std::panic;
use std::sync::Mutex;
struct TestRequest<'a> {
path: &'a str,
nil_handler: bool,
route: &'a str,
ps: Params,
}
impl<'a> TestRequest<'a> {
pub fn new(
path: &'a str,
nil_handler: bool,
route: &'a str,
ps: Params,
) -> TestRequest<'a> {
TestRequest {
path,
nil_handler,
route,
ps,
}
}
}
type TestRequests<'a> = Vec<TestRequest<'a>>;
fn check_requests<T: Fn() -> String>(tree: &mut Node<T>, requests: TestRequests) {
for request in requests {
let (handler, ps, _) = tree.get_value(request.path);
if handler.is_none() {
if !request.nil_handler {
panic!(
"handle mismatch for route '{}': Expected non-nil handle",
request.path
);
}
} else if request.nil_handler {
panic!(
"handle m ismatch for route '{}': Expected nil handle",
request.path
);
} else {
match handler {
Some(h) => {
let res = h();
if res != request.route {
panic!(
"handle mismatch for route '{}': Wrong handle ({} != {})",
request.path, res, request.route
);
}
}
None => {
panic!("handle not found");
}
}
}
if ps != request.ps {
panic!("Params mismatch for route '{}'", request.path);
}
}
}
fn check_priorities<T: Fn() -> String>(n: &mut Node<T>) -> u32 {
let mut prio: u32 = 0;
for i in 0..n.children.len() {
prio += check_priorities(&mut *n.children[i]);
}
if n.handle.is_some() {
prio += 1;
}
if n.priority != prio {
panic!(
"priority mismatch for node '{}': is {}, should be {}",
str::from_utf8(&n.path).unwrap(),
n.priority,
prio
)
}
prio
}
fn check_max_params<T: Fn() -> String>(n: &mut Node<T>) -> u8 {
let mut max_params: u8 = 0;
for i in 0..n.children.len() {
let params = check_max_params(&mut *n.children[i]);
if params > max_params {
max_params = params;
}
}
if n.n_type > NodeType::Root && !n.wild_child {
max_params += 1;
}
if n.max_params != max_params {
panic!(
"maxParams mismatch for node '{}': is {}, should be {}",
str::from_utf8(&n.path).unwrap(),
n.max_params,
max_params,
)
}
max_params
}
fn fake_handler(val: &'static str) -> impl Fn() -> String {
move || val.to_string()
}
#[test]
fn test_count_params() {
assert_eq!(
2,
count_params("/path/:param1/static/*catch-all".as_bytes())
);
assert_eq!(255, count_params("/:param".repeat(256).as_bytes()));
}
#[test]
fn test_tree_add_and_get() {
let mut tree = Node::new();
let routes = vec![
"/hi",
"/contact",
"/co",
"/c",
"/a",
"/ab",
"/doc/",
"/doc/go_faq.html",
"/doc/go1.html",
"/α",
"/β",
];
for route in routes {
tree.add_route(route, fake_handler(route));
}
check_requests(
&mut tree,
vec![
TestRequest::new("/a", false, "/a", Params::new()),
TestRequest::new("/", true, "", Params::new()),
TestRequest::new("/hi", false, "/hi", Params::new()),
TestRequest::new("/contact", false, "/contact", Params::new()),
TestRequest::new("/co", false, "/co", Params::new()),
TestRequest::new("/con", true, "", Params::new()), TestRequest::new("/cona", true, "", Params::new()), TestRequest::new("/no", true, "", Params::new()), TestRequest::new("/ab", false, "/ab", Params::new()),
TestRequest::new("/α", false, "/α", Params::new()),
TestRequest::new("/β", false, "/β", Params::new()),
],
);
check_priorities(&mut tree);
check_max_params(&mut tree);
}
#[test]
fn test_tree_wildcard() {
let mut tree = Node::new();
let routes = vec![
"/",
"/cmd/:tool/:sub",
"/cmd/:tool/",
"/src/*filepath",
"/search/",
"/search/:query",
"/user_:name",
"/user_:name/about",
"/files/:dir/*filepath",
"/doc/",
"/doc/go_faq.html",
"/doc/go1.html",
"/info/:user/public",
"/info/:user/project/:project",
];
for route in routes {
tree.add_route(route, fake_handler(route));
}
check_requests(
&mut tree,
vec![
TestRequest::new("/", false, "/", Params::new()),
TestRequest::new(
"/cmd/test/",
false,
"/cmd/:tool/",
Params(vec![Param::new("tool", "test")]),
),
TestRequest::new(
"/cmd/test",
true,
"",
Params(vec![Param::new("tool", "test")]),
),
TestRequest::new(
"/cmd/test/3",
false,
"/cmd/:tool/:sub",
Params(vec![Param::new("tool", "test"), Param::new("sub", "3")]),
),
TestRequest::new(
"/src/",
false,
"/src/*filepath",
Params(vec![Param::new("filepath", "/")]),
),
TestRequest::new(
"/src/some/file.png",
false,
"/src/*filepath",
Params(vec![Param::new("filepath", "/some/file.png")]),
),
TestRequest::new("/search/", false, "/search/", Params::new()),
TestRequest::new(
"/search/someth!ng+in+ünìcodé",
false,
"/search/:query",
Params(vec![Param::new("query", "someth!ng+in+ünìcodé")]),
),
TestRequest::new(
"/search/someth!ng+in+ünìcodé/",
true,
"",
Params(vec![Param::new("query", "someth!ng+in+ünìcodé")]),
),
TestRequest::new(
"/user_gopher",
false,
"/user_:name",
Params(vec![Param::new("name", "gopher")]),
),
TestRequest::new(
"/user_gopher/about",
false,
"/user_:name/about",
Params(vec![Param::new("name", "gopher")]),
),
TestRequest::new(
"/files/js/inc/framework.js",
false,
"/files/:dir/*filepath",
Params(vec![
Param::new("dir", "js"),
Param::new("filepath", "/inc/framework.js"),
]),
),
TestRequest::new(
"/info/gordon/public",
false,
"/info/:user/public",
Params(vec![Param::new("user", "gordon")]),
),
TestRequest::new(
"/info/gordon/project/go",
false,
"/info/:user/project/:project",
Params(vec![
Param::new("user", "gordon"),
Param::new("project", "go"),
]),
),
],
);
check_priorities(&mut tree);
check_max_params(&mut tree);
}
type TestRoute = (&'static str, bool);
fn test_routes(routes: Vec<TestRoute>) {
let tree = Mutex::new(Node::new());
for route in routes {
let recv = panic::catch_unwind(|| {
let mut guard = match tree.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
guard.add_route(route.0, ());
});
if route.1 {
if recv.is_ok() {
panic!("no panic for conflicting route '{}'", route.0);
}
} else if recv.is_err() {
panic!("unexpected panic for route '{}': {:?}", route.0, recv);
}
}
}
#[test]
fn test_tree_wildcard_conflict() {
let routes = vec![
("/cmd/:tool/:sub", false),
("/cmd/vet", true),
("/src/*filepath", false),
("/src/*filepathx", true),
("/src/", true),
("/src1/", false),
("/src1/*filepath", true),
("/src2*filepath", true),
("/search/:query", false),
("/search/invalid", true),
("/user_:name", false),
("/user_x", true),
("/user_:name", true), ("/id:id", false),
("/id/:id", true),
];
test_routes(routes);
}
#[test]
fn test_tree_child_conflict() {
let routes = vec![
("/cmd/vet", false),
("/cmd/:tool/:sub", true),
("/src/AUTHORS", false),
("/src/*filepath", true),
("/user_x", false),
("/user_:name", true),
("/id/:id", false),
("/id:id", true),
("/:id", true),
("/*filepath", true),
];
test_routes(routes);
}
#[test]
fn test_tree_duplicate_path() {
let tree = Mutex::new(Node::new());
let routes = vec![
"/",
"/doc/",
"/src/*filepath",
"/search/:query",
"/user_:name",
];
for route in routes {
let mut recv = panic::catch_unwind(|| {
let mut guard = match tree.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
guard.add_route(route, fake_handler(route));
});
if recv.is_err() {
panic!("panic inserting route '{}': {:?}", route, recv);
}
recv = panic::catch_unwind(|| {
let mut guard = match tree.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
guard.add_route(route, fake_handler(route));
});
if recv.is_ok() {
panic!("no panic while inserting duplicate route '{}'", route);
}
}
check_requests(
&mut tree.lock().unwrap_or_else(|poisoned| poisoned.into_inner()),
vec![
TestRequest::new("/", false, "/", Params::new()),
TestRequest::new("/doc/", false, "/doc/", Params::new()),
TestRequest::new(
"/src/some/file.png",
false,
"/src/*filepath",
Params(vec![Param::new("filepath", "/some/file.png")]),
),
TestRequest::new(
"/search/someth!ng+in+ünìcodé",
false,
"/search/:query",
Params(vec![Param::new("query", "someth!ng+in+ünìcodé")]),
),
TestRequest::new(
"/user_gopher",
false,
"/user_:name",
Params(vec![Param::new("name", "gopher")]),
),
],
);
}
#[test]
fn test_empty_wildcard_name() {
let tree = Mutex::new(Node::new());
let routes = vec!["/user:", "/user:/", "/cmd/:/", "/src/*"];
for route in routes {
let mut recv = panic::catch_unwind(|| {
let mut guard = match tree.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
guard.add_route(route, fake_handler(route));
});
if recv.is_ok() {
panic!(
"no panic while inserting route with empty wildcard name '{}",
route
);
}
}
}
#[test]
fn test_tree_catch_all_conflict() {
let routes = vec![
("/src/*filepath/x", true),
("/src2/", false),
("/src2/*filepath/x", true),
];
test_routes(routes);
}
#[test]
fn test_tree_catch_all_conflict_root() {
let routes = vec![("/", false), ("/*filepath", true)];
test_routes(routes);
}
#[test]
fn test_tree_double_wildcard() {
let panic_msg = "only one wildcard per path segment is allowed";
let routes = vec!["/:foo:bar", "/:foo:bar/", "/:foo*bar"];
for route in routes {
let tree = Mutex::new(Node::new());
let mut recv = panic::catch_unwind(|| {
let mut guard = match tree.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
guard.add_route(route, fake_handler(route));
});
if recv.is_ok() {
panic!(panic_msg);
}
}
}
#[test]
fn test_tree_trailing_slash_redirect() {
let tree = Mutex::new(Node::new());
let routes = vec![
"/hi",
"/b/",
"/search/:query",
"/cmd/:tool/",
"/src/*filepath",
"/x",
"/x/y",
"/y/",
"/y/z",
"/0/:id",
"/0/:id/1",
"/1/:id/",
"/1/:id/2",
"/aa",
"/a/",
"/admin",
"/admin/:category",
"/admin/:category/:page",
"/doc",
"/doc/go_faq.html",
"/doc/go1.html",
"/no/a",
"/no/b",
"/api/hello/:name",
];
for route in routes {
let mut recv = panic::catch_unwind(|| {
let mut guard = match tree.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
guard.add_route(route, fake_handler(route));
});
if recv.is_err() {
panic!("panic inserting route '{}': {:?}", route, recv);
}
}
let tsr_routes = vec![
"/hi/",
"/b",
"/search/gopher/",
"/cmd/vet",
"/src",
"/x/",
"/y",
"/0/go/",
"/1/go",
"/a",
"/admin/",
"/admin/config/",
"/admin/config/permissions/",
"/doc/",
];
for route in tsr_routes {
let mut guard = match tree.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
let (handler, _, tsr) = guard.get_value(route);
if handler.is_some() {
panic!("non-nil handler for TSR route '{}'", route);
} else if !tsr {
panic!("expected TSR recommendation for route '{}'", route);
}
}
let no_tsr_routes = vec!["/", "/no", "/no/", "/_", "/_/", "/api/world/abc"];
for route in no_tsr_routes {
let mut guard = match tree.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
let (handler, _, tsr) = guard.get_value(route);
if handler.is_some() {
panic!("non-nil handler for TSR route '{}'", route);
} else if tsr {
panic!("expected TSR recommendation for route '{}'", route);
}
}
}
#[test]
fn test_tree_root_trailing_slash_redirect() {
let tree = Mutex::new(Node::new());
let recv = panic::catch_unwind(|| {
let mut guard = match tree.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
guard.add_route("/:test", fake_handler("/:test"));
});
if recv.is_err() {
panic!("panic inserting test route: {:?}", recv);
}
let guard = match tree.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
let (handler, _, tsr) = guard.get_value("/");
if handler.is_some() {
panic!("non-nil handler");
} else if tsr {
panic!("expected no TSR recommendation");
}
}
#[test]
fn test_tree_find_case_insensitive_path() {
let mut tree = Node::new();
let routes = vec![
"/hi",
"/b/",
"/ABC/",
"/search/:query",
"/cmd/:tool/",
"/src/*filepath",
"/x",
"/x/y",
"/y/",
"/y/z",
"/0/:id",
"/0/:id/1",
"/1/:id/",
"/1/:id/2",
"/aa",
"/a/",
"/doc",
"/doc/go_faq.html",
"/doc/go1.html",
"/doc/go/away",
"/no/a",
"/no/b",
"/Π",
"/u/apfêl/",
"/u/äpfêl/",
"/u/öpfêl",
"/v/Äpfêl/",
"/v/Öpfêl",
"/w/♬", "/w/♭/", "/w/𠜎", "/w/𠜏/", ];
for route in &routes {
tree.add_route(route, fake_handler(route));
}
for route in &routes {
let (out, found) = tree.find_case_insensitive_path(route, false);
if !found {
panic!("Route '{}' not found!", route);
} else if out != *route {
panic!("Wrong result for route '{}': {}", route, out);
}
}
}
}