1
0
mirror of https://github.com/fafhrd91/actix-net synced 2025-01-19 03:44:40 +01:00

allow specify set of resource patters

This commit is contained in:
Nikolay Kim 2019-12-25 15:10:01 +04:00
parent b599bc4a0c
commit 0fe8038d23
3 changed files with 228 additions and 44 deletions

View File

@ -1,5 +1,9 @@
# Changes # Changes
## [0.2.1] - 2019-12-xx
* Add multi-pattern resources
## [0.2.0] - 2019-12-07 ## [0.2.0] - 2019-12-07
* Update http to 0.2 * Update http to 0.2

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-router" name = "actix-router"
version = "0.2.0" version = "0.2.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Path router" description = "Path router"
keywords = ["actix"] keywords = ["actix"]
@ -8,9 +8,7 @@ homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git" repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-router/" documentation = "https://docs.rs/actix-router/"
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018" edition = "2018"
workspace = ".."
[lib] [lib]
name = "actix_router" name = "actix_router"
@ -21,8 +19,8 @@ default = ["http"]
[dependencies] [dependencies]
regex = "1.3.1" regex = "1.3.1"
serde = "1.0.80" serde = "1.0.104"
bytestring = "0.1.0" bytestring = "0.1.2"
log = "0.4.8" log = "0.4.8"
http = { version="0.2.0", optional=true } http = { version="0.2.0", optional=true }

View File

@ -2,7 +2,7 @@ use std::cmp::min;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::rc::Rc; use std::rc::Rc;
use regex::{escape, Regex}; use regex::{escape, Regex, RegexSet};
use crate::path::{Path, PathItem}; use crate::path::{Path, PathItem};
use crate::{Resource, ResourcePath}; use crate::{Resource, ResourcePath};
@ -32,21 +32,56 @@ enum PatternType {
Static(String), Static(String),
Prefix(String), Prefix(String),
Dynamic(Regex, Vec<Rc<String>>, usize), Dynamic(Regex, Vec<Rc<String>>, usize),
DynamicSet(RegexSet, Vec<(Regex, Vec<Rc<String>>, usize)>),
} }
impl ResourceDef { impl ResourceDef {
/// Parse path pattern and create new `Pattern` instance. /// Parse path pattern and create new `Pattern` instance.
/// ///
/// Panics if path pattern is wrong. /// Panics if path pattern is malformed.
pub fn new(path: &str) -> Self { pub fn new(path: &str) -> Self {
ResourceDef::with_prefix(path, false) ResourceDef::with_prefix(path, false)
} }
/// Parse path pattern and create new `Pattern` instance.
///
/// Panics if any of paths pattern is malformed. Every set element
/// must contain same set of capture elements.
pub fn new_set<T: Iterator<Item = U>, U: AsRef<str>>(set: T) -> Self {
let mut data = Vec::new();
let mut re_set = Vec::new();
for item in set {
let path = item.as_ref().to_owned();
let (pattern, _, _, len) = ResourceDef::parse(&path, false);
let re = match Regex::new(&pattern) {
Ok(re) => re,
Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err),
};
// actix creates one router per thread
let names: Vec<_> = re
.capture_names()
.filter_map(|name| name.map(|name| Rc::new(name.to_owned())))
.collect();
data.push((re, names, len));
re_set.push(pattern);
}
ResourceDef {
id: 0,
tp: PatternType::DynamicSet(RegexSet::new(re_set).unwrap(), data),
elements: Vec::new(),
name: String::new(),
pattern: "".to_owned(),
}
}
/// Parse path pattern and create new `Pattern` instance. /// Parse path pattern and create new `Pattern` instance.
/// ///
/// Use `prefix` type instead of `static`. /// Use `prefix` type instead of `static`.
/// ///
/// Panics if path regex pattern is wrong. /// Panics if path regex pattern is malformed.
pub fn prefix(path: &str) -> Self { pub fn prefix(path: &str) -> Self {
ResourceDef::with_prefix(path, true) ResourceDef::with_prefix(path, true)
} }
@ -57,7 +92,7 @@ impl ResourceDef {
/// ///
/// Use `prefix` type instead of `static`. /// Use `prefix` type instead of `static`.
/// ///
/// Panics if path regex pattern is wrong. /// Panics if path regex pattern is malformed.
pub fn root_prefix(path: &str) -> Self { pub fn root_prefix(path: &str) -> Self {
ResourceDef::with_prefix(&insert_slash(path), true) ResourceDef::with_prefix(&insert_slash(path), true)
} }
@ -123,8 +158,9 @@ impl ResourceDef {
pub fn is_match(&self, path: &str) -> bool { pub fn is_match(&self, path: &str) -> bool {
match self.tp { match self.tp {
PatternType::Static(ref s) => s == path, PatternType::Static(ref s) => s == path,
PatternType::Dynamic(ref re, _, _) => re.is_match(path),
PatternType::Prefix(ref s) => path.starts_with(s), PatternType::Prefix(ref s) => path.starts_with(s),
PatternType::Dynamic(ref re, _, _) => re.is_match(path),
PatternType::DynamicSet(ref re, _) => re.is_match(path),
} }
} }
@ -176,6 +212,30 @@ impl ResourceDef {
}; };
Some(min(plen, len)) Some(min(plen, len))
} }
PatternType::DynamicSet(ref re, ref params) => {
if let Some(idx) = re.matches(path).into_iter().next() {
let (ref pattern, _, len) = params[idx];
if let Some(captures) = pattern.captures(path) {
let mut pos = 0;
let mut passed = false;
for capture in captures.iter() {
if let Some(ref m) = capture {
if !passed {
passed = true;
continue;
}
pos = m.end();
}
}
Some(pos + len)
} else {
None
}
} else {
None
}
}
} }
} }
@ -190,6 +250,25 @@ impl ResourceDef {
false false
} }
} }
PatternType::Prefix(ref s) => {
let rpath = path.path();
let len = if s == rpath {
s.len()
} else if rpath.starts_with(s)
&& (s.ends_with('/') || rpath.split_at(s.len()).1.starts_with('/'))
{
if s.ends_with('/') {
s.len() - 1
} else {
s.len()
}
} else {
return false;
};
let rpath_len = rpath.len();
path.skip(min(rpath_len, len) as u16);
true
}
PatternType::Dynamic(ref re, ref names, len) => { PatternType::Dynamic(ref re, ref names, len) => {
let mut idx = 0; let mut idx = 0;
let mut pos = 0; let mut pos = 0;
@ -219,24 +298,40 @@ impl ResourceDef {
path.skip((pos + len) as u16); path.skip((pos + len) as u16);
true true
} }
PatternType::Prefix(ref s) => { PatternType::DynamicSet(ref re, ref params) => {
let rpath = path.path(); if let Some(idx) = re.matches(path.path()).into_iter().next() {
let len = if s == rpath { let (ref pattern, ref names, len) = params[idx];
s.len() let mut idx = 0;
} else if rpath.starts_with(s) let mut pos = 0;
&& (s.ends_with('/') || rpath.split_at(s.len()).1.starts_with('/')) let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] =
{ [PathItem::Static(""); MAX_DYNAMIC_SEGMENTS];
if s.ends_with('/') {
s.len() - 1 if let Some(captures) = pattern.captures(path.path()) {
for (no, name) in names.iter().enumerate() {
if let Some(m) = captures.name(&name) {
idx += 1;
pos = m.end();
segments[no] =
PathItem::Segment(m.start() as u16, m.end() as u16);
} else { } else {
s.len() log::error!(
"Dynamic path match but not all segments found: {}",
name
);
return false;
}
} }
} else { } else {
return false; return false;
}; }
let rpath_len = rpath.len(); for idx in 0..idx {
path.skip(min(rpath_len, len) as u16); path.add(names[idx].clone(), segments[idx]);
}
path.skip((pos + len) as u16);
true true
} else {
false
}
} }
} }
} }
@ -263,6 +358,30 @@ impl ResourceDef {
false false
} }
} }
PatternType::Prefix(ref s) => {
let len = {
let rpath = res.resource_path().path();
if s == rpath {
s.len()
} else if rpath.starts_with(s)
&& (s.ends_with('/') || rpath.split_at(s.len()).1.starts_with('/'))
{
if s.ends_with('/') {
s.len() - 1
} else {
s.len()
}
} else {
return false;
}
};
if !check(res, user_data) {
return false;
}
let path = res.resource_path();
path.skip(min(path.path().len(), len) as u16);
true
}
PatternType::Dynamic(ref re, ref names, len) => { PatternType::Dynamic(ref re, ref names, len) => {
let mut idx = 0; let mut idx = 0;
let mut pos = 0; let mut pos = 0;
@ -298,29 +417,47 @@ impl ResourceDef {
path.skip((pos + len) as u16); path.skip((pos + len) as u16);
true true
} }
PatternType::Prefix(ref s) => { PatternType::DynamicSet(ref re, ref params) => {
let len = { let path = res.resource_path().path();
let rpath = res.resource_path().path(); if let Some(idx) = re.matches(path).into_iter().next() {
if s == rpath { let (ref pattern, ref names, len) = params[idx];
s.len() let mut idx = 0;
} else if rpath.starts_with(s) let mut pos = 0;
&& (s.ends_with('/') || rpath.split_at(s.len()).1.starts_with('/')) let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] =
{ [PathItem::Static(""); MAX_DYNAMIC_SEGMENTS];
if s.ends_with('/') {
s.len() - 1 if let Some(captures) = pattern.captures(path) {
for (no, name) in names.iter().enumerate() {
if let Some(m) = captures.name(&name) {
idx += 1;
pos = m.end();
segments[no] =
PathItem::Segment(m.start() as u16, m.end() as u16);
} else { } else {
s.len() log::error!(
"Dynamic path match but not all segments found: {}",
name
);
return false;
}
} }
} else { } else {
return false; return false;
} }
};
if !check(res, user_data) { if !check(res, user_data) {
return false; return false;
} }
let path = res.resource_path(); let path = res.resource_path();
path.skip(min(path.path().len(), len) as u16); for idx in 0..idx {
path.add(names[idx].clone(), segments[idx]);
}
path.skip((pos + len) as u16);
true true
} else {
false
}
} }
} }
} }
@ -348,7 +485,10 @@ impl ResourceDef {
} }
} }
} }
}; PatternType::DynamicSet(..) => {
return false;
}
}
true true
} }
@ -558,6 +698,48 @@ mod tests {
assert_eq!(path.get("id").unwrap(), "012345"); assert_eq!(path.get("id").unwrap(), "012345");
} }
#[test]
fn test_dynamic_set() {
let re = ResourceDef::new_set(
vec![
"/user/{id}",
"/v{version}/resource/{id}",
"/{id:[[:digit:]]{6}}",
]
.iter(),
);
assert!(re.is_match("/user/profile"));
assert!(re.is_match("/user/2345"));
assert!(!re.is_match("/user/2345/"));
assert!(!re.is_match("/user/2345/sdg"));
let mut path = Path::new("/user/profile");
assert!(re.match_path(&mut path));
assert_eq!(path.get("id").unwrap(), "profile");
let mut path = Path::new("/user/1245125");
assert!(re.match_path(&mut path));
assert_eq!(path.get("id").unwrap(), "1245125");
assert!(re.is_match("/v1/resource/320120"));
assert!(!re.is_match("/v/resource/1"));
assert!(!re.is_match("/resource"));
let mut path = Path::new("/v151/resource/adahg32");
assert!(re.match_path(&mut path));
assert_eq!(path.get("version").unwrap(), "151");
assert_eq!(path.get("id").unwrap(), "adahg32");
assert!(re.is_match("/012345"));
assert!(!re.is_match("/012"));
assert!(!re.is_match("/01234567"));
assert!(!re.is_match("/XXXXXX"));
let mut path = Path::new("/012345");
assert!(re.match_path(&mut path));
assert_eq!(path.get("id").unwrap(), "012345");
}
#[test] #[test]
fn test_parse_tail() { fn test_parse_tail() {
let re = ResourceDef::new("/user/-{id}*"); let re = ResourceDef::new("/user/-{id}*");