Add non-allocating path item

This commit is contained in:
Nikolay Kim 2020-04-06 11:43:28 +06:00
parent d4c7ac25c4
commit 99b08a1c16
7 changed files with 113 additions and 16 deletions

View file

@ -1,5 +1,11 @@
# Changes
## [0.3.1] - 2020-04-06
* Fix url quoter
* Add non-allocating path item
## [0.3.0] - 2020-03-31
* Case insensitive routing

View file

@ -1,6 +1,6 @@
[package]
name = "ntex-router"
version = "0.3.0"
version = "0.3.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Path router"
keywords = ["ntex"]

View file

@ -528,6 +528,9 @@ mod tests {
("value", PathItem::Static("user1")),
];
let s: () = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
assert_eq!(s, ());
let s: MyStruct =
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
assert_eq!(s.key, "name");
@ -553,11 +556,21 @@ mod tests {
assert_eq!(s.0, "name");
assert_eq!(s.1, 32);
#[derive(Deserialize)]
struct T(Test1);
let s: T = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
assert_eq!((s.0).0, "name");
assert_eq!((s.0).1, 32);
let s: Test2 =
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
assert_eq!(s.key, "name");
assert_eq!(s.value, 32);
let s: Result<(Test2,), _> =
de::Deserialize::deserialize(PathDeserializer::new(&path));
assert!(s.is_err());
let s: (String, u8) =
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
assert_eq!(s.0, "name");
@ -589,6 +602,11 @@ mod tests {
let i: Test =
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
assert_eq!(i.0, 32);
path.segments.push(("value2", PathItem::Static("32")));
let i: Result<i8, _> =
de::Deserialize::deserialize(PathDeserializer::new(&path));
assert!(i.is_err());
}
#[test]

View file

@ -9,6 +9,7 @@ use crate::{Resource, ResourcePath};
pub(super) enum PathItem {
Static(&'static str),
Segment(String),
IdxSegment(u16, u16),
}
/// Resource path match information
@ -123,6 +124,9 @@ impl<T: ResourcePath> Path<T> {
return match item.1 {
PathItem::Static(ref s) => Some(&s),
PathItem::Segment(ref s) => Some(s),
PathItem::IdxSegment(s, e) => {
Some(&self.path.path()[(s as usize)..(e as usize)])
}
};
}
}
@ -182,6 +186,9 @@ impl<'a, T: ResourcePath> Iterator for PathIter<'a, T> {
let res = match self.params.segments[idx].1 {
PathItem::Static(s) => s,
PathItem::Segment(ref s) => s.as_str(),
PathItem::IdxSegment(s, e) => {
&self.params.path.path()[(s as usize)..(e as usize)]
}
};
self.idx += 1;
return Some((&self.params.segments[idx].0, res));
@ -206,6 +213,7 @@ impl<T: ResourcePath> Index<usize> for Path<T> {
match self.segments[idx].1 {
PathItem::Static(ref s) => &s,
PathItem::Segment(ref s) => &s,
PathItem::IdxSegment(s, e) => &self.path.path()[(s as usize)..(e as usize)],
}
}
}
@ -219,3 +227,31 @@ impl<T: ResourcePath> Resource<T> for Path<T> {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_path() {
let mut p: Path<String> = Path::default();
assert_eq!(p.get_ref(), &String::new());
p.get_mut().push_str("test");
assert_eq!(p.get_ref().as_str(), "test");
let p2 = p.clone();
assert_eq!(p2.get_ref().as_str(), "test");
p.skip(2);
assert_eq!(p.get("tail").unwrap(), "st");
assert_eq!(p.get("unknown"), None);
assert_eq!(p.query("tail"), "st");
assert_eq!(p.query("unknown"), "");
assert_eq!(p.unprocessed(), "st");
p.reset();
assert_eq!(p.unprocessed(), "test");
p.segments.push(("k1", PathItem::IdxSegment(0, 2)));
assert_eq!(p.get("k1").unwrap(), "te");
}
}

View file

@ -13,7 +13,14 @@ pub(super) fn requote(val: &[u8]) -> Option<String> {
has_pct += 1;
if has_pct == 3 {
has_pct = 0;
let buf = cloned.as_mut().unwrap();
let buf = if let Some(ref mut buf) = cloned {
buf
} else {
let mut c = Vec::with_capacity(len);
c.extend_from_slice(&val[..idx - 2]);
cloned = Some(c);
cloned.as_mut().unwrap()
};
if let Some(ch) = restore_ch(pct[1], pct[2]) {
buf.push(ch);
@ -23,18 +30,16 @@ pub(super) fn requote(val: &[u8]) -> Option<String> {
}
} 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 {
if let Some(mut data) = cloned {
if has_pct > 0 {
data.extend(&pct[..has_pct]);
}
// 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) })

View file

@ -675,7 +675,11 @@ mod tests {
test_single_value!("/%2B/", "+");
test_single_value!("/%252B/", "%2B");
test_single_value!("/%2F/", "/");
test_single_value!("/test%2Ftest/", "test/test");
test_single_value!("/%252F/", "%2F");
test_single_value!("/%m/", "%m");
test_single_value!("/%mm/", "%mm");
test_single_value!("/test%mm/", "test%mm");
test_single_value!(
"/http%3A%2F%2Flocalhost%3A80%2Ffoo/",
"http://localhost:80/foo"

View file

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::mem;
use super::path::PathItem;
@ -199,6 +200,7 @@ impl Tree {
F: Fn(usize, &R) -> bool,
{
let path = resource.resource_path();
let base_skip = path.skip;
let mut segments = mem::take(&mut path.segments);
let path = resource.path();
@ -235,7 +237,15 @@ impl Tree {
.children
.iter()
.map(|x| {
x.find_inner2(path, resource, check, 1, &mut segments, insensitive)
x.find_inner2(
path,
resource,
check,
1,
&mut segments,
insensitive,
base_skip,
)
})
.filter_map(|x| x)
.next();
@ -260,9 +270,15 @@ impl Tree {
path
};
if let Some((val, skip)) =
self.find_inner2(path, resource, check, 1, &mut segments, insensitive)
{
if let Some((val, skip)) = self.find_inner2(
path,
resource,
check,
1,
&mut segments,
insensitive,
base_skip,
) {
let path = resource.resource_path();
path.segments = segments;
path.skip += skip as u16;
@ -280,6 +296,7 @@ impl Tree {
mut skip: usize,
segments: &mut Vec<(&'static str, PathItem)>,
insensitive: bool,
base_skip: u16,
) -> Option<(usize, usize)>
where
T: ResourcePath,
@ -295,6 +312,11 @@ impl Tree {
path.len()
};
let segment = T::unquote(&path[..idx]);
let quoted = if let Cow::Owned(_) = segment {
true
} else {
false
};
// check segment match
let is_match = match key[0] {
@ -318,10 +340,15 @@ impl Tree {
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()),
));
let item = if quoted {
PathItem::Segment(m.as_str().to_string())
} else {
PathItem::IdxSegment(
base_skip + (skip + m.start()) as u16,
base_skip + (skip + m.end()) as u16,
)
};
segments.push((name, item));
} else {
log::error!(
"Dynamic path match but not all segments found: {}",
@ -431,6 +458,7 @@ impl Tree {
skip,
segments,
insensitive,
base_skip,
)
})
.filter_map(|x| x)