From 76c317e0b20ca6d24109d7fefc32c5f9c9f09e23 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Apr 2019 21:19:22 -0700 Subject: [PATCH] Added support for remainder match --- router/CHANGES.txt | 4 ++ router/Cargo.toml | 4 +- router/src/resource.rs | 86 +++++++++++++++++++++++++++++++++++------- 3 files changed, 79 insertions(+), 15 deletions(-) diff --git a/router/CHANGES.txt b/router/CHANGES.txt index 8b5e4411..48532c01 100644 --- a/router/CHANGES.txt +++ b/router/CHANGES.txt @@ -1,5 +1,9 @@ # Changes +## [0.1.3] - 2019-04-22 + +* Added support for `remainder match` (i.e "/path/{tail}*") + ## [0.1.2] - 2019-04-07 * Export `Quoter` type diff --git a/router/Cargo.toml b/router/Cargo.toml index e11c5db3..e176fbff 100644 --- a/router/Cargo.toml +++ b/router/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "actix-router" -version = "0.1.2" +version = "0.1.3" authors = ["Nikolay Kim "] -description = "Path router" +description = "Path table router" keywords = ["actix"] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-net.git" diff --git a/router/src/resource.rs b/router/src/resource.rs index e2c4cd5c..ccbb3e59 100644 --- a/router/src/resource.rs +++ b/router/src/resource.rs @@ -267,8 +267,9 @@ impl ResourceDef { true } - fn parse_param(pattern: &str) -> (PatternElement, String, &str) { + fn parse_param(pattern: &str) -> (PatternElement, String, &str, bool) { const DEFAULT_PATTERN: &str = "[^/]+"; + const DEFAULT_PATTERN_TAIL: &str = ".*"; let mut params_nesting = 0usize; let close_idx = pattern .find(|c| match c { @@ -283,34 +284,54 @@ impl ResourceDef { _ => false, }) .expect("malformed dynamic segment"); - let (mut param, rem) = pattern.split_at(close_idx + 1); + let (mut param, mut rem) = pattern.split_at(close_idx + 1); param = ¶m[1..param.len() - 1]; // Remove outer brackets + let tail = rem == "*"; + let (name, pattern) = 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, DEFAULT_PATTERN), + None => ( + param, + if tail { + rem = &rem[1..]; + DEFAULT_PATTERN_TAIL + } else { + DEFAULT_PATTERN + }, + ), }; ( PatternElement::Var(name.to_string()), format!(r"(?P<{}>{})", &name, &pattern), rem, + tail, ) } fn parse( mut pattern: &str, - for_prefix: bool, + mut for_prefix: bool, ) -> (String, Vec, bool, usize) { if pattern.find('{').is_none() { - return ( - String::from(pattern), - vec![PatternElement::Str(String::from(pattern))], - false, - pattern.chars().count(), - ); - }; + return if pattern.ends_with('*') { + let path = &pattern[..pattern.len() - 1]; + let re = String::from("^") + path + "(.*)"; + (re, vec![PatternElement::Str(String::from(path))], true, 0) + } else { + ( + String::from(pattern), + vec![PatternElement::Str(String::from(pattern))], + false, + pattern.chars().count(), + ) + }; + } let mut elems = Vec::new(); let mut re = String::from("^"); @@ -320,7 +341,11 @@ impl ResourceDef { let (prefix, rem) = pattern.split_at(idx); elems.push(PatternElement::Str(String::from(prefix))); re.push_str(&escape(prefix)); - let (param_pattern, re_part, rem) = Self::parse_param(rem); + let (param_pattern, re_part, rem, tail) = Self::parse_param(rem); + if tail { + for_prefix = true; + } + elems.push(param_pattern); re.push_str(&re_part); pattern = rem; @@ -340,7 +365,6 @@ impl ResourceDef { if !for_prefix { re.push_str("$"); } - (re, elems, true, pattern.chars().count()) } } @@ -448,6 +472,42 @@ mod tests { assert_eq!(path.get("id").unwrap(), "012345"); } + #[test] + fn test_parse_tail() { + let re = ResourceDef::new("/user/-{id}*"); + + 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/-2345"); + assert!(re.match_path(&mut path)); + assert_eq!(path.get("id").unwrap(), "2345"); + + let mut path = Path::new("/user/-2345/"); + assert!(re.match_path(&mut path)); + assert_eq!(path.get("id").unwrap(), "2345/"); + + let mut path = Path::new("/user/-2345/sdg"); + assert!(re.match_path(&mut path)); + assert_eq!(path.get("id").unwrap(), "2345/sdg"); + } + + #[test] + fn test_static_tail() { + let re = ResourceDef::new("/user*"); + 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 re = ResourceDef::new("/user/*"); + 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")); + } + #[test] fn test_parse_urlencoded_param() { let re = ResourceDef::new("/user/{id}/test");