From 13c18b8a51050028e8d314dd93fcafd651dbd0de Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 14 Jul 2021 10:37:49 +0100 Subject: [PATCH 1/4] Update CHANGES.md --- actix-rt/CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-rt/CHANGES.md b/actix-rt/CHANGES.md index 459d91a7..483999ce 100644 --- a/actix-rt/CHANGES.md +++ b/actix-rt/CHANGES.md @@ -8,7 +8,7 @@ `Ready` object in ok variant. [#293] * Breakage is acceptable since `ActixStream` was not intended to be public. -[#293] https://github.com/actix/actix-net/pull/293 +[#293]: https://github.com/actix/actix-net/pull/293 ## 2.1.0 - 2021-02-24 From dcea0091580e2c9126e8ae0cff560d6d98f3d40b Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Thu, 15 Jul 2021 17:09:01 +0300 Subject: [PATCH 2/4] `ResourceDef`: cleanup (#365) --- actix-router/src/resource.rs | 364 +++++++++-------------------------- 1 file changed, 95 insertions(+), 269 deletions(-) diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index d0caaebf..40957540 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -24,12 +24,12 @@ pub struct ResourceDef { tp: PatternType, name: String, pattern: String, - elements: Vec, + elements: Option>, } #[derive(Debug, Clone, PartialEq)] enum PatternElement { - Str(String), + Const(String), Var(String), } @@ -38,8 +38,8 @@ enum PatternElement { enum PatternType { Static(String), Prefix(String), - Dynamic(Regex, Vec<&'static str>, usize), - DynamicSet(RegexSet, Vec<(Regex, Vec<&'static str>, usize)>), + Dynamic(Regex, Vec<&'static str>), + DynamicSet(RegexSet, Vec<(Regex, Vec<&'static str>)>), } impl ResourceDef { @@ -56,7 +56,7 @@ impl ResourceDef { let mut re_set = Vec::new(); for path in set { - let (pattern, _, _, len) = ResourceDef::parse(&path, false); + let (pattern, _, _) = ResourceDef::parse(&path, false); let re = match Regex::new(&pattern) { Ok(re) => re, @@ -69,14 +69,14 @@ impl ResourceDef { name.map(|name| Box::leak(Box::new(name.to_owned())).as_str()) }) .collect(); - data.push((re, names, len)); + data.push((re, names)); re_set.push(pattern); } ResourceDef { id: 0, tp: PatternType::DynamicSet(RegexSet::new(re_set).unwrap(), data), - elements: Vec::new(), + elements: None, name: String::new(), pattern: "".to_owned(), } @@ -116,7 +116,7 @@ impl ResourceDef { /// 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 (pattern, elements, is_dynamic, len) = ResourceDef::parse(&path, for_prefix); + let (pattern, elements, is_dynamic) = ResourceDef::parse(&path, for_prefix); let tp = if is_dynamic { let re = match Regex::new(&pattern) { @@ -130,7 +130,7 @@ impl ResourceDef { name.map(|name| Box::leak(Box::new(name.to_owned())).as_str()) }) .collect(); - PatternType::Dynamic(re, names, len) + PatternType::Dynamic(re, names) } else if for_prefix { PatternType::Prefix(pattern) } else { @@ -139,7 +139,7 @@ impl ResourceDef { ResourceDef { tp, - elements, + elements: Some(elements), id: 0, name: String::new(), pattern: path, @@ -167,7 +167,7 @@ impl ResourceDef { match self.tp { PatternType::Static(ref s) => s == path, PatternType::Prefix(ref s) => path.starts_with(s), - PatternType::Dynamic(ref re, _, _) => re.is_match(path), + PatternType::Dynamic(ref re, _) => re.is_match(path), PatternType::DynamicSet(ref re, _) => re.is_match(path), } } @@ -185,25 +185,7 @@ impl ResourceDef { None } } - PatternType::Dynamic(ref re, _, len) => { - if let Some(captures) = re.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 - } - } + PatternType::Dynamic(ref re, _) => re.find(path).map(|m| m.end()), PatternType::Prefix(ref s) => { let len = if path == s { s.len() @@ -221,121 +203,16 @@ impl ResourceDef { Some(min(p_len, 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 - } + let idx = re.matches(path).into_iter().next()?; + let (ref pattern, _) = params[idx]; + pattern.find(path).map(|m| m.end()) } } } /// Is the given path and parameters a match against this pattern. pub fn match_path(&self, path: &mut Path) -> bool { - match self.tp { - PatternType::Static(ref s) => { - if s == path.path() { - path.skip(path.len() as u16); - true - } else { - false - } - } - PatternType::Prefix(ref s) => { - let r_path = path.path(); - let len = if s == r_path { - s.len() - } else if r_path.starts_with(s) - && (s.ends_with('/') || r_path.split_at(s.len()).1.starts_with('/')) - { - if s.ends_with('/') { - s.len() - 1 - } else { - s.len() - } - } else { - return false; - }; - let r_path_len = r_path.len(); - path.skip(min(r_path_len, len) as u16); - true - } - PatternType::Dynamic(ref re, ref names, len) => { - let mut pos = 0; - let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] = Default::default(); - - if let Some(captures) = re.captures(path.path()) { - for (no, name) in names.iter().enumerate() { - if let Some(m) = captures.name(&name) { - pos = m.end(); - segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); - } else { - log::error!( - "Dynamic path match but not all segments found: {}", - name - ); - return false; - } - } - } else { - return false; - } - for i in 0..names.len() { - path.add(names[i], mem::take(&mut segments[i])); - } - path.skip((pos + len) as u16); - true - } - PatternType::DynamicSet(ref re, ref params) => { - if let Some(idx) = re.matches(path.path()).into_iter().next() { - let (ref pattern, ref names, len) = params[idx]; - let mut pos = 0; - let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] = Default::default(); - - if let Some(captures) = pattern.captures(path.path()) { - for (no, name) in names.iter().enumerate() { - if let Some(m) = captures.name(&name) { - pos = m.end(); - segments[no] = - PathItem::Segment(m.start() as u16, m.end() as u16); - } else { - log::error!( - "Dynamic path match but not all segments found: {}", - name - ); - return false; - } - } - } else { - return false; - } - for i in 0..names.len() { - path.add(names[i], mem::take(&mut segments[i])); - } - path.skip((pos + len) as u16); - true - } else { - false - } - } - } + self.match_path_checked(path, &|_, _| true, &Some(())) } /// Is the given path and parameters a match against this pattern? @@ -350,19 +227,19 @@ impl ResourceDef { R: Resource, F: Fn(&R, &Option) -> bool, { - match self.tp { + let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] = Default::default(); + let path = res.resource_path(); + + let (matched_len, matched_vars) = match self.tp { PatternType::Static(ref s) => { - if s == res.resource_path().path() && check(res, user_data) { - let path = res.resource_path(); - path.skip(path.len() as u16); - true - } else { - false + if s != path.path() { + return false; } + (path.len(), None) } PatternType::Prefix(ref s) => { let len = { - let r_path = res.resource_path().path(); + let r_path = path.path(); if s == r_path { s.len() } else if r_path.starts_with(s) @@ -377,85 +254,80 @@ impl ResourceDef { return false; } }; - if !check(res, user_data) { - return false; - } - let path = res.resource_path(); - path.skip(min(path.path().len(), len) as u16); - true + (min(path.len(), len), None) } - PatternType::Dynamic(ref re, ref names, len) => { - let mut pos = 0; - let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] = Default::default(); - - if let Some(captures) = re.captures(res.resource_path().path()) { - for (no, name) in names.iter().enumerate() { - if let Some(m) = captures.name(&name) { - pos = m.end(); - segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); - } else { - log::error!( - "Dynamic path match but not all segments found: {}", - name - ); - return false; - } + PatternType::Dynamic(ref re, ref names) => { + let captures = match re.captures(path.path()) { + Some(captures) => captures, + _ => return false, + }; + for (no, name) in names.iter().enumerate() { + if let Some(m) = captures.name(&name) { + segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); + } else { + log::error!("Dynamic path match but not all segments found: {}", name); + return false; } - } else { - return false; } - - if !check(res, user_data) { - return false; - } - - let path = res.resource_path(); - for i in 0..names.len() { - path.add(names[i], mem::take(&mut segments[i])); - } - path.skip((pos + len) as u16); - true + (captures[0].len(), Some(names)) } PatternType::DynamicSet(ref re, ref params) => { - let path = res.resource_path().path(); - if let Some(idx) = re.matches(path).into_iter().next() { - let (ref pattern, ref names, len) = params[idx]; - let mut pos = 0; - let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] = Default::default(); - - if let Some(captures) = pattern.captures(path) { - for (no, name) in names.iter().enumerate() { - if let Some(m) = captures.name(&name) { - pos = m.end(); - segments[no] = - PathItem::Segment(m.start() as u16, m.end() as u16); - } else { - log::error!( - "Dynamic path match but not all segments found: {}", - name - ); - return false; - } - } + let path = path.path(); + let (pattern, names) = match re.matches(path).into_iter().next() { + Some(idx) => ¶ms[idx], + _ => return false, + }; + let captures = match pattern.captures(path.path()) { + Some(captures) => captures, + _ => return false, + }; + for (no, name) in names.iter().enumerate() { + if let Some(m) = captures.name(&name) { + segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); } else { + log::error!("Dynamic path match but not all segments found: {}", name); return false; } - - if !check(res, user_data) { - return false; - } - - let path = res.resource_path(); - for i in 0..names.len() { - path.add(names[i], mem::take(&mut segments[i])); - } - path.skip((pos + len) as u16); - true - } else { - false } + (captures[0].len(), Some(names)) + } + }; + + if !check(res, user_data) { + return false; + } + + // Modify `path` to skip matched part and store matched segments + let path = res.resource_path(); + if let Some(vars) = matched_vars { + for i in 0..vars.len() { + path.add(vars[i], mem::take(&mut segments[i])); } } + path.skip(matched_len as u16); + + true + } + + /// Build resource path with a closure that maps variable elements' names to values. + fn build_resource_path(&self, path: &mut String, mut vars: F) -> bool + where + F: FnMut(&str) -> Option, + I: AsRef, + { + for el in match self.elements { + Some(ref elements) => elements, + None => return false, + } { + match *el { + PatternElement::Const(ref val) => path.push_str(val), + PatternElement::Var(ref name) => match vars(name) { + Some(val) => path.push_str(val.as_ref()), + _ => return false, + }, + } + } + true } /// Build resource path from elements. Returns `true` on success. @@ -464,28 +336,7 @@ impl ResourceDef { U: Iterator, I: AsRef, { - match self.tp { - PatternType::Prefix(ref p) => path.push_str(p), - PatternType::Static(ref p) => path.push_str(p), - PatternType::Dynamic(..) => { - for el in &self.elements { - match *el { - PatternElement::Str(ref s) => path.push_str(s), - PatternElement::Var(_) => { - if let Some(val) = elements.next() { - path.push_str(val.as_ref()) - } else { - return false; - } - } - } - } - } - PatternType::DynamicSet(..) => { - return false; - } - } - true + self.build_resource_path(path, |_| elements.next()) } /// Build resource path from elements. Returns `true` on success. @@ -499,28 +350,7 @@ impl ResourceDef { V: AsRef, S: std::hash::BuildHasher, { - match self.tp { - PatternType::Prefix(ref p) => path.push_str(p), - PatternType::Static(ref p) => path.push_str(p), - PatternType::Dynamic(..) => { - for el in &self.elements { - match *el { - PatternElement::Str(ref s) => path.push_str(s), - PatternElement::Var(ref name) => { - if let Some(val) = elements.get(name) { - path.push_str(val.as_ref()) - } else { - return false; - } - } - } - } - } - PatternType::DynamicSet(..) => { - return false; - } - } - true + self.build_resource_path(path, |name| elements.get(name)) } fn parse_param(pattern: &str) -> (PatternElement, String, &str, bool) { @@ -570,20 +400,16 @@ impl ResourceDef { ) } - fn parse( - mut pattern: &str, - mut for_prefix: bool, - ) -> (String, Vec, bool, usize) { + fn parse(mut pattern: &str, mut for_prefix: bool) -> (String, Vec, bool) { if pattern.find('{').is_none() { return if let Some(path) = pattern.strip_suffix('*') { let re = format!("{}^{}(.*)", REGEX_FLAGS, path); - (re, vec![PatternElement::Str(String::from(path))], true, 0) + (re, vec![PatternElement::Const(String::from(path))], true) } else { ( String::from(pattern), - vec![PatternElement::Str(String::from(pattern))], + vec![PatternElement::Const(String::from(pattern))], false, - pattern.chars().count(), ) }; } @@ -594,7 +420,7 @@ impl ResourceDef { while let Some(idx) = pattern.find('{') { let (prefix, rem) = pattern.split_at(idx); - elements.push(PatternElement::Str(String::from(prefix))); + elements.push(PatternElement::Const(String::from(prefix))); re.push_str(&escape(prefix)); let (param_pattern, re_part, rem, tail) = Self::parse_param(rem); if tail { @@ -607,7 +433,7 @@ impl ResourceDef { dyn_elements += 1; } - elements.push(PatternElement::Str(String::from(pattern))); + elements.push(PatternElement::Const(String::from(pattern))); re.push_str(&escape(pattern)); if dyn_elements > MAX_DYNAMIC_SEGMENTS { @@ -620,7 +446,7 @@ impl ResourceDef { if !for_prefix { re.push('$'); } - (re, elements, true, pattern.chars().count()) + (re, elements, true) } } From e1317bb3a046c694f5644b1222b574d2bc609ee2 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Thu, 15 Jul 2021 17:34:49 +0300 Subject: [PATCH 3/4] `path.len()` != `path.path().len()` (#368) Co-authored-by: Rob Ede --- actix-router/CHANGES.md | 3 +++ actix-router/src/resource.rs | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 252f5da5..8eac4ea9 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +* Fix segment interpolation leaving `Path` in unintended state after matching. [#368] + +[#368]: https://github.com/actix/actix-net/pull/368 ## 0.4.0 - 2021-06-06 diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index 40957540..dad2fd89 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -235,7 +235,7 @@ impl ResourceDef { if s != path.path() { return false; } - (path.len(), None) + (path.path().len(), None) } PatternType::Prefix(ref s) => { let len = { @@ -254,7 +254,7 @@ impl ResourceDef { return false; } }; - (min(path.len(), len), None) + (min(path.path().len(), len), None) } PatternType::Dynamic(ref re, ref names) => { let captures = match re.captures(path.path()) { @@ -500,6 +500,10 @@ mod tests { assert!(!re.is_match("/name/")); assert!(!re.is_match("/name~")); + let mut path = Path::new("/name"); + assert!(re.match_path(&mut path)); + assert_eq!(path.unprocessed(), ""); + assert_eq!(re.is_prefix_match("/name"), Some(5)); assert_eq!(re.is_prefix_match("/name1"), None); assert_eq!(re.is_prefix_match("/name/"), None); @@ -513,6 +517,10 @@ mod tests { let re = ResourceDef::new("/user/profile"); assert!(re.is_match("/user/profile")); assert!(!re.is_match("/user/profile/profile")); + + let mut path = Path::new("/user/profile"); + assert!(re.match_path(&mut path)); + assert_eq!(path.unprocessed(), ""); } #[test] @@ -526,10 +534,12 @@ mod tests { let mut path = Path::new("/user/profile"); assert!(re.match_path(&mut path)); assert_eq!(path.get("id").unwrap(), "profile"); + assert_eq!(path.unprocessed(), ""); let mut path = Path::new("/user/1245125"); assert!(re.match_path(&mut path)); assert_eq!(path.get("id").unwrap(), "1245125"); + assert_eq!(path.unprocessed(), ""); let re = ResourceDef::new("/v{version}/resource/{id}"); assert!(re.is_match("/v1/resource/320120")); @@ -540,6 +550,7 @@ mod tests { assert!(re.match_path(&mut path)); assert_eq!(path.get("version").unwrap(), "151"); assert_eq!(path.get("id").unwrap(), "adage32"); + assert_eq!(path.unprocessed(), ""); let re = ResourceDef::new("/{id:[[:digit:]]{6}}"); assert!(re.is_match("/012345")); @@ -550,6 +561,7 @@ mod tests { let mut path = Path::new("/012345"); assert!(re.match_path(&mut path)); assert_eq!(path.get("id").unwrap(), "012345"); + assert_eq!(path.unprocessed(), ""); } #[allow(clippy::cognitive_complexity)] @@ -568,10 +580,12 @@ mod tests { let mut path = Path::new("/user/profile"); assert!(re.match_path(&mut path)); assert_eq!(path.get("id").unwrap(), "profile"); + assert_eq!(path.unprocessed(), ""); let mut path = Path::new("/user/1245125"); assert!(re.match_path(&mut path)); assert_eq!(path.get("id").unwrap(), "1245125"); + assert_eq!(path.unprocessed(), ""); assert!(re.is_match("/v1/resource/320120")); assert!(!re.is_match("/v/resource/1")); @@ -704,6 +718,14 @@ mod tests { assert!(re.is_match("/name1")); assert!(re.is_match("/name~")); + let mut path = Path::new("/name"); + assert!(re.match_path(&mut path)); + assert_eq!(path.unprocessed(), ""); + + let mut path = Path::new("/name/test"); + assert!(re.match_path(&mut path)); + assert_eq!(path.unprocessed(), "/test"); + assert_eq!(re.is_prefix_match("/name"), Some(5)); assert_eq!(re.is_prefix_match("/name/"), Some(5)); assert_eq!(re.is_prefix_match("/name/test/test"), Some(5)); @@ -719,6 +741,10 @@ mod tests { assert!(re.is_match("/name/")); assert!(re.is_match("/name/gs")); assert!(!re.is_match("/name")); + + let mut path = Path::new("/name/gs"); + assert!(re.match_path(&mut path)); + assert_eq!(path.unprocessed(), "/gs"); } #[test] @@ -736,11 +762,13 @@ mod tests { assert!(re.match_path(&mut path)); assert_eq!(&path["name"], "test2"); assert_eq!(&path[0], "test2"); + assert_eq!(path.unprocessed(), ""); let mut path = Path::new("/test2/subpath1/subpath2/index.html"); assert!(re.match_path(&mut path)); assert_eq!(&path["name"], "test2"); assert_eq!(&path[0], "test2"); + assert_eq!(path.unprocessed(), "subpath1/subpath2/index.html"); } #[test] From 5b1ff30dd9a91cc60bd44b9c02e74c26ab5a42dd Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Fri, 16 Jul 2021 20:17:00 +0300 Subject: [PATCH 4/4] router: fix multi-pattern and path tail matches (#366) --- actix-router/CHANGES.md | 3 + actix-router/src/resource.rs | 113 ++++++++++++++++++----------------- 2 files changed, 60 insertions(+), 56 deletions(-) diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index 8eac4ea9..bc22ee7e 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -2,8 +2,11 @@ ## Unreleased - 2021-xx-xx * Fix segment interpolation leaving `Path` in unintended state after matching. [#368] +* Path tail pattern now works as expected after a dynamic segment (e.g. `/user/{uid}/*`). [#366] +* Fixed a bug where, in multi-patterns, static patterns are interpreted as regex. [#366] [#368]: https://github.com/actix/actix-net/pull/368 +[#366]: https://github.com/actix/actix-net/pull/366 ## 0.4.0 - 2021-06-06 diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index dad2fd89..65cb5cf1 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -48,29 +48,19 @@ impl ResourceDef { /// Panics if path pattern is malformed. pub fn new(path: T) -> Self { if path.is_single() { - let patterns = path.patterns(); - ResourceDef::with_prefix(&patterns[0], false) + ResourceDef::from_single_pattern(&path.patterns()[0], false) } else { - let set = path.patterns(); let mut data = Vec::new(); let mut re_set = Vec::new(); - for path in set { - let (pattern, _, _) = 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| Box::leak(Box::new(name.to_owned())).as_str()) - }) - .collect(); - data.push((re, names)); - re_set.push(pattern); + for pattern in path.patterns() { + match ResourceDef::parse(&pattern, false, true) { + (PatternType::Dynamic(re, names), _) => { + re_set.push(re.as_str().to_owned()); + data.push((re, names)); + } + _ => unreachable!(), + } } ResourceDef { @@ -89,7 +79,7 @@ impl ResourceDef { /// /// Panics if path regex pattern is malformed. pub fn prefix(path: &str) -> Self { - ResourceDef::with_prefix(path, true) + ResourceDef::from_single_pattern(path, true) } /// Parse path pattern and create new `Pattern` instance. @@ -100,7 +90,7 @@ impl ResourceDef { /// /// Panics if path regex pattern is malformed. pub fn root_prefix(path: &str) -> Self { - ResourceDef::with_prefix(&insert_slash(path), true) + ResourceDef::from_single_pattern(&insert_slash(path), true) } /// Resource id @@ -113,36 +103,17 @@ impl ResourceDef { 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 (pattern, elements, is_dynamic) = ResourceDef::parse(&path, for_prefix); - - let tp = if is_dynamic { - 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 = re - .capture_names() - .filter_map(|name| { - name.map(|name| Box::leak(Box::new(name.to_owned())).as_str()) - }) - .collect(); - PatternType::Dynamic(re, names) - } else if for_prefix { - PatternType::Prefix(pattern) - } else { - PatternType::Static(pattern) - }; + /// Parse path pattern and create a new instance + fn from_single_pattern(pattern: &str, for_prefix: bool) -> Self { + let pattern = pattern.to_owned(); + let (tp, elements) = ResourceDef::parse(&pattern, for_prefix, false); ResourceDef { tp, + pattern, elements: Some(elements), id: 0, name: String::new(), - pattern: path, } } @@ -400,20 +371,21 @@ impl ResourceDef { ) } - fn parse(mut pattern: &str, mut for_prefix: bool) -> (String, Vec, bool) { - if pattern.find('{').is_none() { - return if let Some(path) = pattern.strip_suffix('*') { - let re = format!("{}^{}(.*)", REGEX_FLAGS, path); - (re, vec![PatternElement::Const(String::from(path))], true) + fn parse( + mut pattern: &str, + mut for_prefix: bool, + force_dynamic: bool, + ) -> (PatternType, Vec) { + if !force_dynamic && pattern.find('{').is_none() && !pattern.ends_with('*') { + let tp = if for_prefix { + PatternType::Prefix(String::from(pattern)) } else { - ( - String::from(pattern), - vec![PatternElement::Const(String::from(pattern))], - false, - ) + PatternType::Static(String::from(pattern)) }; + return (tp, vec![PatternElement::Const(String::from(pattern))]); } + let pattern_orig = pattern; let mut elements = Vec::new(); let mut re = format!("{}^", REGEX_FLAGS); let mut dyn_elements = 0; @@ -433,6 +405,13 @@ impl ResourceDef { dyn_elements += 1; } + if let Some(path) = pattern.strip_suffix('*') { + elements.push(PatternElement::Const(String::from(path))); + re.push_str(&escape(path)); + re.push_str("(.*)"); + pattern = ""; + } + elements.push(PatternElement::Const(String::from(pattern))); re.push_str(&escape(pattern)); @@ -446,7 +425,18 @@ impl ResourceDef { if !for_prefix { re.push('$'); } - (re, elements, true) + + let re = match Regex::new(&re) { + Ok(re) => re, + Err(err) => panic!("Wrong path pattern: \"{}\" {}", pattern_orig, err), + }; + // actix creates one router per thread + let names = re + .capture_names() + .filter_map(|name| name.map(|name| Box::leak(Box::new(name.to_owned())).as_str())) + .collect(); + + (PatternType::Dynamic(re, names), elements) } } @@ -571,6 +561,7 @@ mod tests { "/user/{id}", "/v{version}/resource/{id}", "/{id:[[:digit:]]{6}}", + "/static", ]); assert!(re.is_match("/user/profile")); assert!(re.is_match("/user/2345")); @@ -601,6 +592,10 @@ mod tests { assert!(!re.is_match("/01234567")); assert!(!re.is_match("/XXXXXX")); + assert!(re.is_match("/static")); + assert!(!re.is_match("/a/static")); + assert!(!re.is_match("/static/a")); + let mut path = Path::new("/012345"); assert!(re.match_path(&mut path)); assert_eq!(path.get("id").unwrap(), "012345"); @@ -660,6 +655,12 @@ mod tests { assert!(re.is_match("/user/2345")); assert!(re.is_match("/user/2345/")); assert!(re.is_match("/user/2345/sdg")); + + let re = ResourceDef::new("/user/{id}/*"); + assert!(!re.is_match("/user/2345")); + let mut path = Path::new("/user/2345/sdg"); + assert!(re.match_path(&mut path)); + assert_eq!(path.get("id").unwrap(), "2345"); } #[test]