feat: Add improvements in resource path resolve

This commit is contained in:
manuelgdlvh 2025-05-14 20:26:28 +02:00
parent 072fdc182d
commit ffa95b7f60
7 changed files with 254 additions and 189 deletions

View File

@ -662,13 +662,13 @@ mod tests {
let rdef = ResourceDef::new("/{key}"); let rdef = ResourceDef::new("/{key}");
let mut path = Path::new("/%25"); let mut path = Path::new("/%25");
rdef.capture_match_info(&mut path); rdef.resolve_resource_if_matches(&mut path);
let de = PathDeserializer::new(&path); let de = PathDeserializer::new(&path);
let segment: String = serde::Deserialize::deserialize(de).unwrap(); let segment: String = serde::Deserialize::deserialize(de).unwrap();
assert_eq!(segment, "%"); assert_eq!(segment, "%");
let mut path = Path::new("/%2F"); let mut path = Path::new("/%2F");
rdef.capture_match_info(&mut path); rdef.resolve_resource_if_matches(&mut path);
let de = PathDeserializer::new(&path); let de = PathDeserializer::new(&path);
let segment: String = serde::Deserialize::deserialize(de).unwrap(); let segment: String = serde::Deserialize::deserialize(de).unwrap();
assert_eq!(segment, "/") assert_eq!(segment, "/")
@ -679,7 +679,7 @@ mod tests {
let rdef = ResourceDef::new("/{key}/{value}"); let rdef = ResourceDef::new("/{key}/{value}");
let mut path = Path::new("/%30%25/%30%2F"); let mut path = Path::new("/%30%25/%30%2F");
rdef.capture_match_info(&mut path); rdef.resolve_resource_if_matches(&mut path);
let de = PathDeserializer::new(&path); let de = PathDeserializer::new(&path);
let segment: (String, String) = serde::Deserialize::deserialize(de).unwrap(); let segment: (String, String) = serde::Deserialize::deserialize(de).unwrap();
assert_eq!(segment.0, "0%"); assert_eq!(segment.0, "0%");
@ -697,7 +697,7 @@ mod tests {
let rdef = ResourceDef::new("/{key}/{value}"); let rdef = ResourceDef::new("/{key}/{value}");
let mut path = Path::new("/%25/%2F"); let mut path = Path::new("/%25/%2F");
rdef.capture_match_info(&mut path); rdef.resolve_resource_if_matches(&mut path);
let de = PathDeserializer::new(&path); let de = PathDeserializer::new(&path);
let vals: Vals = serde::Deserialize::deserialize(de).unwrap(); let vals: Vals = serde::Deserialize::deserialize(de).unwrap();
assert_eq!(vals.key, "%"); assert_eq!(vals.key, "%");
@ -714,7 +714,7 @@ mod tests {
let rdef = ResourceDef::new("/{val}"); let rdef = ResourceDef::new("/{val}");
let mut path = Path::new("/X"); let mut path = Path::new("/X");
rdef.capture_match_info(&mut path); rdef.resolve_resource_if_matches(&mut path);
let de = PathDeserializer::new(&path); let de = PathDeserializer::new(&path);
let params: Params<'_> = serde::Deserialize::deserialize(de).unwrap(); let params: Params<'_> = serde::Deserialize::deserialize(de).unwrap();
assert_eq!(params.val, "X"); assert_eq!(params.val, "X");
@ -723,7 +723,7 @@ mod tests {
assert_eq!(params, "X"); assert_eq!(params, "X");
let mut path = Path::new("/%2F"); let mut path = Path::new("/%2F");
rdef.capture_match_info(&mut path); rdef.resolve_resource_if_matches(&mut path);
let de = PathDeserializer::new(&path); let de = PathDeserializer::new(&path);
assert!(<Params<'_> as serde::Deserialize>::deserialize(de).is_err()); assert!(<Params<'_> as serde::Deserialize>::deserialize(de).is_err());
let de = PathDeserializer::new(&path); let de = PathDeserializer::new(&path);

View File

@ -8,9 +8,9 @@ use std::{
use tracing::error; use tracing::error;
use crate::{ use crate::{
IntoPatterns,
path::PathItem, path::PathItem,
regex_set::{escape, Regex, RegexSet}, Patterns, regex_set::{escape, Regex, RegexSet}, Resource, ResourcePath,
IntoPatterns, Patterns, Resource, ResourcePath,
}; };
const MAX_DYNAMIC_SEGMENTS: usize = 16; const MAX_DYNAMIC_SEGMENTS: usize = 16;
@ -96,7 +96,7 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!resource.is_match("/user/")); /// assert!(!resource.is_match("/user/"));
/// ///
/// let mut path = Path::new("/user/123"); /// let mut path = Path::new("/user/123");
/// resource.capture_match_info(&mut path); /// resource.resolve_resource_if_matches(&mut path);
/// assert_eq!(path.get("id").unwrap(), "123"); /// assert_eq!(path.get("id").unwrap(), "123");
/// ``` /// ```
/// ///
@ -171,7 +171,7 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(resource.is_match("/blob/HEAD/README.md")); /// assert!(resource.is_match("/blob/HEAD/README.md"));
/// ///
/// let mut path = Path::new("/blob/main/LICENSE"); /// let mut path = Path::new("/blob/main/LICENSE");
/// resource.capture_match_info(&mut path); /// resource.resolve_resource_if_matches(&mut path);
/// assert_eq!(path.get("tail").unwrap(), "main/LICENSE"); /// assert_eq!(path.get("tail").unwrap(), "main/LICENSE");
/// ``` /// ```
/// ///
@ -249,6 +249,17 @@ enum PatternType {
DynamicSet(RegexSet, Vec<(Regex, Vec<&'static str>)>), DynamicSet(RegexSet, Vec<(Regex, Vec<&'static str>)>),
} }
pub enum ResourceMatchInfo<'a> {
Static {
matched_len: u16,
},
Dynamic {
matched_len: u16,
matched_vars: &'a [&'static str],
segments: [PathItem; MAX_DYNAMIC_SEGMENTS],
},
}
impl ResourceDef { impl ResourceDef {
/// Constructs a new resource definition from patterns. /// Constructs a new resource definition from patterns.
/// ///
@ -623,18 +634,24 @@ impl ResourceDef {
/// ///
/// let resource = ResourceDef::prefix("/user/{id}"); /// let resource = ResourceDef::prefix("/user/{id}");
/// let mut path = Path::new("/user/123/stars"); /// let mut path = Path::new("/user/123/stars");
/// assert!(resource.capture_match_info(&mut path)); /// assert!(resource.resolve_resource_if_matches(&mut path));
/// assert_eq!(path.get("id").unwrap(), "123"); /// assert_eq!(path.get("id").unwrap(), "123");
/// assert_eq!(path.unprocessed(), "/stars"); /// assert_eq!(path.unprocessed(), "/stars");
/// ///
/// let resource = ResourceDef::new("/blob/{path}*"); /// let resource = ResourceDef::new("/blob/{path}*");
/// let mut path = Path::new("/blob/HEAD/Cargo.toml"); /// let mut path = Path::new("/blob/HEAD/Cargo.toml");
/// assert!(resource.capture_match_info(&mut path)); /// assert!(resource.resolve_resource_if_matches(&mut path));
/// assert_eq!(path.get("path").unwrap(), "HEAD/Cargo.toml"); /// assert_eq!(path.get("path").unwrap(), "HEAD/Cargo.toml");
/// assert_eq!(path.unprocessed(), ""); /// assert_eq!(path.unprocessed(), "");
/// ``` /// ```
pub fn capture_match_info<R: Resource>(&self, resource: &mut R) -> bool { pub fn resolve_resource_if_matches<R: Resource>(&self, resource: &mut R) -> bool {
self.capture_match_info_fn(resource, |_| true) match self.capture_match_info(resource) {
None => { false }
Some(match_info) => {
self.resolve_resource(resource, match_info);
true
}
}
} }
/// Collects dynamic segment values into `resource` after matching paths and executing /// Collects dynamic segment values into `resource` after matching paths and executing
@ -674,80 +691,90 @@ impl ResourceDef {
/// assert!(!try_match(&resource, &mut path)); /// assert!(!try_match(&resource, &mut path));
/// assert_eq!(path.unprocessed(), "/user/admin/stars"); /// assert_eq!(path.unprocessed(), "/user/admin/stars");
/// ``` /// ```
pub fn capture_match_info_fn<R, F>(&self, resource: &mut R, check_fn: F) -> bool pub fn capture_match_info<R>(&self, resource: &mut R) -> Option<ResourceMatchInfo<'_>>
where where
R: Resource, R: Resource,
F: FnOnce(&R) -> bool,
{ {
let mut segments = <[PathItem; MAX_DYNAMIC_SEGMENTS]>::default();
let path = resource.resource_path(); let path = resource.resource_path();
let path_str = path.unprocessed(); let path_str = path.unprocessed();
match &self.pat_type {
let (matched_len, matched_vars) = match &self.pat_type {
PatternType::Static(pattern) => match self.static_match(pattern, path_str) { PatternType::Static(pattern) => match self.static_match(pattern, path_str) {
Some(len) => (len, None), Some(len) => Some(ResourceMatchInfo::Static { matched_len: len as u16 }),
None => return false, None => return None,
}, },
PatternType::Dynamic(re, names) => { PatternType::Dynamic(re, names) => {
let captures = match re.captures(path.unprocessed()) { let captures = match re.captures(path_str) {
Some(captures) => captures, Some(captures) => captures,
_ => return false, _ => return None,
}; };
let mut segments = <[PathItem; MAX_DYNAMIC_SEGMENTS]>::default();
for (no, name) in names.iter().enumerate() { for (no, name) in names.iter().enumerate() {
if let Some(m) = captures.name(name) { if let Some(m) = captures.name(name) {
segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
} else { } else {
error!("Dynamic path match but not all segments found: {}", name); error!("Dynamic path match but not all segments found: {}", name);
return false; return None;
} }
} }
(captures[1].len(), Some(names)) Some(ResourceMatchInfo::Dynamic {
matched_len: captures[1].len() as u16,
matched_vars: names,
segments,
})
} }
PatternType::DynamicSet(re, params) => { PatternType::DynamicSet(re, params) => {
let path = path.unprocessed(); let (pattern, names) = match re.first_match_idx(path_str) {
let (pattern, names) = match re.first_match_idx(path) {
Some(idx) => &params[idx], Some(idx) => &params[idx],
_ => return false, _ => return None,
}; };
let captures = match pattern.captures(path.path()) { let captures = match pattern.captures(path_str) {
Some(captures) => captures, Some(captures) => captures,
_ => return false, _ => return None,
}; };
let mut segments = <[PathItem; MAX_DYNAMIC_SEGMENTS]>::default();
for (no, name) in names.iter().enumerate() { for (no, name) in names.iter().enumerate() {
if let Some(m) = captures.name(name) { if let Some(m) = captures.name(name) {
segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
} else { } else {
error!("Dynamic path match but not all segments found: {}", name); error!("Dynamic path match but not all segments found: {}", name);
return false; return None;
} }
} }
(captures[1].len(), Some(names)) Some(ResourceMatchInfo::Dynamic {
matched_len: captures[1].len() as u16,
matched_vars: names,
segments,
})
}
} }
};
if !check_fn(resource) {
return false;
} }
// Modify `path` to skip matched part and store matched segments
///
pub fn resolve_resource<R>(&self, resource: &mut R, match_info: ResourceMatchInfo<'_>)
where
R: Resource,
{
let path = resource.resource_path(); let path = resource.resource_path();
match match_info {
if let Some(vars) = matched_vars { ResourceMatchInfo::Static { matched_len } => {
for i in 0..vars.len() { path.skip(matched_len);
path.add(vars[i], mem::take(&mut segments[i]));
} }
ResourceMatchInfo::Dynamic { matched_len, matched_vars, mut segments } => {
for i in 0..matched_vars.len() {
path.add(matched_vars[i], mem::take(&mut segments[i]));
} }
path.skip(matched_len as u16); path.skip(matched_len);
}
true }
} }
/// Assembles resource path using a closure that maps variable segment names to values. /// Assembles resource path using a closure that maps variable segment names to values.
@ -1118,9 +1145,10 @@ pub(crate) fn insert_slash(path: &str) -> Cow<'_, str> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use crate::Path; use crate::Path;
use super::*;
#[test] #[test]
fn equivalence() { fn equivalence() {
assert_eq!( assert_eq!(
@ -1171,7 +1199,7 @@ mod tests {
assert!(!re.is_match("/name~")); assert!(!re.is_match("/name~"));
let mut path = Path::new("/name"); let mut path = Path::new("/name");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.unprocessed(), ""); assert_eq!(path.unprocessed(), "");
assert_eq!(re.find_match("/name"), Some(5)); assert_eq!(re.find_match("/name"), Some(5));
@ -1189,7 +1217,7 @@ mod tests {
assert!(!re.is_match("/user/profile/profile")); assert!(!re.is_match("/user/profile/profile"));
let mut path = Path::new("/user/profile"); let mut path = Path::new("/user/profile");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.unprocessed(), ""); assert_eq!(path.unprocessed(), "");
} }
@ -1202,12 +1230,12 @@ mod tests {
assert!(!re.is_match("/user/2345/sdg")); assert!(!re.is_match("/user/2345/sdg"));
let mut path = Path::new("/user/profile"); let mut path = Path::new("/user/profile");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("id").unwrap(), "profile"); assert_eq!(path.get("id").unwrap(), "profile");
assert_eq!(path.unprocessed(), ""); assert_eq!(path.unprocessed(), "");
let mut path = Path::new("/user/1245125"); let mut path = Path::new("/user/1245125");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("id").unwrap(), "1245125"); assert_eq!(path.get("id").unwrap(), "1245125");
assert_eq!(path.unprocessed(), ""); assert_eq!(path.unprocessed(), "");
@ -1217,7 +1245,7 @@ mod tests {
assert!(!re.is_match("/resource")); assert!(!re.is_match("/resource"));
let mut path = Path::new("/v151/resource/adage32"); let mut path = Path::new("/v151/resource/adage32");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("version").unwrap(), "151"); assert_eq!(path.get("version").unwrap(), "151");
assert_eq!(path.get("id").unwrap(), "adage32"); assert_eq!(path.get("id").unwrap(), "adage32");
assert_eq!(path.unprocessed(), ""); assert_eq!(path.unprocessed(), "");
@ -1229,7 +1257,7 @@ mod tests {
assert!(!re.is_match("/XXXXXX")); assert!(!re.is_match("/XXXXXX"));
let mut path = Path::new("/012345"); let mut path = Path::new("/012345");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("id").unwrap(), "012345"); assert_eq!(path.get("id").unwrap(), "012345");
assert_eq!(path.unprocessed(), ""); assert_eq!(path.unprocessed(), "");
} }
@ -1249,12 +1277,12 @@ mod tests {
assert!(!re.is_match("/user/2345/sdg")); assert!(!re.is_match("/user/2345/sdg"));
let mut path = Path::new("/user/profile"); let mut path = Path::new("/user/profile");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("id").unwrap(), "profile"); assert_eq!(path.get("id").unwrap(), "profile");
assert_eq!(path.unprocessed(), ""); assert_eq!(path.unprocessed(), "");
let mut path = Path::new("/user/1245125"); let mut path = Path::new("/user/1245125");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("id").unwrap(), "1245125"); assert_eq!(path.get("id").unwrap(), "1245125");
assert_eq!(path.unprocessed(), ""); assert_eq!(path.unprocessed(), "");
@ -1263,7 +1291,7 @@ mod tests {
assert!(!re.is_match("/resource")); assert!(!re.is_match("/resource"));
let mut path = Path::new("/v151/resource/adage32"); let mut path = Path::new("/v151/resource/adage32");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("version").unwrap(), "151"); assert_eq!(path.get("version").unwrap(), "151");
assert_eq!(path.get("id").unwrap(), "adage32"); assert_eq!(path.get("id").unwrap(), "adage32");
@ -1277,7 +1305,7 @@ mod tests {
assert!(!re.is_match("/static/a")); assert!(!re.is_match("/static/a"));
let mut path = Path::new("/012345"); let mut path = Path::new("/012345");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("id").unwrap(), "012345"); assert_eq!(path.get("id").unwrap(), "012345");
let re = ResourceDef::new([ let re = ResourceDef::new([
@ -1314,7 +1342,7 @@ mod tests {
assert_eq!(re.find_match("/12345"), None); assert_eq!(re.find_match("/12345"), None);
let mut path = Path::new("/151/res"); let mut path = Path::new("/151/res");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("id").unwrap(), "151"); assert_eq!(path.get("id").unwrap(), "151");
assert_eq!(path.unprocessed(), "/res"); assert_eq!(path.unprocessed(), "/res");
} }
@ -1324,19 +1352,19 @@ mod tests {
let re = ResourceDef::new("/user/-{id}*"); let re = ResourceDef::new("/user/-{id}*");
let mut path = Path::new("/user/-profile"); let mut path = Path::new("/user/-profile");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("id").unwrap(), "profile"); assert_eq!(path.get("id").unwrap(), "profile");
let mut path = Path::new("/user/-2345"); let mut path = Path::new("/user/-2345");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("id").unwrap(), "2345"); assert_eq!(path.get("id").unwrap(), "2345");
let mut path = Path::new("/user/-2345/"); let mut path = Path::new("/user/-2345/");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("id").unwrap(), "2345/"); assert_eq!(path.get("id").unwrap(), "2345/");
let mut path = Path::new("/user/-2345/sdg"); let mut path = Path::new("/user/-2345/sdg");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("id").unwrap(), "2345/sdg"); assert_eq!(path.get("id").unwrap(), "2345/sdg");
} }
@ -1364,7 +1392,7 @@ mod tests {
let re = ResourceDef::new("/user/{id}/{tail}*"); let re = ResourceDef::new("/user/{id}/{tail}*");
assert!(!re.is_match("/user/2345")); assert!(!re.is_match("/user/2345"));
let mut path = Path::new("/user/2345/sdg"); let mut path = Path::new("/user/2345/sdg");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("id").unwrap(), "2345"); assert_eq!(path.get("id").unwrap(), "2345");
assert_eq!(path.get("tail").unwrap(), "sdg"); assert_eq!(path.get("tail").unwrap(), "sdg");
assert_eq!(path.unprocessed(), ""); assert_eq!(path.unprocessed(), "");
@ -1379,7 +1407,7 @@ mod tests {
let re = ResourceDef::new("/a{x}b/test/a{y}b"); let re = ResourceDef::new("/a{x}b/test/a{y}b");
let mut path = Path::new("/a\nb/test/a\nb"); let mut path = Path::new("/a\nb/test/a\nb");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("x").unwrap(), "\n"); assert_eq!(path.get("x").unwrap(), "\n");
assert_eq!(path.get("y").unwrap(), "\n"); assert_eq!(path.get("y").unwrap(), "\n");
@ -1388,12 +1416,12 @@ mod tests {
let re = ResourceDef::new("/user/{id}*"); let re = ResourceDef::new("/user/{id}*");
let mut path = Path::new("/user/a\nb/a\nb"); let mut path = Path::new("/user/a\nb/a\nb");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("id").unwrap(), "a\nb/a\nb"); assert_eq!(path.get("id").unwrap(), "a\nb/a\nb");
let re = ResourceDef::new("/user/{id:.*}"); let re = ResourceDef::new("/user/{id:.*}");
let mut path = Path::new("/user/a\nb/a\nb"); let mut path = Path::new("/user/a\nb/a\nb");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("id").unwrap(), "a\nb/a\nb"); assert_eq!(path.get("id").unwrap(), "a\nb/a\nb");
} }
@ -1403,16 +1431,16 @@ mod tests {
let re = ResourceDef::new("/user/{id}/test"); let re = ResourceDef::new("/user/{id}/test");
let mut path = Path::new("/user/2345/test"); let mut path = Path::new("/user/2345/test");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("id").unwrap(), "2345"); assert_eq!(path.get("id").unwrap(), "2345");
let mut path = Path::new("/user/qwe%25/test"); let mut path = Path::new("/user/qwe%25/test");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("id").unwrap(), "qwe%25"); assert_eq!(path.get("id").unwrap(), "qwe%25");
let uri = http::Uri::try_from("/user/qwe%25/test").unwrap(); let uri = http::Uri::try_from("/user/qwe%25/test").unwrap();
let mut path = Path::new(uri); let mut path = Path::new(uri);
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.get("id").unwrap(), "qwe%25"); assert_eq!(path.get("id").unwrap(), "qwe%25");
} }
@ -1429,11 +1457,11 @@ mod tests {
assert!(!re.is_match("/name~")); assert!(!re.is_match("/name~"));
let mut path = Path::new("/name"); let mut path = Path::new("/name");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.unprocessed(), ""); assert_eq!(path.unprocessed(), "");
let mut path = Path::new("/name/test"); let mut path = Path::new("/name/test");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.unprocessed(), "/test"); assert_eq!(path.unprocessed(), "/test");
assert_eq!(re.find_match("/name"), Some(5)); assert_eq!(re.find_match("/name"), Some(5));
@ -1449,10 +1477,10 @@ mod tests {
assert!(!re.is_match("/name")); assert!(!re.is_match("/name"));
let mut path = Path::new("/name/gs"); let mut path = Path::new("/name/gs");
assert!(!re.capture_match_info(&mut path)); assert!(!re.resolve_resource_if_matches(&mut path));
let mut path = Path::new("/name//gs"); let mut path = Path::new("/name//gs");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(path.unprocessed(), "/gs"); assert_eq!(path.unprocessed(), "/gs");
let re = ResourceDef::root_prefix("name/"); let re = ResourceDef::root_prefix("name/");
@ -1462,7 +1490,7 @@ mod tests {
assert!(!re.is_match("/name")); assert!(!re.is_match("/name"));
let mut path = Path::new("/name/gs"); let mut path = Path::new("/name/gs");
assert!(!re.capture_match_info(&mut path)); assert!(!re.resolve_resource_if_matches(&mut path));
} }
#[test] #[test]
@ -1481,13 +1509,13 @@ mod tests {
assert_eq!(re.find_match(""), None); assert_eq!(re.find_match(""), None);
let mut path = Path::new("/test2/"); let mut path = Path::new("/test2/");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(&path["name"], "test2"); assert_eq!(&path["name"], "test2");
assert_eq!(&path[0], "test2"); assert_eq!(&path[0], "test2");
assert_eq!(path.unprocessed(), "/"); assert_eq!(path.unprocessed(), "/");
let mut path = Path::new("/test2/subpath1/subpath2/index.html"); let mut path = Path::new("/test2/subpath1/subpath2/index.html");
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
assert_eq!(&path["name"], "test2"); assert_eq!(&path["name"], "test2");
assert_eq!(&path[0], "test2"); assert_eq!(&path[0], "test2");
assert_eq!(path.unprocessed(), "/subpath1/subpath2/index.html"); assert_eq!(path.unprocessed(), "/subpath1/subpath2/index.html");
@ -1543,7 +1571,7 @@ mod tests {
assert!(resource.resource_path_from_iter( assert!(resource.resource_path_from_iter(
&mut s, &mut s,
#[allow(clippy::useless_vec)] #[allow(clippy::useless_vec)]
&mut vec!["item", "item2"].iter() &mut vec!["item", "item2"].iter(),
)); ));
assert_eq!(s, "/user/item/item2/"); assert_eq!(s, "/user/item/item2/");
} }
@ -1561,22 +1589,22 @@ mod tests {
let resource = ResourceDef::new(["/user/{id}", "/profile/{id}"]); let resource = ResourceDef::new(["/user/{id}", "/profile/{id}"]);
let mut path = Path::new("/user/123"); let mut path = Path::new("/user/123");
assert!(resource.capture_match_info(&mut path)); assert!(resource.resolve_resource_if_matches(&mut path));
assert!(path.get("id").is_some()); assert!(path.get("id").is_some());
let mut path = Path::new("/profile/123"); let mut path = Path::new("/profile/123");
assert!(resource.capture_match_info(&mut path)); assert!(resource.resolve_resource_if_matches(&mut path));
assert!(path.get("id").is_some()); assert!(path.get("id").is_some());
let resource = ResourceDef::new(["/user/{id}", "/profile/{uid}"]); let resource = ResourceDef::new(["/user/{id}", "/profile/{uid}"]);
let mut path = Path::new("/user/123"); let mut path = Path::new("/user/123");
assert!(resource.capture_match_info(&mut path)); assert!(resource.resolve_resource_if_matches(&mut path));
assert!(path.get("id").is_some()); assert!(path.get("id").is_some());
assert!(path.get("uid").is_none()); assert!(path.get("uid").is_none());
let mut path = Path::new("/profile/123"); let mut path = Path::new("/profile/123");
assert!(resource.capture_match_info(&mut path)); assert!(resource.resolve_resource_if_matches(&mut path));
assert!(path.get("id").is_none()); assert!(path.get("id").is_none());
assert!(path.get("uid").is_some()); assert!(path.get("uid").is_some());
} }

View File

@ -13,12 +13,13 @@ pub struct ResourceId(pub u16);
/// not required. /// not required.
pub struct Router<T, U = ()> { pub struct Router<T, U = ()> {
routes: Vec<(ResourceDef, T, U)>, routes: Vec<(ResourceDef, T, U)>,
max_path_conflicts: u16,
} }
impl<T, U> Router<T, U> { impl<T, U> Router<T, U> {
/// Constructs new `RouterBuilder` with empty route list. /// Constructs new `RouterBuilder` with empty route list.
pub fn build() -> RouterBuilder<T, U> { pub fn build() -> RouterBuilder<T, U> {
RouterBuilder { routes: Vec::new() } RouterBuilder { routes: Vec::new(), path_conflicts: vec![] }
} }
/// Finds the value in the router that matches a given [routing resource](Resource). /// Finds the value in the router that matches a given [routing resource](Resource).
@ -46,14 +47,24 @@ impl<T, U> Router<T, U> {
/// the `check` closure is executed, passing the resource and each route's context data. If the /// the `check` closure is executed, passing the resource and each route's context data. If the
/// closure returns true then the match result is stored into `resource` and a reference to /// closure returns true then the match result is stored into `resource` and a reference to
/// the matched _value_ is returned. /// the matched _value_ is returned.
pub fn recognize_fn<R, F>(&self, resource: &mut R, mut check: F) -> Option<(&T, ResourceId)> pub fn recognize_fn<R, F>(&self, resource: &mut R, mut check_fn: F) -> Option<(&T, ResourceId)>
where where
R: Resource, R: Resource,
F: FnMut(&R, &U) -> bool, F: FnMut(&R, &U) -> bool,
{ {
let mut matches = 0;
for (rdef, val, ctx) in self.routes.iter() { for (rdef, val, ctx) in self.routes.iter() {
if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) { match rdef.capture_match_info(resource) {
None => {}
Some(match_info) => {
matches += 1;
if check_fn(resource, ctx) {
rdef.resolve_resource(resource, match_info);
return Some((val, ResourceId(rdef.id()))); return Some((val, ResourceId(rdef.id())));
} else if matches == self.max_path_conflicts {
return None;
}
}
} }
} }
@ -65,15 +76,25 @@ impl<T, U> Router<T, U> {
pub fn recognize_mut_fn<R, F>( pub fn recognize_mut_fn<R, F>(
&mut self, &mut self,
resource: &mut R, resource: &mut R,
mut check: F, mut check_fn: F,
) -> Option<(&mut T, ResourceId)> ) -> Option<(&mut T, ResourceId)>
where where
R: Resource, R: Resource,
F: FnMut(&R, &U) -> bool, F: FnMut(&R, &U) -> bool,
{ {
let mut matches = 0;
for (rdef, val, ctx) in self.routes.iter_mut() { for (rdef, val, ctx) in self.routes.iter_mut() {
if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) { match rdef.capture_match_info(resource) {
None => {}
Some(match_info) => {
matches += 1;
if check_fn(resource, ctx) {
rdef.resolve_resource(resource, match_info);
return Some((val, ResourceId(rdef.id()))); return Some((val, ResourceId(rdef.id())));
} else if matches == self.max_path_conflicts {
return None;
}
}
} }
} }
@ -84,6 +105,7 @@ impl<T, U> Router<T, U> {
/// Builder for an ordered [routing](Router) list. /// Builder for an ordered [routing](Router) list.
pub struct RouterBuilder<T, U = ()> { pub struct RouterBuilder<T, U = ()> {
routes: Vec<(ResourceDef, T, U)>, routes: Vec<(ResourceDef, T, U)>,
path_conflicts: Vec<(ResourceDef, u16)>,
} }
impl<T, U> RouterBuilder<T, U> { impl<T, U> RouterBuilder<T, U> {
@ -96,6 +118,13 @@ impl<T, U> RouterBuilder<T, U> {
val: T, val: T,
ctx: U, ctx: U,
) -> (&mut ResourceDef, &mut T, &mut U) { ) -> (&mut ResourceDef, &mut T, &mut U) {
if let Some((_, path_conflicts)) = self.path_conflicts.iter_mut()
.find(|(current_rdef, _)| rdef.eq(current_rdef)) {
*path_conflicts += 1;
} else {
self.path_conflicts.push((rdef.clone(), 1));
}
self.routes.push((rdef, val, ctx)); self.routes.push((rdef, val, ctx));
#[allow(clippy::map_identity)] // map is used to distribute &mut-ness to tuple elements #[allow(clippy::map_identity)] // map is used to distribute &mut-ness to tuple elements
self.routes self.routes
@ -106,8 +135,13 @@ impl<T, U> RouterBuilder<T, U> {
/// Finish configuration and create router instance. /// Finish configuration and create router instance.
pub fn finish(self) -> Router<T, U> { pub fn finish(self) -> Router<T, U> {
let max_path_conflicts = self.path_conflicts.iter()
.map(|(_, path_conflicts)| *path_conflicts)
.max()
.unwrap_or(1);
Router { Router {
routes: self.routes, routes: self.routes,
max_path_conflicts,
} }
} }
} }

View File

@ -75,7 +75,7 @@ mod tests {
let re = ResourceDef::new(pattern); let re = ResourceDef::new(pattern);
let uri = Uri::try_from(url.as_ref()).unwrap(); let uri = Uri::try_from(url.as_ref()).unwrap();
let mut path = Path::new(Url::new(uri)); let mut path = Path::new(Url::new(uri));
assert!(re.capture_match_info(&mut path)); assert!(re.resolve_resource_if_matches(&mut path));
path path
} }

View File

@ -1,24 +1,25 @@
use std::{cell::RefCell, mem, rc::Rc}; use std::{cell::RefCell, mem, rc::Rc};
use actix_http::Request;
use actix_router::{Path, ResourceDef, Router, Url};
use actix_service::{boxed, fn_service, Service, ServiceFactory}; use actix_service::{boxed, fn_service, Service, ServiceFactory};
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use futures_util::future::join_all; use futures_util::future::join_all;
use actix_http::Request;
use actix_router::{Path, ResourceDef, Router, Url};
use crate::{ use crate::{
body::BoxBody, body::BoxBody,
config::{AppConfig, AppService}, config::{AppConfig, AppService},
data::FnDataFactory, data::FnDataFactory,
dev::Extensions, dev::Extensions,
Error,
guard::Guard, guard::Guard,
HttpResponse,
request::{HttpRequest, HttpRequestPool}, request::{HttpRequest, HttpRequestPool},
rmap::ResourceMap, rmap::ResourceMap, service::{
service::{
AppServiceFactory, BoxedHttpService, BoxedHttpServiceFactory, ServiceRequest, AppServiceFactory, BoxedHttpService, BoxedHttpServiceFactory, ServiceRequest,
ServiceResponse, ServiceResponse,
}, },
Error, HttpResponse,
}; };
/// Service factory to convert [`Request`] to a [`ServiceRequest<S>`]. /// Service factory to convert [`Request`] to a [`ServiceRequest<S>`].
@ -306,16 +307,16 @@ impl Service<ServiceRequest> for AppRouting {
actix_service::always_ready!(); actix_service::always_ready!();
fn call(&self, mut req: ServiceRequest) -> Self::Future { fn call(&self, mut req: ServiceRequest) -> Self::Future {
let res = self.router.recognize_fn(&mut req, |req, guards| { let guards_check_fn = |req: &ServiceRequest, guards: &Vec<Box<dyn Guard>>| {
let guard_ctx = req.guard_ctx(); let guard_ctx = req.guard_ctx();
guards.iter().all(|guard| guard.check(&guard_ctx)) guards.iter().all(|guard| guard.check(&guard_ctx))
}); };
let res = self.router.recognize_fn(&mut req, guards_check_fn);
if let Some((srv, _info)) = res { if let Some((srv, _info)) = res {
srv.call(req) return srv.call(req);
} else {
self.default.call(req)
} }
self.default.call(req)
} }
} }
@ -346,15 +347,15 @@ impl ServiceFactory<ServiceRequest> for AppEntry {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::sync::{ use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Arc,
atomic::{AtomicBool, Ordering},
}; };
use actix_service::Service; use actix_service::Service;
use crate::{ use crate::{
test::{init_service, TestRequest}, App,
web, App, HttpResponse, HttpResponse, test::{init_service, TestRequest}, web,
}; };
struct DropData(Arc<AtomicBool>); struct DropData(Arc<AtomicBool>);

View File

@ -1,7 +1,5 @@
use std::{cell::RefCell, fmt, future::Future, mem, rc::Rc}; use std::{cell::RefCell, fmt, future::Future, mem, rc::Rc};
use actix_http::{body::MessageBody, Extensions};
use actix_router::{ResourceDef, Router};
use actix_service::{ use actix_service::{
apply, apply_fn_factory, boxed, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt, apply, apply_fn_factory, boxed, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt,
Transform, Transform,
@ -9,17 +7,20 @@ use actix_service::{
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use futures_util::future::join_all; use futures_util::future::join_all;
use actix_http::{body::MessageBody, Extensions};
use actix_router::{ResourceDef, Router};
use crate::{ use crate::{
config::ServiceConfig, config::ServiceConfig,
data::Data, data::Data,
dev::AppService, dev::AppService,
Error,
guard::Guard, guard::Guard,
rmap::ResourceMap, Resource,
service::{ rmap::ResourceMap, Route, service::{
AppServiceFactory, BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory, AppServiceFactory, BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory,
ServiceFactoryWrapper, ServiceRequest, ServiceResponse, ServiceFactoryWrapper, ServiceRequest, ServiceResponse,
}, },
Error, Resource, Route,
}; };
type Guards = Vec<Box<dyn Guard>>; type Guards = Vec<Box<dyn Guard>>;
@ -510,16 +511,16 @@ impl Service<ServiceRequest> for ScopeService {
actix_service::always_ready!(); actix_service::always_ready!();
fn call(&self, mut req: ServiceRequest) -> Self::Future { fn call(&self, mut req: ServiceRequest) -> Self::Future {
let res = self.router.recognize_fn(&mut req, |req, guards| { let guards_check_fn = |req: &ServiceRequest, guards: &Vec<Box<dyn Guard>>| {
let guard_ctx = req.guard_ctx(); let guard_ctx = req.guard_ctx();
guards.iter().all(|guard| guard.check(&guard_ctx)) guards.iter().all(|guard| guard.check(&guard_ctx))
}); };
let res = self.router.recognize_fn(&mut req, guards_check_fn);
if let Some((srv, _info)) = res { if let Some((srv, _info)) = res {
srv.call(req) return srv.call(req);
} else {
self.default.call(req)
} }
self.default.call(req)
} }
} }
@ -552,18 +553,19 @@ mod tests {
use actix_utils::future::ok; use actix_utils::future::ok;
use bytes::Bytes; use bytes::Bytes;
use super::*;
use crate::{ use crate::{
App,
guard, guard,
http::{ http::{
header::{self, HeaderValue}, header::{self, HeaderValue},
Method, StatusCode, Method, StatusCode,
}, },
middleware::DefaultHeaders, HttpMessage,
test::{assert_body_eq, call_service, init_service, read_body, TestRequest}, HttpRequest, HttpResponse, middleware::DefaultHeaders, test::{assert_body_eq, call_service, init_service, read_body, TestRequest}, web,
web, App, HttpMessage, HttpRequest, HttpResponse,
}; };
use super::*;
#[test] #[test]
fn can_be_returned_from_fn() { fn can_be_returned_from_fn() {
fn my_scope_1() -> Scope { fn my_scope_1() -> Scope {

View File

@ -176,7 +176,7 @@ mod tests {
let resource = ResourceDef::new("/{value}/"); let resource = ResourceDef::new("/{value}/");
let mut req = TestRequest::with_uri("/32/").to_srv_request(); let mut req = TestRequest::with_uri("/32/").to_srv_request();
resource.capture_match_info(req.match_info_mut()); resource.resolve_resource_if_matches(req.match_info_mut());
let (req, mut pl) = req.into_parts(); let (req, mut pl) = req.into_parts();
assert_eq!(*Path::<i8>::from_request(&req, &mut pl).await.unwrap(), 32); assert_eq!(*Path::<i8>::from_request(&req, &mut pl).await.unwrap(), 32);
@ -189,7 +189,7 @@ mod tests {
let resource = ResourceDef::new("/{key}/{value}/"); let resource = ResourceDef::new("/{key}/{value}/");
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
resource.capture_match_info(req.match_info_mut()); resource.resolve_resource_if_matches(req.match_info_mut());
let (req, mut pl) = req.into_parts(); let (req, mut pl) = req.into_parts();
let (Path(res),) = <(Path<(String, String)>,)>::from_request(&req, &mut pl) let (Path(res),) = <(Path<(String, String)>,)>::from_request(&req, &mut pl)
@ -215,7 +215,7 @@ mod tests {
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
let resource = ResourceDef::new("/{key}/{value}/"); let resource = ResourceDef::new("/{key}/{value}/");
resource.capture_match_info(req.match_info_mut()); resource.resolve_resource_if_matches(req.match_info_mut());
let (req, mut pl) = req.into_parts(); let (req, mut pl) = req.into_parts();
let mut s = Path::<MyStruct>::from_request(&req, &mut pl).await.unwrap(); let mut s = Path::<MyStruct>::from_request(&req, &mut pl).await.unwrap();
@ -238,7 +238,7 @@ mod tests {
let mut req = TestRequest::with_uri("/name/32/").to_srv_request(); let mut req = TestRequest::with_uri("/name/32/").to_srv_request();
let resource = ResourceDef::new("/{key}/{value}/"); let resource = ResourceDef::new("/{key}/{value}/");
resource.capture_match_info(req.match_info_mut()); resource.resolve_resource_if_matches(req.match_info_mut());
let (req, mut pl) = req.into_parts(); let (req, mut pl) = req.into_parts();
let s = Path::<Test2>::from_request(&req, &mut pl).await.unwrap(); let s = Path::<Test2>::from_request(&req, &mut pl).await.unwrap();
@ -262,7 +262,7 @@ mod tests {
async fn paths_decoded() { async fn paths_decoded() {
let resource = ResourceDef::new("/{key}/{value}"); let resource = ResourceDef::new("/{key}/{value}");
let mut req = TestRequest::with_uri("/na%2Bme/us%2Fer%254%32").to_srv_request(); let mut req = TestRequest::with_uri("/na%2Bme/us%2Fer%254%32").to_srv_request();
resource.capture_match_info(req.match_info_mut()); resource.resolve_resource_if_matches(req.match_info_mut());
let (req, mut pl) = req.into_parts(); let (req, mut pl) = req.into_parts();
let path_items = Path::<MyStruct>::from_request(&req, &mut pl).await.unwrap(); let path_items = Path::<MyStruct>::from_request(&req, &mut pl).await.unwrap();