Skip to content
Open
7 changes: 7 additions & 0 deletions actix-router/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## 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()`.
- Minimum supported Rust version (MSRV) is now 1.88.

## 0.5.3
Expand Down
9 changes: 9 additions & 0 deletions actix-router/benches/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,15 @@ fn compare_routers(c: &mut Criterion) {
});
});

group.bench_function("actix_guard_failures", |b| {
b.iter(|| {
for route in call() {
let mut path = actix_router::Path::new(route);
black_box(actix.recognize_fn(&mut path, |_, _| false));
}
});
});

let regex_set = regex::RegexSet::new(register!(regex)).unwrap();
group.bench_function("regex", |b| {
b.iter(|| {
Expand Down
12 changes: 6 additions & 6 deletions actix-router/src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -662,13 +662,13 @@ mod tests {
let rdef = ResourceDef::new("/{key}");

let mut path = Path::new("/%25");
rdef.capture_match_info(&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.capture_match_info(&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, "/")
Expand All @@ -679,7 +679,7 @@ mod tests {
let rdef = ResourceDef::new("/{key}/{value}");

let mut path = Path::new("/%30%25/%30%2F");
rdef.capture_match_info(&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%");
Expand All @@ -697,7 +697,7 @@ mod tests {
let rdef = ResourceDef::new("/{key}/{value}");

let mut path = Path::new("/%25/%2F");
rdef.capture_match_info(&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, "%");
Expand All @@ -714,7 +714,7 @@ mod tests {
let rdef = ResourceDef::new("/{val}");

let mut path = Path::new("/X");
rdef.capture_match_info(&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");
Expand All @@ -723,7 +723,7 @@ mod tests {
assert_eq!(params, "X");

let mut path = Path::new("/%2F");
rdef.capture_match_info(&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);
Expand Down
71 changes: 69 additions & 2 deletions actix-router/src/path.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use std::{
borrow::Cow,
mem,
ops::{DerefMut, Index},
};

use serde::{de, Deserialize};

use crate::{de::PathDeserializer, Resource, ResourcePath};
use crate::{de::PathDeserializer, resource::ResourceMatchInfo, Resource, ResourcePath};

#[derive(Debug, Clone)]
pub(crate) enum PathItem {
pub enum PathItem {
Static(Cow<'static, str>),
Segment(u16, u16),
}
Expand Down Expand Up @@ -106,6 +107,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))),
Expand Down Expand Up @@ -260,4 +282,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);
}
}
Loading
Loading