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]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"ntex",
|
"ntex",
|
||||||
|
"ntex-router",
|
||||||
"ntex-service",
|
"ntex-service",
|
||||||
"ntex-web-macros",
|
"ntex-web-macros",
|
||||||
|
|
||||||
|
@ -11,6 +12,7 @@ members = [
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
ntex = { path = "ntex" }
|
ntex = { path = "ntex" }
|
||||||
|
ntex-router = { path = "ntex-router" }
|
||||||
ntex-service = { path = "ntex-service" }
|
ntex-service = { path = "ntex-service" }
|
||||||
|
|
||||||
actix-server = { path = "actix-net/actix-server" }
|
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