feat: Add minor refactos and changelog

This commit is contained in:
manuelgdlvh 2025-05-16 21:46:38 +02:00
parent 9f2b207137
commit 4e56ae0594
8 changed files with 150 additions and 101 deletions

View File

@ -2,6 +2,14 @@
## Unreleased
### Added
- Add conflict path detection and handling to enhance routing performance.
### Changed
- Refactor capture_match_info_fn by splitting it into three distinct functions: capture_match_info(), resolve_path_if_match(), and resolve().
## 0.5.3
- Add `unicode` crate feature (on-by-default) to switch between `regex` and `regex-lite` as a trade-off between full unicode support and binary size.

View File

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

View File

@ -1,10 +1,12 @@
use std::{
borrow::Cow,
mem,
ops::{DerefMut, Index},
};
use serde::{de, Deserialize};
use crate::resource::ResourceMatchInfo;
use crate::{de::PathDeserializer, Resource, ResourcePath};
#[derive(Debug, Clone)]
@ -106,6 +108,27 @@ impl<T: ResourcePath> Path<T> {
self.skip += n;
}
/// Post-processes the path to resolve dynamic segments, if any, and determines the character offset to skip.
pub fn resolve(&mut self, match_info: ResourceMatchInfo<'_>) {
match match_info {
ResourceMatchInfo::Static { matched_len } => {
self.resource_path().skip(matched_len);
}
ResourceMatchInfo::Dynamic {
matched_len,
matched_vars,
mut segments,
} => {
for i in 0..matched_vars.len() {
self.resource_path()
.add(matched_vars[i], mem::take(&mut segments[i]));
}
self.resource_path().skip(matched_len);
}
}
}
pub(crate) fn add(&mut self, name: impl Into<Cow<'static, str>>, value: PathItem) {
match value {
PathItem::Static(seg) => self.segments.push((name.into(), PathItem::Static(seg))),
@ -260,4 +283,49 @@ mod tests {
let foo = RefCell::new(foo);
let _ = foo.borrow_mut().resource_path();
}
#[test]
fn test_dynamic_path_resolve() {
let mut path = Path::new("/foo/{var1}/{var2}");
assert_eq!(0, path.segments.len());
assert_eq!(0, path.skip);
let mut segments = <[PathItem; 16]>::default();
segments[0] = PathItem::Static(Cow::Borrowed("foo"));
segments[1] = PathItem::Segment(2, 5);
let match_info = ResourceMatchInfo::Dynamic {
matched_len: 3,
matched_vars: &["var1", "var2"],
segments,
};
path.resolve(match_info);
assert_eq!(2, path.segments.len());
assert_eq!(3, path.skip);
let (name, value) = path.segments.get(0).unwrap();
assert_eq!(name.as_ref(), "var1");
assert!(matches!(value, PathItem::Static(Cow::Borrowed("foo"))));
let (name, value) = path.segments.get(1).unwrap();
assert_eq!(name.as_ref(), "var2");
assert!(matches!(value, PathItem::Segment(2, 5)));
}
#[test]
fn test_static_path_resolve() {
let mut path = Path::new("/foo");
assert_eq!(0, path.segments.len());
assert_eq!(0, path.skip);
let match_info = ResourceMatchInfo::Static { matched_len: 2 };
path.resolve(match_info);
assert_eq!(0, path.segments.len());
assert_eq!(2, path.skip);
}
}

View File

@ -79,8 +79,7 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// `/rust-is-hard`.
///
/// For information on capturing segment values from paths or other custom resource types,
/// see [`capture_match_info`][Self::capture_match_info]
/// and [`capture_match_info_fn`][Self::capture_match_info_fn].
/// see [`capture_match_info`][Self::capture_match_info].
///
/// A resource definition can contain at most 16 dynamic segments.
///
@ -95,7 +94,7 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!resource.is_match("/user/"));
///
/// let mut path = Path::new("/user/123");
/// resource.resolve_path_if_matches(&mut path);
/// resource.resolve_path_if_match(&mut path);
/// assert_eq!(path.get("id").unwrap(), "123");
/// ```
///
@ -170,7 +169,7 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(resource.is_match("/blob/HEAD/README.md"));
///
/// let mut path = Path::new("/blob/main/LICENSE");
/// resource.resolve_path_if_matches(&mut path);
/// resource.resolve_path_if_match(&mut path);
/// assert_eq!(path.get("tail").unwrap(), "main/LICENSE");
/// ```
///
@ -248,6 +247,7 @@ enum PatternType {
DynamicSet(RegexSet, Vec<(Regex, Vec<&'static str>)>),
}
/// Holds metadata and parameters used during path resolution.
pub enum ResourceMatchInfo<'a> {
Static {
matched_len: u16,
@ -633,21 +633,21 @@ impl ResourceDef {
///
/// let resource = ResourceDef::prefix("/user/{id}");
/// let mut path = Path::new("/user/123/stars");
/// assert!(resource.resolve_path_if_matches(&mut path));
/// assert!(resource.resolve_path_if_match(&mut path));
/// assert_eq!(path.get("id").unwrap(), "123");
/// assert_eq!(path.unprocessed(), "/stars");
///
/// let resource = ResourceDef::new("/blob/{path}*");
/// let mut path = Path::new("/blob/HEAD/Cargo.toml");
/// assert!(resource.resolve_path_if_matches(&mut path));
/// assert!(resource.resolve_path_if_match(&mut path));
/// assert_eq!(path.get("path").unwrap(), "HEAD/Cargo.toml");
/// assert_eq!(path.unprocessed(), "");
/// ```
pub fn resolve_path_if_matches<R: Resource>(&self, resource: &mut R) -> bool {
pub fn resolve_path_if_match<R: Resource>(&self, resource: &mut R) -> bool {
match self.capture_match_info(resource) {
None => false,
Some(match_info) => {
resource.resolve_path(match_info);
resource.resource_path().resolve(match_info);
true
}
}
@ -660,21 +660,22 @@ impl ResourceDef {
/// This is useful if you want to conditionally match on some non-path related aspect of the
/// resource type.
///
/// Returns `true` if resource path matches this resource definition _and_ satisfies the
/// given check function.
///
/// Returns `ResourceMatchInfo` if the given resource path matches this resource definition,
/// containing the information required to perform path resolution.
/// # Examples
/// ```
/// use actix_router::{Path, ResourceDef};
/// use actix_router::{Path, Resource, ResourceDef};
///
/// fn try_match(resource: &ResourceDef, path: &mut Path<&str>) -> bool {
/// let admin_allowed = std::env::var("ADMIN_ALLOWED").is_ok();
///
/// resource.capture_match_info_fn(
/// path,
/// // when env var is not set, reject when path contains "admin"
/// |path| !(!admin_allowed && path.as_str().contains("admin")),
/// )
/// let match_info = resource.capture_match_info(path);
/// match match_info{
/// None => {false}
/// Some(match_info) => {
/// path.resource_path().resolve(match_info);
/// true
/// }
/// }
/// }
///
/// let resource = ResourceDef::prefix("/user/{id}");
@ -685,10 +686,6 @@ impl ResourceDef {
/// assert_eq!(path.get("id").unwrap(), "james");
/// assert_eq!(path.unprocessed(), "/stars");
///
/// // path matches but fails check function; no segments are collected
/// let mut path = Path::new("/user/admin/stars");
/// assert!(!try_match(&resource, &mut path));
/// assert_eq!(path.unprocessed(), "/user/admin/stars");
/// ```
pub fn capture_match_info<R>(&self, resource: &mut R) -> Option<ResourceMatchInfo<'_>>
where
@ -1179,7 +1176,7 @@ mod tests {
assert!(!re.is_match("/name~"));
let mut path = Path::new("/name");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.unprocessed(), "");
assert_eq!(re.find_match("/name"), Some(5));
@ -1197,7 +1194,7 @@ mod tests {
assert!(!re.is_match("/user/profile/profile"));
let mut path = Path::new("/user/profile");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.unprocessed(), "");
}
@ -1210,12 +1207,12 @@ mod tests {
assert!(!re.is_match("/user/2345/sdg"));
let mut path = Path::new("/user/profile");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "profile");
assert_eq!(path.unprocessed(), "");
let mut path = Path::new("/user/1245125");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "1245125");
assert_eq!(path.unprocessed(), "");
@ -1225,7 +1222,7 @@ mod tests {
assert!(!re.is_match("/resource"));
let mut path = Path::new("/v151/resource/adage32");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("version").unwrap(), "151");
assert_eq!(path.get("id").unwrap(), "adage32");
assert_eq!(path.unprocessed(), "");
@ -1237,7 +1234,7 @@ mod tests {
assert!(!re.is_match("/XXXXXX"));
let mut path = Path::new("/012345");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "012345");
assert_eq!(path.unprocessed(), "");
}
@ -1257,12 +1254,12 @@ mod tests {
assert!(!re.is_match("/user/2345/sdg"));
let mut path = Path::new("/user/profile");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "profile");
assert_eq!(path.unprocessed(), "");
let mut path = Path::new("/user/1245125");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "1245125");
assert_eq!(path.unprocessed(), "");
@ -1271,7 +1268,7 @@ mod tests {
assert!(!re.is_match("/resource"));
let mut path = Path::new("/v151/resource/adage32");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("version").unwrap(), "151");
assert_eq!(path.get("id").unwrap(), "adage32");
@ -1285,7 +1282,7 @@ mod tests {
assert!(!re.is_match("/static/a"));
let mut path = Path::new("/012345");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "012345");
let re = ResourceDef::new([
@ -1322,7 +1319,7 @@ mod tests {
assert_eq!(re.find_match("/12345"), None);
let mut path = Path::new("/151/res");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "151");
assert_eq!(path.unprocessed(), "/res");
}
@ -1332,19 +1329,19 @@ mod tests {
let re = ResourceDef::new("/user/-{id}*");
let mut path = Path::new("/user/-profile");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "profile");
let mut path = Path::new("/user/-2345");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "2345");
let mut path = Path::new("/user/-2345/");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "2345/");
let mut path = Path::new("/user/-2345/sdg");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "2345/sdg");
}
@ -1372,7 +1369,7 @@ mod tests {
let re = ResourceDef::new("/user/{id}/{tail}*");
assert!(!re.is_match("/user/2345"));
let mut path = Path::new("/user/2345/sdg");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "2345");
assert_eq!(path.get("tail").unwrap(), "sdg");
assert_eq!(path.unprocessed(), "");
@ -1387,7 +1384,7 @@ mod tests {
let re = ResourceDef::new("/a{x}b/test/a{y}b");
let mut path = Path::new("/a\nb/test/a\nb");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("x").unwrap(), "\n");
assert_eq!(path.get("y").unwrap(), "\n");
@ -1396,12 +1393,12 @@ mod tests {
let re = ResourceDef::new("/user/{id}*");
let mut path = Path::new("/user/a\nb/a\nb");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "a\nb/a\nb");
let re = ResourceDef::new("/user/{id:.*}");
let mut path = Path::new("/user/a\nb/a\nb");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "a\nb/a\nb");
}
@ -1411,16 +1408,16 @@ mod tests {
let re = ResourceDef::new("/user/{id}/test");
let mut path = Path::new("/user/2345/test");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "2345");
let mut path = Path::new("/user/qwe%25/test");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "qwe%25");
let uri = http::Uri::try_from("/user/qwe%25/test").unwrap();
let mut path = Path::new(uri);
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "qwe%25");
}
@ -1437,11 +1434,11 @@ mod tests {
assert!(!re.is_match("/name~"));
let mut path = Path::new("/name");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.unprocessed(), "");
let mut path = Path::new("/name/test");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.unprocessed(), "/test");
assert_eq!(re.find_match("/name"), Some(5));
@ -1457,10 +1454,10 @@ mod tests {
assert!(!re.is_match("/name"));
let mut path = Path::new("/name/gs");
assert!(!re.resolve_path_if_matches(&mut path));
assert!(!re.resolve_path_if_match(&mut path));
let mut path = Path::new("/name//gs");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.unprocessed(), "/gs");
let re = ResourceDef::root_prefix("name/");
@ -1470,7 +1467,7 @@ mod tests {
assert!(!re.is_match("/name"));
let mut path = Path::new("/name/gs");
assert!(!re.resolve_path_if_matches(&mut path));
assert!(!re.resolve_path_if_match(&mut path));
}
#[test]
@ -1489,13 +1486,13 @@ mod tests {
assert_eq!(re.find_match(""), None);
let mut path = Path::new("/test2/");
assert!(re.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&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.resolve_path_if_matches(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(&path["name"], "test2");
assert_eq!(&path[0], "test2");
assert_eq!(path.unprocessed(), "/subpath1/subpath2/index.html");
@ -1569,22 +1566,22 @@ mod tests {
let resource = ResourceDef::new(["/user/{id}", "/profile/{id}"]);
let mut path = Path::new("/user/123");
assert!(resource.resolve_path_if_matches(&mut path));
assert!(resource.resolve_path_if_match(&mut path));
assert!(path.get("id").is_some());
let mut path = Path::new("/profile/123");
assert!(resource.resolve_path_if_matches(&mut path));
assert!(resource.resolve_path_if_match(&mut path));
assert!(path.get("id").is_some());
let resource = ResourceDef::new(["/user/{id}", "/profile/{uid}"]);
let mut path = Path::new("/user/123");
assert!(resource.resolve_path_if_matches(&mut path));
assert!(resource.resolve_path_if_match(&mut path));
assert!(path.get("id").is_some());
assert!(path.get("uid").is_none());
let mut path = Path::new("/profile/123");
assert!(resource.resolve_path_if_matches(&mut path));
assert!(resource.resolve_path_if_match(&mut path));
assert!(path.get("id").is_none());
assert!(path.get("uid").is_some());
}

View File

@ -1,6 +1,3 @@
use std::mem;
use crate::resource::ResourceMatchInfo;
use crate::Path;
// TODO: this trait is necessary, document it
@ -10,26 +7,6 @@ pub trait Resource {
type Path: ResourcePath;
fn resource_path(&mut self) -> &mut Path<Self::Path>;
fn resolve_path(&mut self, match_info: ResourceMatchInfo<'_>) {
let path = self.resource_path();
match match_info {
ResourceMatchInfo::Static { matched_len } => {
path.skip(matched_len);
}
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);
}
}
}
}
pub trait ResourcePath {

View File

@ -55,18 +55,18 @@ impl<T, U> Router<T, U> {
R: Resource,
F: FnMut(&R, &U) -> bool,
{
let mut next_match_count = 1;
let mut next_resource_match_count = 1;
for (rdef, val, ctx) in self.routes.iter() {
match rdef.capture_match_info(resource) {
None => {}
Some(match_info) => {
if check_fn(resource, ctx) {
resource.resolve_path(match_info);
resource.resource_path().resolve(match_info);
return Some((val, ResourceId(rdef.id())));
} else if next_match_count == self.max_path_conflicts {
} else if next_resource_match_count == self.max_path_conflicts {
return None;
}
next_match_count += 1;
next_resource_match_count += 1;
}
}
}
@ -92,7 +92,7 @@ impl<T, U> Router<T, U> {
Some(match_info) => {
matches += 1;
if check_fn(resource, ctx) {
resource.resolve_path(match_info);
resource.resource_path().resolve(match_info);
return Some((val, ResourceId(rdef.id())));
} else if matches == self.max_path_conflicts {
return None;
@ -108,7 +108,7 @@ impl<T, U> Router<T, U> {
/// Builder for an ordered [routing](Router) list.
pub struct RouterBuilder<T, U = ()> {
routes: Vec<(ResourceDef, T, U)>,
path_conflicts: Vec<(ResourceDef, u16)>,
path_conflicts: Vec<(usize, u16)>,
}
impl<T, U> RouterBuilder<T, U> {
@ -124,14 +124,15 @@ impl<T, U> RouterBuilder<T, U> {
if let Some((_, path_conflicts)) = self
.path_conflicts
.iter_mut()
.find(|(current_rdef, _)| rdef.eq(current_rdef))
.find(|(route_idx, _)| rdef.eq(&self.routes.get(*route_idx).unwrap().0))
{
*path_conflicts += 1;
} else {
self.path_conflicts.push((rdef.clone(), 1));
self.path_conflicts.push((self.routes.len(), 1));
}
self.routes.push((rdef, val, ctx));
#[allow(clippy::map_identity)] // map is used to distribute &mut-ness to tuple elements
self.routes
.last_mut()
@ -141,9 +142,7 @@ impl<T, U> RouterBuilder<T, U> {
/// Finish configuration and create router instance.
pub fn finish(self) -> Router<T, U> {
let max_path_conflicts = self
.path_conflicts
.iter()
let max_path_conflicts = self.path_conflicts.iter()
.map(|(_, path_conflicts)| *path_conflicts)
.max()
.unwrap_or(1);

View File

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

View File

@ -176,7 +176,7 @@ mod tests {
let resource = ResourceDef::new("/{value}/");
let mut req = TestRequest::with_uri("/32/").to_srv_request();
resource.resolve_path_if_matches(req.match_info_mut());
resource.resolve_path_if_match(req.match_info_mut());
let (req, mut pl) = req.into_parts();
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 mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
resource.resolve_path_if_matches(req.match_info_mut());
resource.resolve_path_if_match(req.match_info_mut());
let (req, mut pl) = req.into_parts();
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 resource = ResourceDef::new("/{key}/{value}/");
resource.resolve_path_if_matches(req.match_info_mut());
resource.resolve_path_if_match(req.match_info_mut());
let (req, mut pl) = req.into_parts();
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 resource = ResourceDef::new("/{key}/{value}/");
resource.resolve_path_if_matches(req.match_info_mut());
resource.resolve_path_if_match(req.match_info_mut());
let (req, mut pl) = req.into_parts();
let s = Path::<Test2>::from_request(&req, &mut pl).await.unwrap();
@ -262,7 +262,7 @@ mod tests {
async fn paths_decoded() {
let resource = ResourceDef::new("/{key}/{value}");
let mut req = TestRequest::with_uri("/na%2Bme/us%2Fer%254%32").to_srv_request();
resource.resolve_path_if_matches(req.match_info_mut());
resource.resolve_path_if_match(req.match_info_mut());
let (req, mut pl) = req.into_parts();
let path_items = Path::<MyStruct>::from_request(&req, &mut pl).await.unwrap();