diff --git a/Cargo.toml b/Cargo.toml index c1becce6..764a8e1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "ntex", + "ntex-router", "ntex-service", "ntex-web-macros", @@ -11,6 +12,7 @@ members = [ [patch.crates-io] ntex = { path = "ntex" } +ntex-router = { path = "ntex-router" } ntex-service = { path = "ntex-service" } actix-server = { path = "actix-net/actix-server" } diff --git a/ntex-router/CHANGES.txt b/ntex-router/CHANGES.txt new file mode 100644 index 00000000..6707f9fa --- /dev/null +++ b/ntex-router/CHANGES.txt @@ -0,0 +1,57 @@ +# Changes + +## [0.3.0] - 2020-03-22 + +* Use prefix tree for underling data representation + +## [0.2.4] - 2019-12-31 + +* Add `ResourceDef::resource_path_named()` path generation method + +## [0.2.3] - 2019-12-25 + +* Add impl `IntoPattern` for `&String` + +## [0.2.2] - 2019-12-25 + +* Use `IntoPattern` for `RouterBuilder::path()` + +## [0.2.1] - 2019-12-25 + +* Add `IntoPattern` trait + +* Add multi-pattern resources + +## [0.2.0] - 2019-12-07 + +* Update http to 0.2 + +* Update regex to 1.3 + +* Use bytestring instead of string + +## [0.1.5] - 2019-05-15 + +* Remove debug prints + +## [0.1.4] - 2019-05-15 + +* Fix checked resource match + +## [0.1.3] - 2019-04-22 + +* Added support for `remainder match` (i.e "/path/{tail}*") + +## [0.1.2] - 2019-04-07 + +* Export `Quoter` type + +* Allow to reset `Path` instance + +## [0.1.1] - 2019-04-03 + +* Get dynamic segment by name instead of iterator. + +## [0.1.0] - 2019-03-09 + +* Initial release diff --git a/ntex-router/Cargo.toml b/ntex-router/Cargo.toml new file mode 100644 index 00000000..61a4fffa --- /dev/null +++ b/ntex-router/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "ntex-router" +version = "0.3.0" +authors = ["Nikolay Kim "] +description = "Path router" +keywords = ["ntex"] +repository = "https://github.com/fafhrd91/ntex.git" +documentation = "https://docs.rs/ntex-router/" +license = "MIT" +edition = "2018" + +[lib] +name = "ntex_router" +path = "src/lib.rs" + +[features] +default = ["http"] + +[dependencies] +regex = "1.3.5" +serde = "1.0.105" +bytestring = "0.1.2" +log = "0.4.8" +http = { version = "0.2.0", optional = true } + +[dev-dependencies] +http = "0.2.0" +serde_derive = "1.0" diff --git a/ntex-router/src/de.rs b/ntex-router/src/de.rs new file mode 100644 index 00000000..e22cf539 --- /dev/null +++ b/ntex-router/src/de.rs @@ -0,0 +1,714 @@ +use serde::de::{self, Deserializer, Error as DeError, Visitor}; +use serde::forward_to_deserialize_any; + +use crate::path::{Path, PathIter}; +use crate::ResourcePath; + +macro_rules! unsupported_type { + ($trait_fn:ident, $name:expr) => { + fn $trait_fn(self, _: V) -> Result + where V: Visitor<'de> + { + Err(de::value::Error::custom(concat!("unsupported type: ", $name))) + } + }; +} + +macro_rules! parse_single_value { + ($trait_fn:ident, $visit_fn:ident, $tp:tt) => { + fn $trait_fn(self, visitor: V) -> Result + where V: Visitor<'de> + { + if self.path.len() != 1 { + Err(de::value::Error::custom( + format!("wrong number of parameters: {} expected 1", + self.path.len()).as_str())) + } else { + let v = self.path[0].parse().map_err( + |_| de::value::Error::custom( + format!("can not parse {:?} to a {}", &self.path[0], $tp)))?; + visitor.$visit_fn(v) + } + } + } +} + +pub struct PathDeserializer<'de, T: ResourcePath + 'de> { + path: &'de Path, +} + +impl<'de, T: ResourcePath + 'de> PathDeserializer<'de, T> { + pub fn new(path: &'de Path) -> Self { + PathDeserializer { path } + } +} + +impl<'de, T: ResourcePath + 'de> Deserializer<'de> for PathDeserializer<'de, T> { + type Error = de::value::Error; + + fn deserialize_map(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_map(ParamsDeserializer { + params: self.path.iter(), + current: None, + }) + } + + fn deserialize_struct( + self, + _: &'static str, + _: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_map(visitor) + } + + fn deserialize_unit(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_unit() + } + + fn deserialize_unit_struct( + self, + _: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_unit(visitor) + } + + fn deserialize_newtype_struct( + self, + _: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + fn deserialize_tuple( + self, + len: usize, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + if self.path.len() < len { + Err(de::value::Error::custom( + format!( + "wrong number of parameters: {} expected {}", + self.path.len(), + len + ) + .as_str(), + )) + } else { + visitor.visit_seq(ParamsSeq { + params: self.path.iter(), + }) + } + } + + fn deserialize_tuple_struct( + self, + _: &'static str, + len: usize, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + if self.path.len() < len { + Err(de::value::Error::custom( + format!( + "wrong number of parameters: {} expected {}", + self.path.len(), + len + ) + .as_str(), + )) + } else { + visitor.visit_seq(ParamsSeq { + params: self.path.iter(), + }) + } + } + + fn deserialize_enum( + self, + _: &'static str, + _: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + if self.path.is_empty() { + Err(de::value::Error::custom( + "expeceted at least one parameters", + )) + } else { + visitor.visit_enum(ValueEnum { + value: &self.path[0], + }) + } + } + + fn deserialize_str(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.path.len() != 1 { + Err(de::value::Error::custom( + format!("wrong number of parameters: {} expected 1", self.path.len()) + .as_str(), + )) + } else { + visitor.visit_str(&self.path[0]) + } + } + + fn deserialize_seq(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_seq(ParamsSeq { + params: self.path.iter(), + }) + } + + unsupported_type!(deserialize_any, "'any'"); + unsupported_type!(deserialize_bytes, "bytes"); + unsupported_type!(deserialize_option, "Option"); + unsupported_type!(deserialize_identifier, "identifier"); + unsupported_type!(deserialize_ignored_any, "ignored_any"); + + parse_single_value!(deserialize_bool, visit_bool, "bool"); + parse_single_value!(deserialize_i8, visit_i8, "i8"); + parse_single_value!(deserialize_i16, visit_i16, "i16"); + parse_single_value!(deserialize_i32, visit_i32, "i32"); + parse_single_value!(deserialize_i64, visit_i64, "i64"); + parse_single_value!(deserialize_u8, visit_u8, "u8"); + parse_single_value!(deserialize_u16, visit_u16, "u16"); + parse_single_value!(deserialize_u32, visit_u32, "u32"); + parse_single_value!(deserialize_u64, visit_u64, "u64"); + parse_single_value!(deserialize_f32, visit_f32, "f32"); + parse_single_value!(deserialize_f64, visit_f64, "f64"); + parse_single_value!(deserialize_string, visit_string, "String"); + parse_single_value!(deserialize_byte_buf, visit_string, "String"); + parse_single_value!(deserialize_char, visit_char, "char"); +} + +struct ParamsDeserializer<'de, T: ResourcePath> { + params: PathIter<'de, T>, + current: Option<(&'de str, &'de str)>, +} + +impl<'de, T: ResourcePath> de::MapAccess<'de> for ParamsDeserializer<'de, T> { + type Error = de::value::Error; + + fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> + where + K: de::DeserializeSeed<'de>, + { + self.current = self.params.next().map(|ref item| (item.0, item.1)); + match self.current { + Some((key, _)) => Ok(Some(seed.deserialize(Key { key })?)), + None => Ok(None), + } + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: de::DeserializeSeed<'de>, + { + if let Some((_, value)) = self.current.take() { + seed.deserialize(Value { value }) + } else { + Err(de::value::Error::custom("unexpected item")) + } + } +} + +struct Key<'de> { + key: &'de str, +} + +impl<'de> Deserializer<'de> for Key<'de> { + type Error = de::value::Error; + + fn deserialize_identifier(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_str(self.key) + } + + fn deserialize_any(self, _visitor: V) -> Result + where + V: Visitor<'de>, + { + Err(de::value::Error::custom("Unexpected")) + } + + forward_to_deserialize_any! { + bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes + byte_buf option unit unit_struct newtype_struct seq tuple + tuple_struct map struct enum ignored_any + } +} + +macro_rules! parse_value { + ($trait_fn:ident, $visit_fn:ident, $tp:tt) => { + fn $trait_fn(self, visitor: V) -> Result + where V: Visitor<'de> + { + let v = self.value.parse().map_err( + |_| de::value::Error::custom( + format!("can not parse {:?} to a {}", self.value, $tp)))?; + visitor.$visit_fn(v) + } + } +} + +struct Value<'de> { + value: &'de str, +} + +impl<'de> Deserializer<'de> for Value<'de> { + type Error = de::value::Error; + + parse_value!(deserialize_bool, visit_bool, "bool"); + parse_value!(deserialize_i8, visit_i8, "i8"); + parse_value!(deserialize_i16, visit_i16, "i16"); + parse_value!(deserialize_i32, visit_i32, "i16"); + parse_value!(deserialize_i64, visit_i64, "i64"); + parse_value!(deserialize_u8, visit_u8, "u8"); + parse_value!(deserialize_u16, visit_u16, "u16"); + parse_value!(deserialize_u32, visit_u32, "u32"); + parse_value!(deserialize_u64, visit_u64, "u64"); + parse_value!(deserialize_f32, visit_f32, "f32"); + parse_value!(deserialize_f64, visit_f64, "f64"); + parse_value!(deserialize_string, visit_string, "String"); + parse_value!(deserialize_byte_buf, visit_string, "String"); + parse_value!(deserialize_char, visit_char, "char"); + + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_unit() + } + + fn deserialize_unit(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_unit() + } + + fn deserialize_unit_struct( + self, + _: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + visitor.visit_unit() + } + + fn deserialize_bytes(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_borrowed_bytes(self.value.as_bytes()) + } + + fn deserialize_str(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_borrowed_str(self.value) + } + + fn deserialize_option(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_some(self) + } + + fn deserialize_enum( + self, + _: &'static str, + _: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + visitor.visit_enum(ValueEnum { value: self.value }) + } + + fn deserialize_newtype_struct( + self, + _: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + fn deserialize_tuple(self, _: usize, _: V) -> Result + where + V: Visitor<'de>, + { + Err(de::value::Error::custom("unsupported type: tuple")) + } + + fn deserialize_struct( + self, + _: &'static str, + _: &'static [&'static str], + _: V, + ) -> Result + where + V: Visitor<'de>, + { + Err(de::value::Error::custom("unsupported type: struct")) + } + + fn deserialize_tuple_struct( + self, + _: &'static str, + _: usize, + _: V, + ) -> Result + where + V: Visitor<'de>, + { + Err(de::value::Error::custom("unsupported type: tuple struct")) + } + + unsupported_type!(deserialize_any, "any"); + unsupported_type!(deserialize_seq, "seq"); + unsupported_type!(deserialize_map, "map"); + unsupported_type!(deserialize_identifier, "identifier"); +} + +struct ParamsSeq<'de, T: ResourcePath> { + params: PathIter<'de, T>, +} + +impl<'de, T: ResourcePath> de::SeqAccess<'de> for ParamsSeq<'de, T> { + type Error = de::value::Error; + + fn next_element_seed(&mut self, seed: U) -> Result, Self::Error> + where + U: de::DeserializeSeed<'de>, + { + match self.params.next() { + Some(item) => Ok(Some(seed.deserialize(Value { value: item.1 })?)), + None => Ok(None), + } + } +} + +struct ValueEnum<'de> { + value: &'de str, +} + +impl<'de> de::EnumAccess<'de> for ValueEnum<'de> { + type Error = de::value::Error; + type Variant = UnitVariant; + + fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> + where + V: de::DeserializeSeed<'de>, + { + Ok((seed.deserialize(Key { key: self.value })?, UnitVariant)) + } +} + +struct UnitVariant; + +impl<'de> de::VariantAccess<'de> for UnitVariant { + type Error = de::value::Error; + + fn unit_variant(self) -> Result<(), Self::Error> { + Ok(()) + } + + fn newtype_variant_seed(self, _seed: T) -> Result + where + T: de::DeserializeSeed<'de>, + { + Err(de::value::Error::custom("not supported")) + } + + fn tuple_variant(self, _len: usize, _visitor: V) -> Result + where + V: Visitor<'de>, + { + Err(de::value::Error::custom("not supported")) + } + + fn struct_variant( + self, + _: &'static [&'static str], + _: V, + ) -> Result + where + V: Visitor<'de>, + { + Err(de::value::Error::custom("not supported")) + } +} + +// #[cfg(test)] +// mod tests { +// use serde::de; +// use serde_derive::Deserialize; + +// use super::*; +// use crate::path::Path; +// use crate::router::Router; + +// #[derive(Deserialize)] +// struct MyStruct { +// key: String, +// value: String, +// } + +// #[derive(Deserialize)] +// struct Id { +// _id: String, +// } + +// #[derive(Debug, Deserialize)] +// struct Test1(String, u32); + +// #[derive(Debug, Deserialize)] +// struct Test2 { +// key: String, +// value: u32, +// } + +// #[derive(Debug, Deserialize, PartialEq)] +// #[serde(rename_all = "lowercase")] +// enum TestEnum { +// Val1, +// Val2, +// } + +// #[derive(Debug, Deserialize)] +// struct Test3 { +// val: TestEnum, +// } + +// #[test] +// fn test_request_extract() { +// let mut router = Router::<()>::build(); +// router.path("/{key}/{value}/", ()); +// let router = router.finish(); + +// let mut path = Path::new("/name/user1/"); +// assert!(router.recognize(&mut path).is_some()); + +// let s: MyStruct = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); +// assert_eq!(s.key, "name"); +// assert_eq!(s.value, "user1"); + +// let s: (String, String) = +// de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); +// assert_eq!(s.0, "name"); +// assert_eq!(s.1, "user1"); + +// let mut router = Router::<()>::build(); +// router.path("/{key}/{value}/", ()); +// let router = router.finish(); + +// let mut path = Path::new("/name/32/"); +// assert!(router.recognize(&mut path).is_some()); + +// let s: Test1 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); +// assert_eq!(s.0, "name"); +// assert_eq!(s.1, 32); + +// let s: Test2 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); +// assert_eq!(s.key, "name"); +// assert_eq!(s.value, 32); + +// let s: (String, u8) = +// de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); +// assert_eq!(s.0, "name"); +// assert_eq!(s.1, 32); + +// let res: Vec = +// de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); +// assert_eq!(res[0], "name".to_owned()); +// assert_eq!(res[1], "32".to_owned()); +// } + +// #[test] +// fn test_extract_path_single() { +// let mut router = Router::<()>::build(); +// router.path("/{value}/", ()); +// let router = router.finish(); + +// let mut path = Path::new("/32/"); +// assert!(router.recognize(&mut path).is_some()); +// let i: i8 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); +// assert_eq!(i, 32); +// } + +// #[test] +// fn test_extract_enum() { +// let mut router = Router::<()>::build(); +// router.path("/{val}/", ()); +// let router = router.finish(); + +// let mut path = Path::new("/val1/"); +// assert!(router.recognize(&mut path).is_some()); +// let i: TestEnum = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); +// assert_eq!(i, TestEnum::Val1); + +// let mut router = Router::<()>::build(); +// router.path("/{val1}/{val2}/", ()); +// let router = router.finish(); + +// let mut path = Path::new("/val1/val2/"); +// assert!(router.recognize(&mut path).is_some()); +// let i: (TestEnum, TestEnum) = +// de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); +// assert_eq!(i, (TestEnum::Val1, TestEnum::Val2)); +// } + +// #[test] +// fn test_extract_enum_value() { +// let mut router = Router::<()>::build(); +// router.path("/{val}/", ()); +// let router = router.finish(); + +// let mut path = Path::new("/val1/"); +// assert!(router.recognize(&mut path).is_some()); +// let i: Test3 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap(); +// assert_eq!(i.val, TestEnum::Val1); + +// let mut path = Path::new("/val3/"); +// assert!(router.recognize(&mut path).is_some()); +// let i: Result = +// de::Deserialize::deserialize(PathDeserializer::new(&path)); +// assert!(i.is_err()); +// assert!(format!("{:?}", i).contains("unknown variant")); +// } + +// #[test] +// fn test_extract_errors() { +// let mut router = Router::<()>::build(); +// router.path("/{value}/", ()); +// let router = router.finish(); + +// let mut path = Path::new("/name/"); +// assert!(router.recognize(&mut path).is_some()); + +// let s: Result = +// de::Deserialize::deserialize(PathDeserializer::new(&path)); +// assert!(s.is_err()); +// assert!(format!("{:?}", s).contains("wrong number of parameters")); + +// let s: Result = +// de::Deserialize::deserialize(PathDeserializer::new(&path)); +// assert!(s.is_err()); +// assert!(format!("{:?}", s).contains("can not parse")); + +// let s: Result<(String, String), de::value::Error> = +// de::Deserialize::deserialize(PathDeserializer::new(&path)); +// assert!(s.is_err()); +// assert!(format!("{:?}", s).contains("wrong number of parameters")); + +// let s: Result = +// de::Deserialize::deserialize(PathDeserializer::new(&path)); +// assert!(s.is_err()); +// assert!(format!("{:?}", s).contains("can not parse")); +// } + +// #[test] +// fn test_extract_path_decode() { +// let mut router = Router::<()>::default(); +// router.register_resource(Resource::new(ResourceDef::new("/{value}/"))); + +// macro_rules! test_single_value { +// ($value:expr, $expected:expr) => {{ +// let req = TestRequest::with_uri($value).finish(); +// let info = router.recognize(&req, &(), 0); +// let req = req.with_route_info(info); +// assert_eq!( +// *Path::::from_request(&req, &PathConfig::default()).unwrap(), +// $expected +// ); +// }}; +// } + +// test_single_value!("/%25/", "%"); +// test_single_value!("/%40%C2%A3%24%25%5E%26%2B%3D/", "@£$%^&+="); +// test_single_value!("/%2B/", "+"); +// test_single_value!("/%252B/", "%2B"); +// test_single_value!("/%2F/", "/"); +// test_single_value!("/%252F/", "%2F"); +// test_single_value!( +// "/http%3A%2F%2Flocalhost%3A80%2Ffoo/", +// "http://localhost:80/foo" +// ); +// test_single_value!("/%2Fvar%2Flog%2Fsyslog/", "/var/log/syslog"); +// test_single_value!( +// "/http%3A%2F%2Flocalhost%3A80%2Ffile%2F%252Fvar%252Flog%252Fsyslog/", +// "http://localhost:80/file/%2Fvar%2Flog%2Fsyslog" +// ); + +// let req = TestRequest::with_uri("/%25/7/?id=test").finish(); + +// let mut router = Router::<()>::default(); +// router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); +// let info = router.recognize(&req, &(), 0); +// let req = req.with_route_info(info); + +// let s = Path::::from_request(&req, &PathConfig::default()).unwrap(); +// assert_eq!(s.key, "%"); +// assert_eq!(s.value, 7); + +// let s = Path::<(String, String)>::from_request(&req, &PathConfig::default()).unwrap(); +// assert_eq!(s.0, "%"); +// assert_eq!(s.1, "7"); +// } + +// #[test] +// fn test_extract_path_no_decode() { +// let mut router = Router::<()>::default(); +// router.register_resource(Resource::new(ResourceDef::new("/{value}/"))); + +// let req = TestRequest::with_uri("/%25/").finish(); +// let info = router.recognize(&req, &(), 0); +// let req = req.with_route_info(info); +// assert_eq!( +// *Path::::from_request(&req, &&PathConfig::default().disable_decoding()) +// .unwrap(), +// "%25" +// ); +// } +// } diff --git a/ntex-router/src/lib.rs b/ntex-router/src/lib.rs new file mode 100644 index 00000000..94f215ee --- /dev/null +++ b/ntex-router/src/lib.rs @@ -0,0 +1,161 @@ +#![allow(clippy::cognitive_complexity)] + +//! Resource path matching library. +mod de; +mod path; +mod resource; +mod router; +mod tree; + +pub use self::de::PathDeserializer; +pub use self::path::Path; +pub use self::resource::ResourceDef; +pub use self::router::{ResourceInfo, Router, RouterBuilder}; + +pub trait Resource { + fn path(&self) -> &str; + + fn resource_path(&mut self) -> &mut Path; +} + +pub trait ResourcePath { + fn path(&self) -> &str; + + fn unquote<'a>(s: &'a str) -> std::borrow::Cow<'a, str> { + s.into() + } +} + +impl ResourcePath for String { + fn path(&self) -> &str { + self.as_str() + } +} + +impl<'a> ResourcePath for &'a str { + fn path(&self) -> &str { + self + } +} + +impl ResourcePath for bytestring::ByteString { + fn path(&self) -> &str { + &*self + } +} + +/// Helper trait for type that could be converted to path pattern +pub trait IntoPattern { + /// Signle patter + fn is_single(&self) -> bool; + + fn patterns(&self) -> Vec; +} + +impl IntoPattern for String { + fn is_single(&self) -> bool { + true + } + + fn patterns(&self) -> Vec { + vec![self.clone()] + } +} + +impl<'a> IntoPattern for &'a String { + fn is_single(&self) -> bool { + true + } + + fn patterns(&self) -> Vec { + vec![self.as_str().to_string()] + } +} + +impl<'a> IntoPattern for &'a str { + fn is_single(&self) -> bool { + true + } + + fn patterns(&self) -> Vec { + vec![(*self).to_string()] + } +} + +impl> IntoPattern for Vec { + fn is_single(&self) -> bool { + self.len() == 1 + } + + fn patterns(&self) -> Vec { + self.into_iter().map(|v| v.as_ref().to_string()).collect() + } +} + +macro_rules! array_patterns (($tp:ty, $num:tt) => { + impl IntoPattern for [$tp; $num] { + fn is_single(&self) -> bool { + $num == 1 + } + + fn patterns(&self) -> Vec { + self.iter().map(|v| v.to_string()).collect() + } + } +}); + +array_patterns!(&str, 1); +array_patterns!(&str, 2); +array_patterns!(&str, 3); +array_patterns!(&str, 4); +array_patterns!(&str, 5); +array_patterns!(&str, 6); +array_patterns!(&str, 7); +array_patterns!(&str, 8); +array_patterns!(&str, 9); +array_patterns!(&str, 10); +array_patterns!(&str, 11); +array_patterns!(&str, 12); +array_patterns!(&str, 13); +array_patterns!(&str, 14); +array_patterns!(&str, 15); +array_patterns!(&str, 16); + +array_patterns!(String, 1); +array_patterns!(String, 2); +array_patterns!(String, 3); +array_patterns!(String, 4); +array_patterns!(String, 5); +array_patterns!(String, 6); +array_patterns!(String, 7); +array_patterns!(String, 8); +array_patterns!(String, 9); +array_patterns!(String, 10); +array_patterns!(String, 11); +array_patterns!(String, 12); +array_patterns!(String, 13); +array_patterns!(String, 14); +array_patterns!(String, 15); +array_patterns!(String, 16); + +mod quoter; + +#[cfg(feature = "http")] +mod http_support { + use super::ResourcePath; + use http::Uri; + + impl ResourcePath for Uri { + fn path(&self) -> &str { + self.path() + } + + fn unquote<'a>(s: &'a str) -> std::borrow::Cow<'a, str> { + if let Some(q) = super::quoter::requote(s.as_bytes()) { + std::borrow::Cow::Owned(q) + } else { + std::borrow::Cow::Borrowed(s) + } + } + } +} diff --git a/ntex-router/src/path.rs b/ntex-router/src/path.rs new file mode 100644 index 00000000..205c375a --- /dev/null +++ b/ntex-router/src/path.rs @@ -0,0 +1,221 @@ +use std::ops::Index; + +use serde::de; + +use crate::de::PathDeserializer; +use crate::{Resource, ResourcePath}; + +#[derive(Debug, Clone)] +pub(super) enum PathItem { + Static(&'static str), + Segment(String), +} + +/// Resource path match information +/// +/// If resource path contains variable patterns, `Path` stores them. +#[derive(Debug)] +pub struct Path { + path: T, + pub(super) skip: u16, + pub(super) segments: Vec<(&'static str, PathItem)>, +} + +impl Default for Path { + fn default() -> Self { + Path { + path: T::default(), + skip: 0, + segments: Vec::new(), + } + } +} + +impl Clone for Path { + fn clone(&self) -> Self { + Path { + path: self.path.clone(), + skip: self.skip, + segments: self.segments.clone(), + } + } +} + +impl Path { + pub fn new(path: T) -> Path { + Path { + path, + skip: 0, + segments: Vec::new(), + } + } + + #[inline] + /// Get reference to inner path instance + pub fn get_ref(&self) -> &T { + &self.path + } + + #[inline] + /// Get mutable reference to inner path instance + pub fn get_mut(&mut self) -> &mut T { + &mut self.path + } + + #[inline] + /// Path + pub fn path(&self) -> &str { + let skip = self.skip as usize; + let path = self.path.path(); + if skip <= path.len() { + &path[skip..] + } else { + "" + } + } + + #[inline] + /// Set new path + pub fn set(&mut self, path: T) { + self.skip = 0; + self.path = path; + self.segments.clear(); + } + + #[inline] + /// Reset state + pub fn reset(&mut self) { + self.skip = 0; + self.segments.clear(); + } + + #[inline] + /// Skip first `n` chars in path + pub fn skip(&mut self, n: u16) { + self.skip += n; + } + + pub(crate) fn add(&mut self, name: &'static str, value: String) { + self.segments.push((name, PathItem::Segment(value))) + } + + #[doc(hidden)] + pub fn add_static(&mut self, name: &'static str, value: &'static str) { + self.segments.push((name, PathItem::Static(value))); + } + + #[inline] + /// Check if there are any matched patterns + pub fn is_empty(&self) -> bool { + self.segments.is_empty() + } + + #[inline] + /// Check number of extracted parameters + pub fn len(&self) -> usize { + self.segments.len() + } + + /// Get matched parameter by name without type conversion + pub fn get(&self, key: &str) -> Option<&str> { + for item in self.segments.iter() { + if key == item.0 { + return match item.1 { + PathItem::Static(ref s) => Some(&s), + PathItem::Segment(ref s) => Some(s), + }; + } + } + if key == "tail" { + Some(&self.path.path()[(self.skip as usize)..]) + } else { + None + } + } + + /// Get unprocessed part of the path + pub fn unprocessed(&self) -> &str { + &self.path.path()[(self.skip as usize)..] + } + + /// Get matched parameter by name. + /// + /// If keyed parameter is not available empty string is used as default + /// value. + pub fn query(&self, key: &str) -> &str { + if let Some(s) = self.get(key) { + s + } else { + "" + } + } + + /// Return iterator to items in parameter container + pub fn iter(&self) -> PathIter { + PathIter { + idx: 0, + params: self, + } + } + + /// Try to deserialize matching parameters to a specified type `U` + pub fn load<'de, U: serde::Deserialize<'de>>( + &'de self, + ) -> Result { + de::Deserialize::deserialize(PathDeserializer::new(self)) + } +} + +#[derive(Debug)] +pub struct PathIter<'a, T> { + idx: usize, + params: &'a Path, +} + +impl<'a, T: ResourcePath> Iterator for PathIter<'a, T> { + type Item = (&'a str, &'a str); + + #[inline] + fn next(&mut self) -> Option<(&'a str, &'a str)> { + if self.idx < self.params.len() { + let idx = self.idx; + let res = match self.params.segments[idx].1 { + PathItem::Static(s) => s, + PathItem::Segment(ref s) => s.as_str(), + }; + self.idx += 1; + return Some((&self.params.segments[idx].0, res)); + } + None + } +} + +impl<'a, T: ResourcePath> Index<&'a str> for Path { + type Output = str; + + fn index(&self, name: &'a str) -> &str { + self.get(name) + .expect("Value for parameter is not available") + } +} + +impl Index for Path { + type Output = str; + + fn index(&self, idx: usize) -> &str { + match self.segments[idx].1 { + PathItem::Static(ref s) => &s, + PathItem::Segment(ref s) => &s, + } + } +} + +impl Resource for Path { + fn path(&self) -> &str { + self.path() + } + + fn resource_path(&mut self) -> &mut Path { + self + } +} diff --git a/ntex-router/src/quoter.rs b/ntex-router/src/quoter.rs new file mode 100644 index 00000000..390df17d --- /dev/null +++ b/ntex-router/src/quoter.rs @@ -0,0 +1,62 @@ +pub fn requote(val: &[u8]) -> Option { + let mut has_pct = 0; + let mut pct = [b'%', 0, 0]; + let mut idx = 0; + let mut cloned: Option> = None; + + let len = val.len(); + while idx < len { + let ch = val[idx]; + + if has_pct != 0 { + pct[has_pct] = val[idx]; + has_pct += 1; + if has_pct == 3 { + has_pct = 0; + let buf = cloned.as_mut().unwrap(); + + if let Some(ch) = restore_ch(pct[1], pct[2]) { + buf.push(ch); + } else { + buf.extend_from_slice(&pct[..]); + } + } + } else if ch == b'%' { + has_pct = 1; + if cloned.is_none() { + let mut c = Vec::with_capacity(len); + c.extend_from_slice(&val[..idx]); + cloned = Some(c); + } + } else if let Some(ref mut cloned) = cloned { + cloned.push(ch) + } + idx += 1; + } + + if let Some(data) = cloned { + // Unsafe: we get data from http::Uri, which does utf-8 checks already + // this code only decodes valid pct encoded values + Some(unsafe { String::from_utf8_unchecked(data) }) + } else { + None + } +} + +#[inline] +fn from_hex(v: u8) -> Option { + if v >= b'0' && v <= b'9' { + Some(v - 0x30) // ord('0') == 0x30 + } else if v >= b'A' && v <= b'F' { + Some(v - 0x41 + 10) // ord('A') == 0x41 + } else if v > b'a' && v <= b'f' { + Some(v - 0x61 + 10) // ord('a') == 0x61 + } else { + None + } +} + +#[inline] +fn restore_ch(d1: u8, d2: u8) -> Option { + from_hex(d1).and_then(|d1| from_hex(d2).map(move |d2| d1 << 4 | d2)) +} diff --git a/ntex-router/src/resource.rs b/ntex-router/src/resource.rs new file mode 100644 index 00000000..45b5f3f7 --- /dev/null +++ b/ntex-router/src/resource.rs @@ -0,0 +1,824 @@ +use std::collections::HashMap; +use std::hash::{Hash, Hasher}; + +use regex::{escape, Regex}; + +use super::IntoPattern; + +#[derive(Clone, Debug)] +pub(super) struct Segments { + pub(super) tp: Vec, + pub(super) slesh: bool, +} + +/// ResourceDef describes an entry in resources table +/// +/// Resource definition can contain only 16 dynamic segments +#[derive(Clone, Debug)] +pub struct ResourceDef { + id: u16, + pub(super) tp: Vec, // set of matching paths + name: String, + pattern: String, + elements: Vec, + pub(super) prefix: bool, +} + +#[derive(Debug, Clone, PartialEq)] +enum PathElement { + Str(String), + Var(String), +} + +impl PathElement { + fn is_str(&self) -> bool { + match self { + PathElement::Str(_) => true, + _ => false, + } + } + fn into_str(self) -> String { + match self { + PathElement::Str(s) => s, + _ => panic!(), + } + } + fn as_str(&self) -> &str { + match self { + PathElement::Str(s) => s.as_str(), + PathElement::Var(s) => s.as_str(), + } + } +} + +#[derive(Clone, Debug)] +pub(crate) enum Segment { + Static(String), + Dynamic { + pattern: Regex, + names: Vec<&'static str>, + len: usize, + tail: bool, + }, +} + +impl Eq for Segment {} + +impl PartialEq for Segment { + fn eq(&self, other: &Self) -> bool { + match self { + Segment::Static(ref p1) => match other { + Segment::Static(p2) => p1 == p2, + _ => false, + }, + Segment::Dynamic { + pattern: ref p1, + tail: t1, + .. + } => match other { + Segment::Static { .. } => false, + Segment::Dynamic { + pattern: ref p2, + tail: t2, + .. + } => p1.as_str() == p2.as_str() && t1 == t2, + }, + } + } +} + +impl ResourceDef { + /// Parse path pattern and create new `ResourceDef` instance. + /// + /// Path segments are separatted by `/`. Pattern must start + /// with segment separator. Static segments could be + /// case insensitive. + /// + /// Panics if path pattern is malformed. + pub fn new(path: T) -> Self { + if path.is_single() { + let patterns = path.patterns(); + ResourceDef::with_prefix(&patterns[0], false) + } else { + let set = path.patterns(); + let mut tp = Vec::new(); + let mut elements = Vec::new(); + + for path in set { + let (pelems, elems) = ResourceDef::parse(&path); + tp.push(pelems); + elements = elems; + } + + ResourceDef { + tp, + elements, + id: 0, + name: String::new(), + pattern: "".to_owned(), + prefix: false, + } + } + } + + /// Parse path pattern and create new `ResourceDef` instance. + /// + /// Use `prefix` type instead of `static`. + /// + /// Panics if path regex pattern is malformed. + pub fn prefix(path: &str) -> Self { + ResourceDef::with_prefix(path, true) + } + + /// Parse path pattern and create new `ResourceDef` instance. + /// Inserts `/` to the start of the pattern. + /// + /// Panics if path regex pattern is malformed. + pub fn root_prefix(path: &str) -> Self { + ResourceDef::with_prefix(&insert_slash(path), true) + } + + /// Resource id + pub fn id(&self) -> u16 { + self.id + } + + /// Set resource id + pub fn set_id(&mut self, id: u16) { + self.id = id; + } + + /// Parse path pattern and create new `Pattern` instance with custom prefix + fn with_prefix(path: &str, for_prefix: bool) -> Self { + let path = path.to_owned(); + let (tp, elements) = ResourceDef::parse(&path); + + ResourceDef { + elements, + id: 0, + tp: vec![tp], + name: String::new(), + pattern: path, + prefix: for_prefix, + } + } + + /// Resource pattern name + pub fn name(&self) -> &str { + &self.name + } + + /// Mutable reference to a name of a resource definition. + pub fn name_mut(&mut self) -> &mut String { + &mut self.name + } + + /// Path pattern of the resource + pub fn pattern(&self) -> &str { + &self.pattern + } + + /// Build resource path from elements. Returns `true` on success. + pub fn resource_path(&self, path: &mut String, elements: &mut U) -> bool + where + U: Iterator, + I: AsRef, + { + for el in &self.elements { + match *el { + PathElement::Str(ref s) => path.push_str(s), + PathElement::Var(_) => { + if let Some(val) = elements.next() { + path.push_str(val.as_ref()) + } else { + return false; + } + } + } + } + true + } + + /// Build resource path from elements. Returns `true` on success. + pub fn resource_path_named( + &self, + path: &mut String, + elements: &HashMap, + ) -> bool + where + K: std::borrow::Borrow + Eq + Hash, + V: AsRef, + S: std::hash::BuildHasher, + { + for el in &self.elements { + match *el { + PathElement::Str(ref s) => path.push_str(s), + PathElement::Var(ref name) => { + if let Some(val) = elements.get(name) { + path.push_str(val.as_ref()) + } else { + return false; + } + } + } + } + true + } + + fn parse_segment<'a>( + pattern: &'a str, + elems: &mut Vec, + ) -> (String, &'a str, bool) { + const DEFAULT_PATTERN: &str = ".+"; + const DEFAULT_PATTERN_TAIL: &str = ".*"; + + let mut re = "^".to_string(); + let mut end = None; + let mut tail = false; + let mut rem = pattern; + let mut pattern = &pattern[1..]; + elems.push(PathElement::Str('/'.to_string())); + + while let Some(start_idx) = pattern.find('{') { + if let Some(end) = end { + if start_idx > end { + break; + } + } + let p = pattern.split_at(start_idx); + pattern = p.1; + re.push_str(&escape(&p.0)); + elems.push(PathElement::Str(p.0.to_string())); + + // find closing } + let mut params_nesting = 0usize; + let close_idx = pattern + .find(|c| match c { + '{' => { + params_nesting += 1; + false + } + '}' => { + params_nesting -= 1; + params_nesting == 0 + } + _ => false, + }) + .expect("malformed dynamic segment"); + + let p = pattern.split_at(close_idx + 1); + rem = p.1; + let param = &p.0[1..p.0.len() - 1]; // Remove outer brackets + tail = rem == "*"; // tail match (should match regardless of segments) + + let (name, pat) = match param.find(':') { + Some(idx) => { + if tail { + panic!("Custom regex is not supported for remainder match"); + } + let (name, pattern) = param.split_at(idx); + (name, &pattern[1..]) + } + None => ( + param, + if tail { + rem = &rem[1..]; + DEFAULT_PATTERN_TAIL + } else { + DEFAULT_PATTERN + }, + ), + }; + + re.push_str(&format!(r"(?P<{}>{})", &escape(name), pat)); + + elems.push(PathElement::Var(name.to_string())); + + if let Some(idx) = rem.find(|c| c == '{' || c == '/') { + end = Some(idx); + pattern = rem; + continue; + } else { + re += rem; + rem = ""; + break; + } + } + + // find end of segment + if let Some(idx) = rem.find('/') { + re.push_str(&escape(&rem[..idx])); + rem = &rem[idx..]; + } else { + re.push_str(&escape(rem)); + rem = ""; + }; + re.push('$'); + + (re, rem, tail) + } + + fn parse(mut pattern: &str) -> (Segments, Vec) { + let mut elems = Vec::new(); + let mut pelems = Vec::new(); + + while let Some(idx) = pattern[1..].find(|c| c == '{' || c == '/') { + let idx = idx + 1; + + // static segment + if let Some(i) = pattern[1..idx + 1].find('/') { + elems.push(PathElement::Str(pattern[..i + 1].to_string())); + pelems.push(Segment::Static(pattern[1..i + 1].to_string())); + pattern = &pattern[i + 1..]; + continue; + } + + // dynamic segment + let (re_part, rem, tail) = Self::parse_segment(pattern, &mut elems); + let re = Regex::new(&re_part).unwrap(); + let names: Vec<_> = re + .capture_names() + .filter_map(|name| { + name.map(|name| Box::leak(Box::new(name.to_owned())).as_str()) + }) + .collect(); + pelems.push(Segment::Dynamic { + names, + tail, + pattern: re, + len: 0, + }); + + pattern = rem; + if pattern.is_empty() { + break; + } + } + + // tail + let slesh = pattern.ends_with('/'); + if slesh { + pattern = &pattern[..pattern.len() - 1]; + } + elems.push(PathElement::Str(pattern.to_string())); + if pattern.starts_with('/') { + pattern = &pattern[1..]; + } + if !pattern.is_empty() { + // handle tail expression for static segment + if pattern.ends_with('*') { + let pattern = + Regex::new(&format!("^{}(.+)", &pattern[..pattern.len() - 1])) + .unwrap(); + pelems.push(Segment::Dynamic { + pattern, + names: Vec::new(), + tail: true, + len: 0, + }); + } else { + pelems.push(Segment::Static(pattern.to_string())); + } + } + + // insert last slesh + if slesh { + elems.push(PathElement::Str("/".to_string())) + } + + // merge path elements + let mut idx = 0; + while idx + 1 < elems.len() { + if elems[idx + 1].is_str() && elems[idx + 1].as_str().is_empty() { + elems.remove(idx + 1); + continue; + } + if elems[idx].is_str() && elems[idx + 1].is_str() { + let s2 = elems.remove(idx + 1).into_str(); + if let PathElement::Str(ref mut s1) = elems[idx] { + s1.push_str(&s2); + continue; + } + } + idx += 1; + } + + //println!("P: {:#?}", pelems); + //println!("T: {:#?}", elems); + + (Segments { tp: pelems, slesh }, elems) + } +} + +impl Eq for ResourceDef {} + +impl PartialEq for ResourceDef { + fn eq(&self, other: &ResourceDef) -> bool { + self.pattern == other.pattern + } +} + +impl Hash for ResourceDef { + fn hash(&self, state: &mut H) { + self.pattern.hash(state); + } +} + +impl<'a> From<&'a str> for ResourceDef { + fn from(path: &'a str) -> ResourceDef { + ResourceDef::new(path) + } +} + +impl From for ResourceDef { + fn from(path: String) -> ResourceDef { + ResourceDef::new(path) + } +} + +pub(crate) fn insert_slash(path: &str) -> String { + let mut path = path.to_owned(); + if !path.is_empty() && !path.starts_with('/') { + path.insert(0, '/'); + }; + path +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::path::Path; + use crate::tree::Tree; + + #[test] + fn test_parse_static() { + let re = ResourceDef::new("/"); + let tree = Tree::new(&re, 1); + assert_eq!(tree.find(&mut Path::new("/")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/a")), None); + + let re = ResourceDef::new("/name"); + let tree = Tree::new(&re, 1); + assert_eq!(tree.find(&mut Path::new("/name")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/")), None); + assert_eq!(tree.find(&mut Path::new("/name1")), None); + assert_eq!(tree.find(&mut Path::new("/name/")), None); + assert_eq!(tree.find(&mut Path::new("/name~")), None); + + let re = ResourceDef::new("/name/"); + let tree = Tree::new(&re, 1); + assert_eq!(tree.find(&mut Path::new("/name/")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/name")), None); + assert_eq!(tree.find(&mut Path::new("/name/gs")), None); + + let re = ResourceDef::new("/user/profile"); + let tree = Tree::new(&re, 1); + assert_eq!(tree.find(&mut Path::new("/user/profile")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/profile/profile")), None); + + let mut tree = Tree::new(&ResourceDef::new("/name"), 1); + tree.insert(&ResourceDef::new("/name/"), 2); + assert_eq!(tree.find(&mut Path::new("/name")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/name/")), Some(2)); + } + + #[test] + fn test_parse_param() { + let re = ResourceDef::new("/{id}"); + let tree = Tree::new(&re, 1); + assert_eq!(tree.find(&mut Path::new("/profile")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/2345")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/2345/")), None); + assert_eq!(tree.find(&mut Path::new("/2345/sdg")), None); + + let re = ResourceDef::new("/user/{id}"); + let tree = Tree::new(&re, 1); + assert_eq!(tree.find(&mut Path::new("/user/profile")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/2345")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/2345/")), None); + assert_eq!(tree.find(&mut Path::new("/user/2345/sdg")), None); + + let mut resource = Path::new("/user/profile"); + let tree = Tree::new(&re, 1); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.get("id").unwrap(), "profile"); + + let mut resource = Path::new("/user/1245125"); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.get("id").unwrap(), "1245125"); + + let re = ResourceDef::new("/v{version}/resource/{id}"); + let tree = Tree::new(&re, 1); + assert_eq!(tree.find(&mut Path::new("/v1/resource/320120")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/v1/resource/320120/")), None,); + assert_eq!(tree.find(&mut Path::new("/v/resource/1")), None); + assert_eq!(tree.find(&mut Path::new("/resource")), None); + + let mut resource = Path::new("/v151/resource/adahg32"); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.get("version").unwrap(), "151"); + assert_eq!(resource.get("id").unwrap(), "adahg32"); + + let re = ResourceDef::new("/{id:[[:digit:]]{6}}"); + let tree = Tree::new(&re, 1); + assert_eq!(tree.find(&mut Path::new("/012345")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/012345/")), None); + assert_eq!(tree.find(&mut Path::new("/012345/index")), None); + assert_eq!(tree.find(&mut Path::new("/012")), None); + assert_eq!(tree.find(&mut Path::new("/01234567")), None); + assert_eq!(tree.find(&mut Path::new("/XXXXXX")), None); + + let mut resource = Path::new("/012345"); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.get("id").unwrap(), "012345"); + + let re = + ResourceDef::new("/u/test/v{version}-no-{minor}xx/resource/{id}/{name}"); + let tree = Tree::new(&re, 1); + let mut resource = Path::new("/u/test/v1-no-3xx/resource/320120/name"); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.get("version").unwrap(), "1"); + assert_eq!(resource.get("minor").unwrap(), "3"); + assert_eq!(resource.get("id").unwrap(), "320120"); + assert_eq!(resource.get("name").unwrap(), "name"); + } + + #[test] + fn test_dynamic_set() { + let re = ResourceDef::new(vec![ + "/user/{id}", + "/v{version}/resource/{id}", + "/{id:[[:digit:]]{6}}", + ]); + let tree = Tree::new(&re, 1); + assert_eq!(tree.find(&mut Path::new("/user/profile")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/2345")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/2345/")), None); + assert_eq!(tree.find(&mut Path::new("/user/2345/sdg")), None); + + let mut resource = Path::new("/user/profile"); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.get("id").unwrap(), "profile"); + + let mut resource = Path::new("/user/1245125"); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.get("id").unwrap(), "1245125"); + + assert_eq!(tree.find(&mut Path::new("/v1/resource/320120")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/v/resource/1")), None); + assert_eq!(tree.find(&mut Path::new("/resource")), None); + + let mut resource = Path::new("/v151/resource/adahg32"); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.get("version").unwrap(), "151"); + assert_eq!(resource.get("id").unwrap(), "adahg32"); + + assert_eq!(tree.find(&mut Path::new("/012345")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/012")), None); + assert_eq!(tree.find(&mut Path::new("/01234567")), None); + assert_eq!(tree.find(&mut Path::new("/XXXXXX")), None); + + let mut resource = Path::new("/012345"); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.get("id").unwrap(), "012345"); + + let re = ResourceDef::new([ + "/user/{id}", + "/v{version}/resource/{id}", + "/{id:[[:digit:]]{6}}", + ]); + let tree = Tree::new(&re, 1); + assert_eq!(tree.find(&mut Path::new("/user/profile")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/2345")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/2345/")), None); + assert_eq!(tree.find(&mut Path::new("/user/2345/sdg")), None); + + let re = ResourceDef::new([ + "/user/{id}".to_string(), + "/v{version}/resource/{id}".to_string(), + "/{id:[[:digit:]]{6}}".to_string(), + ]); + let tree = Tree::new(&re, 1); + assert_eq!(tree.find(&mut Path::new("/user/profile")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/2345")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/2345/")), None); + assert_eq!(tree.find(&mut Path::new("/user/2345/sdg")), None); + } + + #[cfg(feature = "http")] + #[test] + fn test_parse_urlencoded() { + use http::Uri; + use std::convert::TryFrom; + + let tree = Tree::new(&ResourceDef::new("/user/{id}/test"), 1); + let uri = Uri::try_from("/user/2345/test").unwrap(); + let mut resource = Path::new(uri); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.get("id").unwrap(), "2345"); + + let uri = Uri::try_from("/user/qwe%25/test").unwrap(); + let mut resource = Path::new(uri); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.get("id").unwrap(), "qwe%"); + + let uri = Uri::try_from("/user/qwe%25rty/test").unwrap(); + let mut resource = Path::new(uri); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.get("id").unwrap(), "qwe%rty"); + + let uri = Uri::try_from("/user/foo-%2f-%252f-bar/test").unwrap(); + let mut resource = Path::new(uri); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.get("id").unwrap(), "foo-/-%2f-bar"); + + let uri = Uri::try_from( + "/user/http%3A%2F%2Flocalhost%3A80%2Ffile%2F%2Fvar%2Flog%2Fsyslog/test", + ) + .unwrap(); + let mut resource = Path::new(uri); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!( + resource.get("id").unwrap(), + "http://localhost:80/file//var/log/syslog" + ); + } + + #[test] + fn test_parse_tail() { + let re = ResourceDef::new("/user/-{id}*"); + let tree = Tree::new(&re, 1); + + let mut resource = Path::new("/user/-profile"); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.get("id").unwrap(), "profile"); + + let mut resource = Path::new("/user/-2345"); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.get("id").unwrap(), "2345"); + + let mut resource = Path::new("/user/-2345/"); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.get("id").unwrap(), "2345/"); + + let mut resource = Path::new("/user/-2345/sdg"); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.get("id").unwrap(), "2345/sdg"); + } + + #[test] + fn test_static_tail() { + let re = ResourceDef::new("/*"); + let tree = Tree::new(&re, 1); + assert_eq!(tree.find(&mut Path::new("/")), None); + assert_eq!(tree.find(&mut Path::new("/profile")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/profile")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/2345")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/2345/")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/2345/")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/2345/sdg")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/2345/sdg")), Some(1)); + + let re = ResourceDef::new("/user*"); + let tree = Tree::new(&re, 1); + assert_eq!(tree.find(&mut Path::new("/user/profile")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/2345")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/2345/")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/2345/sdg")), Some(1)); + + let re = ResourceDef::new("/v/user*"); + let tree = Tree::new(&re, 1); + assert_eq!(tree.find(&mut Path::new("/v/user/profile")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/v/user/2345")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/v/user/2345/")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/v/user/2345/sdg")), Some(1)); + + let re = ResourceDef::new("/user/*"); + let tree = Tree::new(&re, 1); + assert_eq!(tree.find(&mut Path::new("/user/profile")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/2345")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/2345/")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/2345/sdg")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/user/")), None); + assert_eq!(tree.find(&mut Path::new("/user")), None); + + let re = ResourceDef::new("/v/user/*"); + let tree = Tree::new(&re, 1); + assert_eq!(tree.find(&mut Path::new("/v/user/profile")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/v/user/2345")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/v/user/2345/")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/v/user/2345/sdg")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/v/user/")), None); + assert_eq!(tree.find(&mut Path::new("/v/user")), None); + } + + #[test] + fn test_resource_prefix() { + let tree = Tree::new(&ResourceDef::prefix("/name"), 1); + assert_eq!(tree.find(&mut Path::new("/name")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/name/")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/name/test/test")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/name1")), None); + assert_eq!(tree.find(&mut Path::new("/name~")), None); + + let mut resource = Path::new("/name/subpath1/subpath2/index.html"); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.path(), "/subpath1/subpath2/index.html"); + + let tree = Tree::new(&ResourceDef::prefix("/name/"), 1); + assert_eq!(tree.find(&mut Path::new("/name/")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/name/test/test")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/name")), None); + assert_eq!(tree.find(&mut Path::new("/name1")), None); + + let tree = Tree::new(&ResourceDef::root_prefix("name/"), 1); + assert_eq!(tree.find(&mut Path::new("/name/")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/name/test/test")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/name")), None); + assert_eq!(tree.find(&mut Path::new("/name1")), None); + + let mut resource = Path::new("/name/subpath1/subpath2/index.html"); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(resource.path(), "/subpath1/subpath2/index.html"); + } + + #[test] + fn test_reousrce_prefix_dynamic() { + let tree = Tree::new(&ResourceDef::prefix("/{name}/"), 1); + assert_eq!(tree.find(&mut Path::new("/name/")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/name/test/test")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/name")), None); + assert_eq!(tree.find(&mut Path::new("/name1")), None); + assert_eq!(tree.find(&mut Path::new("/name~")), None); + + let mut resource = Path::new("/test2/"); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(&resource["name"], "test2"); + assert_eq!(&resource[0], "test2"); + + let mut resource = Path::new("/test2/subpath1/subpath2/index.html"); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(&resource["name"], "test2"); + assert_eq!(&resource[0], "test2"); + assert_eq!(resource.path(), "/subpath1/subpath2/index.html"); + + let tree = Tree::new(&ResourceDef::prefix("/{name}"), 1); + assert_eq!(tree.find(&mut Path::new("/name")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/name/")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/name/test/test")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/name1")), Some(1)); + assert_eq!(tree.find(&mut Path::new("/name~")), Some(1)); + + let mut resource = Path::new("/test2/subpath1/subpath2/index.html"); + assert_eq!(tree.find(&mut resource), Some(1)); + assert_eq!(&resource["name"], "test2"); + assert_eq!(&resource[0], "test2"); + assert_eq!(resource.path(), "/subpath1/subpath2/index.html"); + } + + #[test] + fn test_resource_path() { + let mut s = String::new(); + let resource = ResourceDef::new("/user/{item1}/test"); + assert!(resource.resource_path(&mut s, &mut (&["user1"]).iter())); + assert_eq!(s, "/user/user1/test"); + + let mut s = String::new(); + let resource = ResourceDef::new("/user/{item1}/{item2}/test"); + assert!(resource.resource_path(&mut s, &mut (&["item", "item2"]).iter())); + assert_eq!(s, "/user/item/item2/test"); + + let mut s = String::new(); + let resource = ResourceDef::new("/user/{item1}/{item2}"); + assert!(resource.resource_path(&mut s, &mut (&["item", "item2"]).iter())); + assert_eq!(s, "/user/item/item2"); + + let mut s = String::new(); + let resource = ResourceDef::new("/user/{item1}/{item2}/"); + assert!(resource.resource_path(&mut s, &mut (&["item", "item2"]).iter())); + assert_eq!(s, "/user/item/item2/"); + + let mut s = String::new(); + assert!(!resource.resource_path(&mut s, &mut (&["item"]).iter())); + + let mut s = String::new(); + assert!(resource.resource_path(&mut s, &mut (&["item", "item2"]).iter())); + assert_eq!(s, "/user/item/item2/"); + assert!(!resource.resource_path(&mut s, &mut (&["item"]).iter())); + + let mut s = String::new(); + assert!(resource.resource_path(&mut s, &mut vec!["item", "item2"].into_iter())); + assert_eq!(s, "/user/item/item2/"); + + let mut map = HashMap::new(); + map.insert("item1", "item"); + + let mut s = String::new(); + assert!(!resource.resource_path_named(&mut s, &map)); + + let mut s = String::new(); + map.insert("item2", "item2"); + assert!(resource.resource_path_named(&mut s, &map)); + assert_eq!(s, "/user/item/item2/"); + } +} diff --git a/ntex-router/src/router.rs b/ntex-router/src/router.rs new file mode 100644 index 00000000..e508a4aa --- /dev/null +++ b/ntex-router/src/router.rs @@ -0,0 +1,288 @@ +use super::tree::Tree; +use super::{IntoPattern, Resource, ResourceDef, ResourcePath}; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct ResourceId(pub u16); + +/// Information about current resource +#[derive(Clone, Debug)] +pub struct ResourceInfo { + resource: ResourceId, +} + +/// Resource router. +pub struct Router { + tree: Tree, + resources: Vec<(ResourceDef, T, Option)>, +} + +impl Router { + pub fn build() -> RouterBuilder { + RouterBuilder { + resources: Vec::new(), + } + } + + pub fn recognize(&self, resource: &mut R) -> Option<(&T, ResourceId)> + where + R: Resource

, + P: ResourcePath, + { + if let Some(idx) = self.tree.find(resource) { + let item = &self.resources[idx]; + Some((&item.1, ResourceId(item.0.id()))) + } else { + None + } + } + + pub fn recognize_mut( + &mut self, + resource: &mut R, + ) -> Option<(&mut T, ResourceId)> + where + R: Resource

, + P: ResourcePath, + { + if let Some(idx) = self.tree.find(resource) { + let item = &mut self.resources[idx]; + Some((&mut item.1, ResourceId(item.0.id()))) + } else { + None + } + } + + pub fn recognize_mut_checked( + &mut self, + resource: &mut R, + check: F, + ) -> Option<(&mut T, ResourceId)> + where + F: Fn(&R, Option<&U>) -> bool, + R: Resource

, + P: ResourcePath, + { + if let Some(idx) = self.tree.find_checked(resource, &|idx, res| { + let item = &self.resources[idx]; + check(res, item.2.as_ref()) + }) { + let item = &mut self.resources[idx]; + Some((&mut item.1, ResourceId(item.0.id()))) + } else { + None + } + } +} + +pub struct RouterBuilder { + resources: Vec<(ResourceDef, T, Option)>, +} + +impl RouterBuilder { + /// Register resource for specified path. + pub fn path( + &mut self, + path: P, + resource: T, + ) -> &mut (ResourceDef, T, Option) { + self.resources + .push((ResourceDef::new(path), resource, None)); + self.resources.last_mut().unwrap() + } + + /// Register resource for specified path prefix. + pub fn prefix( + &mut self, + prefix: &str, + resource: T, + ) -> &mut (ResourceDef, T, Option) { + self.resources + .push((ResourceDef::prefix(prefix), resource, None)); + self.resources.last_mut().unwrap() + } + + /// Register resource for ResourceDef + pub fn rdef( + &mut self, + rdef: ResourceDef, + resource: T, + ) -> &mut (ResourceDef, T, Option) { + self.resources.push((rdef, resource, None)); + self.resources.last_mut().unwrap() + } + + /// Finish configuration and create router instance. + pub fn finish(self) -> Router { + let tree = if self.resources.is_empty() { + Tree::default() + } else { + let mut tree = Tree::new(&self.resources[0].0, 0); + for (idx, r) in self.resources[1..].iter().enumerate() { + tree.insert(&r.0, idx + 1) + } + tree + }; + + Router { + tree, + resources: self.resources, + } + } +} + +#[cfg(test)] +mod tests { + use crate::path::Path; + use crate::router::{ResourceId, Router}; + + #[test] + fn test_recognizer_1() { + let mut router = Router::::build(); + router.path("/name", 10).0.set_id(0); + router.path("/name/{val}", 11).0.set_id(1); + router.path("/name/{val}/index.html", 12).0.set_id(2); + router.path("/file/{file}.{ext}", 13).0.set_id(3); + router.path("/v{val}/{val2}/index.html", 14).0.set_id(4); + router.path("/v/{tail}*", 15).0.set_id(5); + router.path("/test2/{test}.html", 16).0.set_id(6); + router.path("/{test}/index.html", 17).0.set_id(7); + router.path("/v2/{custom:.*}/test.html", 18).0.set_id(8); + let mut router = router.finish(); + + let mut path = Path::new("/unknown"); + assert!(router.recognize_mut(&mut path).is_none()); + + let mut path = Path::new("/unknown"); + assert!(router.recognize(&mut path).is_none()); + + let mut path = Path::new("/name"); + let (h, info) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 10); + assert_eq!(info, ResourceId(0)); + assert!(path.is_empty()); + + let mut path = Path::new("/name"); + let (h, info) = router.recognize(&mut path).unwrap(); + assert_eq!(*h, 10); + assert_eq!(info, ResourceId(0)); + assert!(path.is_empty()); + + let mut path = Path::new("/name/value"); + let (h, info) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 11); + assert_eq!(info, ResourceId(1)); + assert_eq!(path.get("val").unwrap(), "value"); + assert_eq!(&path["val"], "value"); + + let mut path = Path::new("/name/value2/index.html"); + let (h, info) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 12); + assert_eq!(info, ResourceId(2)); + assert_eq!(path.get("val").unwrap(), "value2"); + + let mut path = Path::new("/file/file.gz"); + let (h, info) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 13); + assert_eq!(info, ResourceId(3)); + assert_eq!(path.get("file").unwrap(), "file"); + assert_eq!(path.get("ext").unwrap(), "gz"); + + let mut path = Path::new("/vtest/ttt/index.html"); + let (h, info) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 14); + assert_eq!(info, ResourceId(4)); + assert_eq!(path.get("val").unwrap(), "test"); + assert_eq!(path.get("val2").unwrap(), "ttt"); + + let mut path = Path::new("/v/blah-blah/index.html"); + let (h, info) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 15); + assert_eq!(info, ResourceId(5)); + assert_eq!(path.get("tail").unwrap(), "blah-blah/index.html"); + + let mut path = Path::new("/test2/index.html"); + let (h, info) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 16); + assert_eq!(info, ResourceId(6)); + assert_eq!(path.get("test").unwrap(), "index"); + + let mut path = Path::new("/bbb/index.html"); + let (h, info) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 17); + assert_eq!(info, ResourceId(7)); + assert_eq!(path.get("test").unwrap(), "bbb"); + + let mut path = Path::new("/v2/blah-blah/test.html"); + let (h, info) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 18); + assert_eq!(info, ResourceId(8)); + assert_eq!(path.get("custom").unwrap(), "blah-blah"); + } + + #[test] + fn test_recognizer_2() { + let mut router = Router::::build(); + router.path("/index.json", 10); + router.path("/{source}.json", 11); + let mut router = router.finish(); + + let mut path = Path::new("/index.json"); + let (h, _) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 10); + + let mut path = Path::new("/test.json"); + let (h, _) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 11); + } + + #[test] + fn test_recognizer_with_path_skip() { + let mut router = Router::::build(); + router.path("/name", 10).0.set_id(0); + router.path("/name/{val}", 11).0.set_id(1); + let mut router = router.finish(); + + let mut path = Path::new("/name"); + path.skip(5); + assert!(router.recognize_mut(&mut path).is_none()); + + let mut path = Path::new("/test/name"); + path.skip(5); + let (h, _) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 10); + + println!("\n======================="); + let mut path = Path::new("/test/name/value"); + path.skip(5); + let (h, id) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 11); + assert_eq!(id, ResourceId(1)); + assert_eq!(path.get("val").unwrap(), "value"); + assert_eq!(&path["val"], "value"); + + // same patterns + let mut router = Router::::build(); + router.path("/name", 10); + router.path("/name/{val}", 11); + let mut router = router.finish(); + + let mut path = Path::new("/name"); + path.skip(5); + assert!(router.recognize_mut(&mut path).is_none()); + + let mut path = Path::new("/test2/name"); + path.skip(6); + let (h, _) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 10); + + let mut path = Path::new("/test2/name-test"); + path.skip(6); + assert!(router.recognize_mut(&mut path).is_none()); + + let mut path = Path::new("/test2/name/ttt"); + path.skip(6); + let (h, _) = router.recognize_mut(&mut path).unwrap(); + assert_eq!(*h, 11); + assert_eq!(&path["val"], "ttt"); + } +} diff --git a/ntex-router/src/tree.rs b/ntex-router/src/tree.rs new file mode 100644 index 00000000..505a1ac5 --- /dev/null +++ b/ntex-router/src/tree.rs @@ -0,0 +1,397 @@ +use std::mem; + +use super::path::PathItem; +use super::resource::{ResourceDef, Segment}; +use super::{Resource, ResourcePath}; + +#[derive(Debug)] +pub(super) struct Tree { + key: Vec, + value: Vec, + children: Vec, +} + +#[derive(Copy, Clone, Debug)] +enum Value { + Val(usize), + Slesh(usize), + Prefix(usize), + PrefixSlesh(usize), +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum PathState { + Empty, + Slesh, + Tail, +} + +impl Value { + fn value(&self) -> usize { + match self { + Value::Val(v) => *v, + Value::Slesh(v) => *v, + Value::Prefix(v) => *v, + Value::PrefixSlesh(v) => *v, + } + } +} + +impl Default for Tree { + fn default() -> Tree { + Tree { + key: Vec::new(), + value: Vec::new(), + children: Vec::new(), + } + } +} + +impl Tree { + pub(crate) fn new(resource: &ResourceDef, value: usize) -> Tree { + if resource.tp.is_empty() { + panic!("Non empty key is expected"); + } + + let val = if resource.tp[0].slesh { + if resource.prefix { + Value::PrefixSlesh(value) + } else { + Value::Slesh(value) + } + } else if resource.prefix { + Value::Prefix(value) + } else { + Value::Val(value) + }; + + let mut tree = Tree::child(resource.tp[0].tp.clone(), Some(val)); + + for seg in &resource.tp[1..] { + let val = if seg.slesh { + if resource.prefix { + Value::PrefixSlesh(value) + } else { + Value::Slesh(value) + } + } else if resource.prefix { + Value::Prefix(value) + } else { + Value::Val(value) + }; + tree.insert_path(seg.tp.clone(), val) + } + tree + } + + fn child(key: Vec, value: Option) -> Tree { + let value = if let Some(val) = value { + vec![val] + } else { + Vec::new() + }; + Tree { + key, + value, + children: Vec::new(), + } + } + + pub(super) fn insert(&mut self, resource: &ResourceDef, value: usize) { + for seg in &resource.tp { + let value = if seg.slesh { + if resource.prefix { + Value::PrefixSlesh(value) + } else { + Value::Slesh(value) + } + } else if resource.prefix { + Value::Prefix(value) + } else { + Value::Val(value) + }; + let key: Vec<_> = seg.tp.to_vec(); + self.insert_path(key, value); + } + } + + fn insert_path(&mut self, key: Vec, value: Value) { + let p = common_prefix(&self.key, &key); + + // split current key, and move all children to sub tree + if p < self.key.len() { + let child = Tree { + key: self.key.split_off(p), + value: mem::replace(&mut self.value, Vec::new()), + children: mem::take(&mut self.children), + }; + self.children.push(child); + } + // update value if key is the same + if p == key.len() { + self.value.push(value); + } else { + // insert into sub tree + let mut child = self + .children + .iter_mut() + .find(|x| common_prefix(&x.key, &key[p..]) > 0); + if let Some(ref mut child) = child { + child.insert_path(key[p..].to_vec(), value) + } else { + self.children + .push(Tree::child(key[p..].to_vec(), Some(value))); + } + } + } + + fn get_value(&self, resource: &R, check: &F) -> Option + where + T: ResourcePath, + R: Resource, + F: Fn(usize, &R) -> bool, + { + for val in &self.value { + let v = val.value(); + if check(v, resource) { + return Some(v); + } + } + None + } + + pub(crate) fn find(&self, resource: &mut R) -> Option + where + T: ResourcePath, + R: Resource, + { + self.find_checked(resource, &|_, _| true) + } + + pub(crate) fn find_checked( + &self, + resource: &mut R, + check: &F, + ) -> Option + where + T: ResourcePath, + R: Resource, + F: Fn(usize, &R) -> bool, + { + let path = resource.resource_path(); + let mut segments = mem::take(&mut path.segments); + let path = resource.path(); + + println!("\n\n\n=========== {:?}", path); + + if self.key.is_empty() { + if path == "/" { + if let Some(val) = self.get_value(resource, check) { + return Some(val); + } + } + let res = self + .children + .iter() + .map(|x| x.find_inner2(path, resource, check, 1, &mut segments)) + .filter_map(|x| x) + .next(); + + return if let Some((val, skip)) = res { + let path = resource.resource_path(); + path.segments = segments; + path.skip = path.skip + skip as u16; + Some(val) + } else { + None + }; + } + + if path.is_empty() { + return None; + } + + if let Some((val, skip)) = + self.find_inner2(path, resource, check, 1, &mut segments) + { + let path = resource.resource_path(); + path.segments = segments; + path.skip = path.skip + skip as u16; + Some(val) + } else { + None + } + } + + fn find_inner2( + &self, + path: &str, + resource: &R, + check: &F, + mut skip: usize, + segments: &mut Vec<(&'static str, PathItem)>, + ) -> Option<(usize, usize)> + where + T: ResourcePath, + R: Resource, + F: Fn(usize, &R) -> bool, + { + // println!("K: {:?} {:?}", self.value, self.key); + // println!("P: {:?} {:?}", path, skip); + + let mut key: &[_] = &self.key; + let mut path = &path[1..]; + + loop { + let idx = if let Some(idx) = path.find('/') { + idx + } else { + path.len() + }; + let segment = T::unquote(&path[..idx]); + //println!("ITEM: {:?} {:?}", segment, key[0]); + + // check segment match + let is_match = match key[0] { + Segment::Static(ref pattern) => pattern == segment.as_ref(), + Segment::Dynamic { + ref pattern, + ref names, + tail, + .. + } => { + // println!( + // "MATCH: {:?} {:?} {:?} {:?}", + // pattern.as_str(), + // segment, + // pattern.is_match(&segment), + // tail, + // ); + + // special treatment for tail, it matches regardless of sleshes + let seg = if tail { path } else { segment.as_ref() }; + + if let Some(captures) = pattern.captures(seg) { + let mut is_match = true; + for name in names.iter() { + if let Some(m) = captures.name(&name) { + segments.push(( + name, + PathItem::Segment(m.as_str().to_string()), + )); + } else { + log::error!( + "Dynamic path match but not all segments found: {}", + name + ); + is_match = false; + break; + } + } + + // we have to process checker for tail matches separately + if tail && is_match { + // checker + if let Some(val) = self.get_value(resource, check) { + return Some((val, skip + idx)); + } + } + + is_match + } else { + false + } + } + }; + + if is_match { + key = &key[1..]; + skip += idx + 1; + + // check path length (path is consumed) + if idx == path.len() { + return { + if key.is_empty() { + // checker + for val in &self.value { + let v = match val { + Value::Val(v) | Value::Prefix(v) => *v, + Value::Slesh(_) | Value::PrefixSlesh(_) => continue, + }; + if check(v, resource) { + return Some((v, skip)); + } + } + } + None + }; + } else if key.is_empty() { + path = &path[idx..]; + + let p = if path.is_empty() { + PathState::Empty + } else if path == "/" { + PathState::Slesh + } else { + PathState::Tail + }; + + for val in &self.value { + let v = match val { + Value::Val(v) => { + if p == PathState::Empty { + *v + } else { + continue; + } + } + Value::Slesh(v) => { + if p == PathState::Slesh { + *v + } else { + continue; + } + } + Value::Prefix(v) => { + if p == PathState::Slesh || p == PathState::Tail { + *v + } else { + continue; + } + } + Value::PrefixSlesh(v) => { + if p == PathState::Slesh || p == PathState::Tail { + *v + } else { + continue; + } + } + }; + if check(v, resource) { + return Some((v, skip - 1)); + } + } + + // println!("TREE: {:#?}", self); + return self + .children + .iter() + .map(|x| x.find_inner2(path, resource, check, skip, segments)) + .filter_map(|x| x) + .next(); + } else { + path = &path[idx + 1..]; + } + } else { + return None; + } + } + } +} + +fn common_prefix(k1: &[Segment], k2: &[Segment]) -> usize { + k1.iter() + .zip(k2.iter()) + .take_while(|&(a, b)| a == b) + .count() +}