mirror of
https://github.com/ntex-rs/ntex.git
synced 2025-04-04 21:37:58 +03:00
add ntex-rouoter crate
This commit is contained in:
parent
68daf75e91
commit
e862efc4e6
10 changed files with 2754 additions and 0 deletions
|
@ -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" }
|
||||
|
|
57
ntex-router/CHANGES.txt
Normal file
57
ntex-router/CHANGES.txt
Normal file
|
@ -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
|
28
ntex-router/Cargo.toml
Normal file
28
ntex-router/Cargo.toml
Normal file
|
@ -0,0 +1,28 @@
|
|||
[package]
|
||||
name = "ntex-router"
|
||||
version = "0.3.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
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"
|
714
ntex-router/src/de.rs
Normal file
714
ntex-router/src/de.rs
Normal file
|
@ -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<V>(self, _: V) -> Result<V::Value, Self::Error>
|
||||
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<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
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<T>,
|
||||
}
|
||||
|
||||
impl<'de, T: ResourcePath + 'de> PathDeserializer<'de, T> {
|
||||
pub fn new(path: &'de Path<T>) -> Self {
|
||||
PathDeserializer { path }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T: ResourcePath + 'de> Deserializer<'de> for PathDeserializer<'de, T> {
|
||||
type Error = de::value::Error;
|
||||
|
||||
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_map(ParamsDeserializer {
|
||||
params: self.path.iter(),
|
||||
current: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn deserialize_struct<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
_: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
self.deserialize_map(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_unit()
|
||||
}
|
||||
|
||||
fn deserialize_unit_struct<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
self.deserialize_unit(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_newtype_struct<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_newtype_struct(self)
|
||||
}
|
||||
|
||||
fn deserialize_tuple<V>(
|
||||
self,
|
||||
len: usize,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
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<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
len: usize,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
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<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
_: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
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<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
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<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
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<T>");
|
||||
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<K>(&mut self, seed: K) -> Result<Option<K::Value>, 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<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
|
||||
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<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_str(self.key)
|
||||
}
|
||||
|
||||
fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
|
||||
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<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
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<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_unit()
|
||||
}
|
||||
|
||||
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_unit()
|
||||
}
|
||||
|
||||
fn deserialize_unit_struct<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_unit()
|
||||
}
|
||||
|
||||
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_borrowed_bytes(self.value.as_bytes())
|
||||
}
|
||||
|
||||
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_borrowed_str(self.value)
|
||||
}
|
||||
|
||||
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_some(self)
|
||||
}
|
||||
|
||||
fn deserialize_enum<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
_: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_enum(ValueEnum { value: self.value })
|
||||
}
|
||||
|
||||
fn deserialize_newtype_struct<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_newtype_struct(self)
|
||||
}
|
||||
|
||||
fn deserialize_tuple<V>(self, _: usize, _: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
Err(de::value::Error::custom("unsupported type: tuple"))
|
||||
}
|
||||
|
||||
fn deserialize_struct<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
_: &'static [&'static str],
|
||||
_: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
Err(de::value::Error::custom("unsupported type: struct"))
|
||||
}
|
||||
|
||||
fn deserialize_tuple_struct<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
_: usize,
|
||||
_: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
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<U>(&mut self, seed: U) -> Result<Option<U::Value>, 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<V>(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<T>(self, _seed: T) -> Result<T::Value, Self::Error>
|
||||
where
|
||||
T: de::DeserializeSeed<'de>,
|
||||
{
|
||||
Err(de::value::Error::custom("not supported"))
|
||||
}
|
||||
|
||||
fn tuple_variant<V>(self, _len: usize, _visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
Err(de::value::Error::custom("not supported"))
|
||||
}
|
||||
|
||||
fn struct_variant<V>(
|
||||
self,
|
||||
_: &'static [&'static str],
|
||||
_: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
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<String> =
|
||||
// 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<Test3, de::value::Error> =
|
||||
// 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<Test1, de::value::Error> =
|
||||
// de::Deserialize::deserialize(PathDeserializer::new(&path));
|
||||
// assert!(s.is_err());
|
||||
// assert!(format!("{:?}", s).contains("wrong number of parameters"));
|
||||
|
||||
// let s: Result<Test2, de::value::Error> =
|
||||
// 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<u32, de::value::Error> =
|
||||
// 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::<String>::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::<Test2>::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::<String>::from_request(&req, &&PathConfig::default().disable_decoding())
|
||||
// .unwrap(),
|
||||
// "%25"
|
||||
// );
|
||||
// }
|
||||
// }
|
161
ntex-router/src/lib.rs
Normal file
161
ntex-router/src/lib.rs
Normal file
|
@ -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<T: ResourcePath> {
|
||||
fn path(&self) -> &str;
|
||||
|
||||
fn resource_path(&mut self) -> &mut Path<T>;
|
||||
}
|
||||
|
||||
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<String>;
|
||||
}
|
||||
|
||||
impl IntoPattern for String {
|
||||
fn is_single(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn patterns(&self) -> Vec<String> {
|
||||
vec![self.clone()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoPattern for &'a String {
|
||||
fn is_single(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn patterns(&self) -> Vec<String> {
|
||||
vec![self.as_str().to_string()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoPattern for &'a str {
|
||||
fn is_single(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn patterns(&self) -> Vec<String> {
|
||||
vec![(*self).to_string()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> IntoPattern for Vec<T> {
|
||||
fn is_single(&self) -> bool {
|
||||
self.len() == 1
|
||||
}
|
||||
|
||||
fn patterns(&self) -> Vec<String> {
|
||||
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<String> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
221
ntex-router/src/path.rs
Normal file
221
ntex-router/src/path.rs
Normal file
|
@ -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<T> {
|
||||
path: T,
|
||||
pub(super) skip: u16,
|
||||
pub(super) segments: Vec<(&'static str, PathItem)>,
|
||||
}
|
||||
|
||||
impl<T: Default> Default for Path<T> {
|
||||
fn default() -> Self {
|
||||
Path {
|
||||
path: T::default(),
|
||||
skip: 0,
|
||||
segments: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Clone for Path<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Path {
|
||||
path: self.path.clone(),
|
||||
skip: self.skip,
|
||||
segments: self.segments.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ResourcePath> Path<T> {
|
||||
pub fn new(path: T) -> Path<T> {
|
||||
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<T> {
|
||||
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<U, de::value::Error> {
|
||||
de::Deserialize::deserialize(PathDeserializer::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PathIter<'a, T> {
|
||||
idx: usize,
|
||||
params: &'a Path<T>,
|
||||
}
|
||||
|
||||
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<T> {
|
||||
type Output = str;
|
||||
|
||||
fn index(&self, name: &'a str) -> &str {
|
||||
self.get(name)
|
||||
.expect("Value for parameter is not available")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ResourcePath> Index<usize> for Path<T> {
|
||||
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<T: ResourcePath> Resource<T> for Path<T> {
|
||||
fn path(&self) -> &str {
|
||||
self.path()
|
||||
}
|
||||
|
||||
fn resource_path(&mut self) -> &mut Path<T> {
|
||||
self
|
||||
}
|
||||
}
|
62
ntex-router/src/quoter.rs
Normal file
62
ntex-router/src/quoter.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
pub fn requote(val: &[u8]) -> Option<String> {
|
||||
let mut has_pct = 0;
|
||||
let mut pct = [b'%', 0, 0];
|
||||
let mut idx = 0;
|
||||
let mut cloned: Option<Vec<u8>> = 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<u8> {
|
||||
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<u8> {
|
||||
from_hex(d1).and_then(|d1| from_hex(d2).map(move |d2| d1 << 4 | d2))
|
||||
}
|
824
ntex-router/src/resource.rs
Normal file
824
ntex-router/src/resource.rs
Normal file
|
@ -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<Segment>,
|
||||
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<Segments>, // set of matching paths
|
||||
name: String,
|
||||
pattern: String,
|
||||
elements: Vec<PathElement>,
|
||||
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<T: IntoPattern>(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<U, I>(&self, path: &mut String, elements: &mut U) -> bool
|
||||
where
|
||||
U: Iterator<Item = I>,
|
||||
I: AsRef<str>,
|
||||
{
|
||||
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<K, V, S>(
|
||||
&self,
|
||||
path: &mut String,
|
||||
elements: &HashMap<K, V, S>,
|
||||
) -> bool
|
||||
where
|
||||
K: std::borrow::Borrow<str> + Eq + Hash,
|
||||
V: AsRef<str>,
|
||||
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<PathElement>,
|
||||
) -> (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<PathElement>) {
|
||||
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<H: Hasher>(&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<String> 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/");
|
||||
}
|
||||
}
|
288
ntex-router/src/router.rs
Normal file
288
ntex-router/src/router.rs
Normal file
|
@ -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<T, U = ()> {
|
||||
tree: Tree,
|
||||
resources: Vec<(ResourceDef, T, Option<U>)>,
|
||||
}
|
||||
|
||||
impl<T, U> Router<T, U> {
|
||||
pub fn build() -> RouterBuilder<T, U> {
|
||||
RouterBuilder {
|
||||
resources: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recognize<R, P>(&self, resource: &mut R) -> Option<(&T, ResourceId)>
|
||||
where
|
||||
R: Resource<P>,
|
||||
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<R, P>(
|
||||
&mut self,
|
||||
resource: &mut R,
|
||||
) -> Option<(&mut T, ResourceId)>
|
||||
where
|
||||
R: Resource<P>,
|
||||
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<R, P, F>(
|
||||
&mut self,
|
||||
resource: &mut R,
|
||||
check: F,
|
||||
) -> Option<(&mut T, ResourceId)>
|
||||
where
|
||||
F: Fn(&R, Option<&U>) -> bool,
|
||||
R: Resource<P>,
|
||||
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<T, U = ()> {
|
||||
resources: Vec<(ResourceDef, T, Option<U>)>,
|
||||
}
|
||||
|
||||
impl<T, U> RouterBuilder<T, U> {
|
||||
/// Register resource for specified path.
|
||||
pub fn path<P: IntoPattern>(
|
||||
&mut self,
|
||||
path: P,
|
||||
resource: T,
|
||||
) -> &mut (ResourceDef, T, Option<U>) {
|
||||
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<U>) {
|
||||
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<U>) {
|
||||
self.resources.push((rdef, resource, None));
|
||||
self.resources.last_mut().unwrap()
|
||||
}
|
||||
|
||||
/// Finish configuration and create router instance.
|
||||
pub fn finish(self) -> Router<T, U> {
|
||||
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::<usize>::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::<usize>::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::<usize>::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::<usize>::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");
|
||||
}
|
||||
}
|
397
ntex-router/src/tree.rs
Normal file
397
ntex-router/src/tree.rs
Normal file
|
@ -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<Segment>,
|
||||
value: Vec<Value>,
|
||||
children: Vec<Tree>,
|
||||
}
|
||||
|
||||
#[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<Segment>, value: Option<Value>) -> 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<Segment>, 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<T, R, F>(&self, resource: &R, check: &F) -> Option<usize>
|
||||
where
|
||||
T: ResourcePath,
|
||||
R: Resource<T>,
|
||||
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<T, R>(&self, resource: &mut R) -> Option<usize>
|
||||
where
|
||||
T: ResourcePath,
|
||||
R: Resource<T>,
|
||||
{
|
||||
self.find_checked(resource, &|_, _| true)
|
||||
}
|
||||
|
||||
pub(crate) fn find_checked<T, R, F>(
|
||||
&self,
|
||||
resource: &mut R,
|
||||
check: &F,
|
||||
) -> Option<usize>
|
||||
where
|
||||
T: ResourcePath,
|
||||
R: Resource<T>,
|
||||
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<T, R, F>(
|
||||
&self,
|
||||
path: &str,
|
||||
resource: &R,
|
||||
check: &F,
|
||||
mut skip: usize,
|
||||
segments: &mut Vec<(&'static str, PathItem)>,
|
||||
) -> Option<(usize, usize)>
|
||||
where
|
||||
T: ResourcePath,
|
||||
R: Resource<T>,
|
||||
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()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue