mirror of https://github.com/rwf2/Rocket.git
Major refactoring. Merged two `Route` structure. Created `URI` and `URIBuf`.
This commit is contained in:
parent
50bc0d6999
commit
5fb12485e8
|
@ -9,16 +9,21 @@ mod error;
|
|||
mod param;
|
||||
mod router;
|
||||
mod rocket;
|
||||
mod route;
|
||||
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
pub mod handler {
|
||||
use super::{Request, Response};
|
||||
|
||||
pub type Handler<'a> = fn(Request) -> Response<'a>;
|
||||
}
|
||||
|
||||
pub use request::Request;
|
||||
pub use method::Method;
|
||||
pub use response::{Response, Responder};
|
||||
pub use error::Error;
|
||||
pub use param::FromParam;
|
||||
pub use router::Router;
|
||||
pub use route::{Route, Handler};
|
||||
pub use router::{Router, Route};
|
||||
pub use rocket::Rocket;
|
||||
pub use handler::Handler;
|
||||
|
|
|
@ -56,17 +56,20 @@ impl Rocket {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn mount(&mut self, base: &'static str, routes: &[&Route]) -> &mut Self {
|
||||
pub fn mount(&mut self, base: &'static str, routes: Vec<Route>) -> &mut Self {
|
||||
println!("🛰 {} '{}':", Magenta.paint("Mounting"), Blue.paint(base));
|
||||
for route in routes {
|
||||
for mut route in routes {
|
||||
let path = format!("{}/{}", base, route.path.as_str());
|
||||
route.set_path(path);
|
||||
|
||||
println!("\t* {}", route);
|
||||
self.router.add_route(route.method, base, route.path, route.handler);
|
||||
self.router.add(route);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn mount_and_launch(mut self, base: &'static str, routes: &[&Route]) {
|
||||
pub fn mount_and_launch(mut self, base: &'static str, routes: Vec<Route>) {
|
||||
self.mount(base, routes);
|
||||
self.launch();
|
||||
}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
use request::Request;
|
||||
use response::Response;
|
||||
use method::Method;
|
||||
|
||||
use std::fmt;
|
||||
use term_painter::Color::*;
|
||||
use term_painter::ToStyle;
|
||||
|
||||
pub type Handler<'a> = fn(Request) -> Response<'a>;
|
||||
|
||||
// TODO: Figure out if using 'static for Handler is a good idea.
|
||||
// TODO: Merge this `Route` and route::Route, somewhow.
|
||||
pub struct Route {
|
||||
pub method: Method,
|
||||
pub path: &'static str,
|
||||
pub handler: Handler<'static>
|
||||
}
|
||||
|
||||
impl Route {
|
||||
pub fn new(method: Method, path: &'static str, handler: Handler<'static>)
|
||||
-> Route {
|
||||
Route {
|
||||
method: method,
|
||||
path: path,
|
||||
handler: handler
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Route {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} {:?}", Green.paint(&self.method), Blue.paint(&self.path))
|
||||
}
|
||||
}
|
||||
|
|
@ -98,21 +98,21 @@ mod tests {
|
|||
}
|
||||
|
||||
fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool {
|
||||
let route_a = Route::new(a.0, "/", a.1, dummy_handler);
|
||||
route_a.collides_with(&Route::new(b.0, "/", b.1, dummy_handler))
|
||||
let route_a = Route::new(a.0, a.1.to_string(), dummy_handler);
|
||||
route_a.collides_with(&Route::new(b.0, b.1.to_string(), dummy_handler))
|
||||
}
|
||||
|
||||
fn collide(a: &'static str, b: &'static str) -> bool {
|
||||
let route_a = Route::new(Get, "/", a, dummy_handler);
|
||||
route_a.collides_with(&Route::new(Get, "/", b, dummy_handler))
|
||||
let route_a = Route::new(Get, a.to_string(), dummy_handler);
|
||||
route_a.collides_with(&Route::new(Get, b.to_string(), dummy_handler))
|
||||
}
|
||||
|
||||
fn s_r_collide(a: &'static str, b: &'static str) -> bool {
|
||||
a.collides_with(&Route::new(Get, "/", b, dummy_handler))
|
||||
a.collides_with(&Route::new(Get, b.to_string(), dummy_handler))
|
||||
}
|
||||
|
||||
fn r_s_collide(a: &'static str, b: &'static str) -> bool {
|
||||
let route_a = Route::new(Get, "/", a, dummy_handler);
|
||||
let route_a = Route::new(Get, a.to_string(), dummy_handler);
|
||||
route_a.collides_with(b)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
mod collider;
|
||||
mod route;
|
||||
mod uri;
|
||||
|
||||
pub use self::collider::Collider;
|
||||
pub use self::uri::{URI, URIBuf};
|
||||
pub use self::route::Route;
|
||||
|
||||
use term_painter::ToStyle;
|
||||
use term_painter::Color::*;
|
||||
use self::route::Route;
|
||||
use std::collections::hash_map::HashMap;
|
||||
use std::path::Path;
|
||||
use method::Method;
|
||||
use route::Handler;
|
||||
|
||||
type Selector = (Method, usize);
|
||||
|
||||
|
@ -24,11 +24,8 @@ impl Router {
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME: Take in Handler.
|
||||
pub fn add_route(&mut self, method: Method, base: &'static str,
|
||||
route: &'static str, handler: Handler<'static>) {
|
||||
let route = Route::new(method, base, route, handler);
|
||||
let selector = (method, route.component_count());
|
||||
pub fn add(&mut self, route: Route) {
|
||||
let selector = (route.method, route.path.segment_count());
|
||||
self.routes.entry(selector).or_insert(vec![]).push(route);
|
||||
}
|
||||
|
||||
|
@ -38,9 +35,9 @@ impl Router {
|
|||
pub fn route<'b>(&'b self, method: Method, uri: &str) -> Option<&'b Route> {
|
||||
let mut matched_route = None;
|
||||
|
||||
let path = Path::new(uri);
|
||||
let num_components = path.components().count();
|
||||
if let Some(routes) = self.routes.get(&(method, num_components)) {
|
||||
let path = URI::new(uri);
|
||||
let num_segments = path.segment_count();
|
||||
if let Some(routes) = self.routes.get(&(method, num_segments)) {
|
||||
for route in routes.iter().filter(|r| r.collides_with(uri)) {
|
||||
println!("\t=> Matched {} to: {}", uri, route);
|
||||
if let None = matched_route {
|
||||
|
@ -73,8 +70,8 @@ impl Router {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Router;
|
||||
use Method::*;
|
||||
use super::{Router, Route};
|
||||
use {Response, Request};
|
||||
|
||||
fn dummy_handler(_req: Request) -> Response<'static> {
|
||||
|
@ -84,7 +81,8 @@ mod test {
|
|||
fn router_with_routes(routes: &[&'static str]) -> Router {
|
||||
let mut router = Router::new();
|
||||
for route in routes {
|
||||
router.add_route(Get, "/", route, dummy_handler);
|
||||
let route = Route::new(Get, route.to_string(), dummy_handler);
|
||||
router.add(route);
|
||||
}
|
||||
|
||||
router
|
||||
|
@ -123,9 +121,9 @@ mod test {
|
|||
assert!(router.route(Get, "/jdlk/asdij").is_some());
|
||||
|
||||
let mut router = Router::new();
|
||||
router.add_route(Put, "/", "/hello", dummy_handler);
|
||||
router.add_route(Post, "/", "/hello", dummy_handler);
|
||||
router.add_route(Delete, "/", "/hello", dummy_handler);
|
||||
router.add(Route::new(Put, "/hello".to_string(), dummy_handler));
|
||||
router.add(Route::new(Post, "/hello".to_string(), dummy_handler));
|
||||
router.add(Route::new(Delete, "/hello".to_string(), dummy_handler));
|
||||
assert!(router.route(Put, "/hello").is_some());
|
||||
assert!(router.route(Post, "/hello").is_some());
|
||||
assert!(router.route(Delete, "/hello").is_some());
|
||||
|
|
|
@ -1,86 +1,47 @@
|
|||
use term_painter::ToStyle;
|
||||
use term_painter::Color::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fmt;
|
||||
use method::Method;
|
||||
use super::Collider; // :D
|
||||
use std::path::Component;
|
||||
use route::Handler;
|
||||
use super::{Collider, URI, URIBuf}; // :D
|
||||
use handler::Handler;
|
||||
|
||||
// TODO: Add ranking to routes. Give static routes higher rank by default.
|
||||
// FIXME: Take in the handler! Or maybe keep that in `Router`?
|
||||
pub struct Route {
|
||||
method: Method,
|
||||
n_components: usize,
|
||||
pub method: Method,
|
||||
pub handler: Handler<'static>,
|
||||
path: PathBuf
|
||||
}
|
||||
|
||||
macro_rules! comp_to_str {
|
||||
($component:expr) => (
|
||||
match $component {
|
||||
&Component::Normal(ref comp) => {
|
||||
if let Some(string) = comp.to_str() { string }
|
||||
else { panic!("Whoops, no string!") }
|
||||
},
|
||||
&Component::RootDir => "/",
|
||||
&c@_ => panic!("Whoops, not normal: {:?}!", c)
|
||||
};
|
||||
)
|
||||
pub path: URIBuf,
|
||||
}
|
||||
|
||||
impl Route {
|
||||
pub fn new(m: Method, mount: &'static str, route: &'static str,
|
||||
handler: Handler<'static>) -> Route {
|
||||
let deduped_path = Route::dedup(mount, route);
|
||||
let path = PathBuf::from(deduped_path);
|
||||
|
||||
pub fn new(m: Method, path: String, handler: Handler<'static>) -> Route {
|
||||
Route {
|
||||
method: m,
|
||||
n_components: path.components().count(),
|
||||
path: URIBuf::new(path),
|
||||
handler: handler,
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn component_count(&self) -> usize {
|
||||
self.n_components
|
||||
pub fn set_path(&mut self, path: String) {
|
||||
self.path = URIBuf::new(path);
|
||||
}
|
||||
|
||||
// FIXME: This is dirty (the comp_to_str and the RootDir thing). Might need
|
||||
// to have my own wrapper arround path strings.
|
||||
// FIXME: Decide whether a component has to be fully variable or not. That
|
||||
// is, whether you can have: /a<a>b/ or even /<a>:<b>/
|
||||
// TODO: Don't return a Vec...take in an &mut [&'a str] (no alloc!)
|
||||
pub fn get_params<'a>(&self, uri: &'a str) -> Vec<&'a str> {
|
||||
let mut result = Vec::with_capacity(self.component_count());
|
||||
let route_components = self.path.components();
|
||||
let uri_components = Path::new(uri).components();
|
||||
let route_components = self.path.segments();
|
||||
let uri_components = URI::new(uri).segments();
|
||||
|
||||
for (route_comp, uri_comp) in route_components.zip(uri_components) {
|
||||
if comp_to_str!(&route_comp).starts_with("<") {
|
||||
result.push(comp_to_str!(&uri_comp));
|
||||
let mut result = Vec::with_capacity(self.path.segment_count());
|
||||
for (route_seg, uri_seg) in route_components.zip(uri_components) {
|
||||
if route_seg.starts_with("<") { // FIXME: Here.
|
||||
result.push(uri_seg);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn dedup(base: &'static str, route: &'static str) -> String {
|
||||
let mut deduped = String::with_capacity(base.len() + route.len() + 1);
|
||||
|
||||
let mut last = '\0';
|
||||
for c in base.chars().chain("/".chars()).chain(route.chars()) {
|
||||
if !(last == '/' && c == '/') {
|
||||
deduped.push(c);
|
||||
}
|
||||
|
||||
last = c;
|
||||
}
|
||||
|
||||
deduped
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Route {
|
||||
|
@ -91,7 +52,8 @@ impl fmt::Display for Route {
|
|||
|
||||
impl Collider for Route {
|
||||
fn collides_with(&self, b: &Route) -> bool {
|
||||
if self.n_components != b.n_components || self.method != b.method {
|
||||
if self.path.segment_count() != b.path.segment_count()
|
||||
|| self.method != b.method {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -101,7 +63,7 @@ impl Collider for Route {
|
|||
|
||||
impl<'a> Collider<Route> for &'a str {
|
||||
fn collides_with(&self, other: &Route) -> bool {
|
||||
let path = Path::new(self);
|
||||
let path = URI::new(self);
|
||||
path.collides_with(&other.path)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,266 @@
|
|||
use std::cell::Cell;
|
||||
use super::Collider;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct URI<'a> {
|
||||
path: &'a str,
|
||||
segment_count: Cell<Option<usize>>
|
||||
}
|
||||
|
||||
impl<'a> URI<'a> {
|
||||
pub fn new<T: AsRef<str> + ?Sized>(path: &'a T) -> URI<'a> {
|
||||
URI {
|
||||
segment_count: Cell::new(None),
|
||||
path: path.as_ref(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn segment_count(&self) -> usize {
|
||||
self.segment_count.get().unwrap_or_else(|| {
|
||||
let count = self.segments().count();
|
||||
self.segment_count.set(Some(count));
|
||||
count
|
||||
})
|
||||
}
|
||||
|
||||
pub fn segments(&self) -> Segments<'a> {
|
||||
Segments(self.path)
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &'a str {
|
||||
self.path
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'a> Sync for URI<'a> { /* It's safe! */ }
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct URIBuf {
|
||||
path: String,
|
||||
segment_count: Cell<Option<usize>>
|
||||
}
|
||||
|
||||
// I don't like repeating all of this stuff. Is there a better way?
|
||||
impl URIBuf {
|
||||
pub fn new(path: String) -> URIBuf {
|
||||
URIBuf {
|
||||
segment_count: Cell::new(None),
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn segment_count(&self) -> usize {
|
||||
self.segment_count.get().unwrap_or_else(|| {
|
||||
println!("Computing!");
|
||||
let count = self.segments().count();
|
||||
self.segment_count.set(Some(count));
|
||||
count
|
||||
})
|
||||
}
|
||||
|
||||
pub fn segments<'a>(&'a self) -> Segments<'a> {
|
||||
Segments(self.path.as_str())
|
||||
}
|
||||
|
||||
pub fn as_uri<'a>(&'a self) -> URI<'a> {
|
||||
let mut uri = URI::new(self.path.as_str());
|
||||
uri.segment_count = self.segment_count.clone();
|
||||
uri
|
||||
}
|
||||
|
||||
pub fn as_str<'a>(&'a self) -> &'a str {
|
||||
self.path.as_str()
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
self.path.clone()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Sync for URIBuf { /* It's safe! */ }
|
||||
|
||||
impl<'a> Collider for URI<'a> {
|
||||
fn collides_with(&self, other: &URI) -> bool {
|
||||
if self.segment_count() != other.segment_count() {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (seg_a, seg_b) in self.segments().zip(other.segments()) {
|
||||
if !seg_a.collides_with(seg_b) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
impl Collider for URIBuf {
|
||||
fn collides_with(&self, other: &URIBuf) -> bool {
|
||||
self.as_uri().collides_with(&other.as_uri())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Collider<URI<'a>> for URIBuf {
|
||||
fn collides_with(&self, other: &URI<'a>) -> bool {
|
||||
self.as_uri().collides_with(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Collider<URIBuf> for URI<'a> {
|
||||
fn collides_with(&self, other: &URIBuf) -> bool {
|
||||
other.as_uri().collides_with(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Segments<'a>(&'a str);
|
||||
|
||||
impl<'a> Iterator for Segments<'a> {
|
||||
type Item = &'a str;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// Find the start of the next segment (first that's not '/').
|
||||
let s = self.0.chars().enumerate().skip_while(|&(_, c)| c == '/').next();
|
||||
if s.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// i is the index of the first character that's not '/'
|
||||
let (i, _) = s.unwrap();
|
||||
|
||||
// Get the index of the first character that _is_ a '/' after start.
|
||||
// j = index of first character after i (hence the i +) that's not a '/'
|
||||
let rest = &self.0[i..];
|
||||
let mut end_iter = rest.chars().enumerate().skip_while(|&(_, c)| c != '/');
|
||||
let j = end_iter.next().map_or(self.0.len(), |(j, _)| i + j);
|
||||
|
||||
// Save the result, update the iterator, and return!
|
||||
let result = Some(&self.0[i..j]);
|
||||
self.0 = &self.0[j..];
|
||||
result
|
||||
}
|
||||
|
||||
// TODO: Potentially take a second parameter with Option<cached count> and
|
||||
// return it here if it's Some. The downside is that a decision has to be
|
||||
// made about -when- to compute and cache that count. A place to do it is in
|
||||
// the segments() method. But this means that the count will always be
|
||||
// computed regardless of whether it's needed. Maybe this is ok. We'll see.
|
||||
// fn count(self) -> usize where Self: Sized {
|
||||
// self.1.unwrap_or_else(self.fold(0, |cnt, _| cnt + 1))
|
||||
// }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{URI, URIBuf};
|
||||
|
||||
fn seg_count(path: &str, expected: usize) -> bool {
|
||||
let actual = URI::new(path).segment_count();
|
||||
let actual_buf = URIBuf::new(path.to_string()).segment_count();
|
||||
if actual != expected || actual_buf != expected {
|
||||
println!("Count mismatch: expected {}, got {}.", expected, actual);
|
||||
println!("{}", if actual != expected { "lifetime" } else { "buf" });
|
||||
println!("Segments (for {}):", path);
|
||||
for (i, segment) in URI::new(path).segments().enumerate() {
|
||||
println!("\t{}: {}", i, segment);
|
||||
}
|
||||
}
|
||||
|
||||
actual == expected && actual_buf == expected
|
||||
}
|
||||
|
||||
fn eq_segments(path: &str, expected: &[&str]) -> bool {
|
||||
let uri = URI::new(path);
|
||||
let actual: Vec<&str> = uri.segments().collect();
|
||||
|
||||
let uri_buf = URIBuf::new(path.to_string());
|
||||
let actual_buf: Vec<&str> = uri_buf.segments().collect();
|
||||
|
||||
actual == expected && actual_buf == expected
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_segment_count() {
|
||||
assert!(seg_count("", 0));
|
||||
assert!(seg_count("/", 0));
|
||||
assert!(seg_count("a", 1));
|
||||
assert!(seg_count("/a", 1));
|
||||
assert!(seg_count("a/", 1));
|
||||
assert!(seg_count("/a/", 1));
|
||||
assert!(seg_count("/a/b", 2));
|
||||
assert!(seg_count("/a/b/", 2));
|
||||
assert!(seg_count("a/b/", 2));
|
||||
assert!(seg_count("ab/", 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn segment_count() {
|
||||
assert!(seg_count("////", 0));
|
||||
assert!(seg_count("//a//", 1));
|
||||
assert!(seg_count("//abc//", 1));
|
||||
assert!(seg_count("//abc/def/", 2));
|
||||
assert!(seg_count("//////abc///def//////////", 2));
|
||||
assert!(seg_count("a/b/c/d/e/f/g", 7));
|
||||
assert!(seg_count("/a/b/c/d/e/f/g", 7));
|
||||
assert!(seg_count("/a/b/c/d/e/f/g/", 7));
|
||||
assert!(seg_count("/a/b/cdjflk/d/e/f/g", 7));
|
||||
assert!(seg_count("//aaflja/b/cdjflk/d/e/f/g", 7));
|
||||
assert!(seg_count("/a /b", 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_segments_match() {
|
||||
assert!(eq_segments("", &[]));
|
||||
assert!(eq_segments("a", &["a"]));
|
||||
assert!(eq_segments("/a", &["a"]));
|
||||
assert!(eq_segments("/a/", &["a"]));
|
||||
assert!(eq_segments("a/", &["a"]));
|
||||
assert!(eq_segments("///a/", &["a"]));
|
||||
assert!(eq_segments("///a///////", &["a"]));
|
||||
assert!(eq_segments("a///////", &["a"]));
|
||||
assert!(eq_segments("//a", &["a"]));
|
||||
assert!(eq_segments("", &[]));
|
||||
assert!(eq_segments("abc", &["abc"]));
|
||||
assert!(eq_segments("/a", &["a"]));
|
||||
assert!(eq_segments("/abc/", &["abc"]));
|
||||
assert!(eq_segments("abc/", &["abc"]));
|
||||
assert!(eq_segments("///abc/", &["abc"]));
|
||||
assert!(eq_segments("///abc///////", &["abc"]));
|
||||
assert!(eq_segments("abc///////", &["abc"]));
|
||||
assert!(eq_segments("//abc", &["abc"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_segments_match() {
|
||||
assert!(eq_segments("a/b/c", &["a", "b", "c"]));
|
||||
assert!(eq_segments("/a/b", &["a", "b"]));
|
||||
assert!(eq_segments("/a///b", &["a", "b"]));
|
||||
assert!(eq_segments("a/b/c/d", &["a", "b", "c", "d"]));
|
||||
assert!(eq_segments("///a///////d////c", &["a", "d", "c"]));
|
||||
assert!(eq_segments("abc/abc", &["abc", "abc"]));
|
||||
assert!(eq_segments("abc/abc/", &["abc", "abc"]));
|
||||
assert!(eq_segments("///abc///////a", &["abc", "a"]));
|
||||
assert!(eq_segments("/////abc/b", &["abc", "b"]));
|
||||
assert!(eq_segments("//abc//c////////d", &["abc", "c", "d"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_segments_match_funky_chars() {
|
||||
assert!(eq_segments("a/b/c!!!", &["a", "b", "c!!!"]));
|
||||
assert!(eq_segments("a /b", &["a ", "b"]));
|
||||
assert!(eq_segments(" a/b", &[" a", "b"]));
|
||||
assert!(eq_segments(" a/b ", &[" a", "b "]));
|
||||
assert!(eq_segments(" a///b ", &[" a", "b "]));
|
||||
assert!(eq_segments(" ab ", &[" ab "]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn segment_mismatch() {
|
||||
assert!(!eq_segments("", &["a"]));
|
||||
assert!(!eq_segments("a", &[]));
|
||||
assert!(!eq_segments("/a/a", &["a"]));
|
||||
assert!(!eq_segments("/a/b", &["b", "a"]));
|
||||
assert!(!eq_segments("/a/a/b", &["a", "b"]));
|
||||
assert!(!eq_segments("///a/", &[]));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue