Compare commits

...

21 Commits

Author SHA1 Message Date
Manuel Garcia de la Vega Hoyo 535103ef2c
Merge 5ad04e6de7 into 9fb6c13a1a 2025-08-26 10:11:10 +02:00
Rob Ede 9fb6c13a1a
ci: fix msrv job 2025-08-26 08:26:49 +01:00
Rob Ede 05cfef7f4b
ci: fix msrv job 2025-08-26 08:18:34 +01:00
Rob Ede 8f3eb32a32
chore: fix justfile for msrv 2025-08-26 08:00:19 +01:00
Rob Ede ddd16ec9db
chore(actix-http): prepare release 3.11.1 2025-08-26 07:28:27 +01:00
dependabot[bot] 9c47a247fb
build(deps): bump bitflags from 2.9.2 to 2.9.3 (#3745)
Bumps [bitflags](https://github.com/bitflags/bitflags) from 2.9.2 to 2.9.3.
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/2.9.2...2.9.3)

---
updated-dependencies:
- dependency-name: bitflags
  dependency-version: 2.9.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-25 12:31:30 +00:00
dependabot[bot] 2536823e3b
build(deps): bump regex-lite from 0.1.6 to 0.1.7 (#3743)
Bumps [regex-lite](https://github.com/rust-lang/regex) from 0.1.6 to 0.1.7.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/regex-lite-0.1.6...regex-lite-0.1.7)

---
updated-dependencies:
- dependency-name: regex-lite
  dependency-version: 0.1.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-25 10:43:50 +00:00
dependabot[bot] e3f81d0643
build(deps): bump url from 2.5.4 to 2.5.7 (#3742)
Bumps [url](https://github.com/servo/rust-url) from 2.5.4 to 2.5.7.
- [Release notes](https://github.com/servo/rust-url/releases)
- [Commits](https://github.com/servo/rust-url/commits)

---
updated-dependencies:
- dependency-name: url
  dependency-version: 2.5.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-25 10:42:00 +00:00
dependabot[bot] a84aee836b
build(deps): bump tempfile from 3.20.0 to 3.21.0 (#3744)
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.20.0 to 3.21.0.
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/commits)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-version: 3.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-25 10:40:55 +00:00
dependabot[bot] 1f08cb24c3
build(deps): bump regex from 1.11.1 to 1.11.2 (#3746)
Bumps [regex](https://github.com/rust-lang/regex) from 1.11.1 to 1.11.2.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.11.1...1.11.2)

---
updated-dependencies:
- dependency-name: regex
  dependency-version: 1.11.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-25 10:36:40 +00:00
dependabot[bot] 1b49047086
build(deps): bump codecov/codecov-action from 5.4.3 to 5.5.0 (#3741)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.4.3 to 5.5.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](18283e04ce...fdcc847654)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: 5.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-25 10:33:15 +00:00
dependabot[bot] 9fe033a963
build(deps): bump taiki-e/install-action from 2.58.17 to 2.58.21 (#3740)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.58.17 to 2.58.21.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](ad95d4e02e...f63c33fd96)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.58.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-25 10:32:36 +00:00
dependabot[bot] 2ba69a1904
build(deps): bump actions-rust-lang/setup-rust-toolchain from 1.13.0 to 1.14.0 (#3739)
build(deps): bump actions-rust-lang/setup-rust-toolchain

Bumps [actions-rust-lang/setup-rust-toolchain](https://github.com/actions-rust-lang/setup-rust-toolchain) from 1.13.0 to 1.14.0.
- [Release notes](https://github.com/actions-rust-lang/setup-rust-toolchain/releases)
- [Changelog](https://github.com/actions-rust-lang/setup-rust-toolchain/blob/main/CHANGELOG.md)
- [Commits](fb51252c7b...ab6845274e)

---
updated-dependencies:
- dependency-name: actions-rust-lang/setup-rust-toolchain
  dependency-version: 1.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-25 10:31:31 +00:00
Manuel Garcia de la Vega Hoyo 5ad04e6de7
Merge branch 'master' into feature/mgdlvh/capture_match_info 2025-05-24 13:35:01 +02:00
manuelgdlvh 6f4bdb31f9 feat: Add code formatting 2025-05-16 22:27:19 +02:00
manuelgdlvh 4e56ae0594 feat: Add minor refactos and changelog 2025-05-16 21:46:38 +02:00
manuelgdlvh 9f2b207137 feat: Add fix to visibility warning and apply code format 2025-05-14 21:34:50 +02:00
manuelgdlvh b47530d66a feat: Add tests for max path conflicts modification 2025-05-14 21:22:09 +02:00
manuelgdlvh 55c38a625f feat: Move resolve path to Resource trait as default implementation 2025-05-14 20:53:22 +02:00
manuelgdlvh 934f68ebf6 feat: Add router bench for guard check failures 2025-05-14 20:41:45 +02:00
manuelgdlvh ffa95b7f60 feat: Add improvements in resource path resolve 2025-05-14 20:26:28 +02:00
21 changed files with 606 additions and 353 deletions

View File

@ -3,6 +3,6 @@ disallowed-names = [
"e", # no single letter error bindings
]
disallowed-methods = [
{ path = "std::cell::RefCell::default()", reason = "prefer explicit inner type default" },
{ path = "std::rc::Rc::default()", reason = "prefer explicit inner type default" },
{ path = "std::cell::RefCell::default()", reason = "prefer explicit inner type default (remove allow-invalid when rust-lang/rust-clippy/#8581 is fixed)", allow-invalid = true },
{ path = "std::rc::Rc::default()", reason = "prefer explicit inner type default (remove allow-invalid when rust-lang/rust-clippy/#8581 is fixed)", allow-invalid = true },
]

View File

@ -44,12 +44,12 @@ jobs:
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
- name: Install Rust (${{ matrix.version.name }})
uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
with:
toolchain: ${{ matrix.version.version }}
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
uses: taiki-e/install-action@ad95d4e02e061d4390c4b66ef5ed56c7fee3d2ce # v2.58.17
uses: taiki-e/install-action@f63c33fd96cc1e69a29bafd06541cf28588b43a4 # v2.58.21
with:
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
@ -80,10 +80,10 @@ jobs:
uses: rui314/setup-mold@7344740a9418dcdcb481c7df83d9fbd1d5072d7d # v1
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
- name: Install just, cargo-hack
uses: taiki-e/install-action@ad95d4e02e061d4390c4b66ef5ed56c7fee3d2ce # v2.58.17
uses: taiki-e/install-action@f63c33fd96cc1e69a29bafd06541cf28588b43a4 # v2.58.21
with:
tool: just,cargo-hack

View File

@ -59,12 +59,12 @@ jobs:
uses: rui314/setup-mold@7344740a9418dcdcb481c7df83d9fbd1d5072d7d # v1
- name: Install Rust (${{ matrix.version.name }})
uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
with:
toolchain: ${{ matrix.version.version }}
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
uses: taiki-e/install-action@ad95d4e02e061d4390c4b66ef5ed56c7fee3d2ce # v2.58.17
uses: taiki-e/install-action@f63c33fd96cc1e69a29bafd06541cf28588b43a4 # v2.58.21
with:
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
@ -92,7 +92,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
with:
toolchain: nightly
@ -108,12 +108,12 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
with:
toolchain: nightly
- name: Install just
uses: taiki-e/install-action@ad95d4e02e061d4390c4b66ef5ed56c7fee3d2ce # v2.58.17
uses: taiki-e/install-action@f63c33fd96cc1e69a29bafd06541cf28588b43a4 # v2.58.21
with:
tool: just

View File

@ -18,13 +18,13 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
with:
toolchain: nightly
components: llvm-tools
- name: Install just, cargo-llvm-cov, cargo-nextest
uses: taiki-e/install-action@ad95d4e02e061d4390c4b66ef5ed56c7fee3d2ce # v2.58.17
uses: taiki-e/install-action@f63c33fd96cc1e69a29bafd06541cf28588b43a4 # v2.58.21
with:
tool: just,cargo-llvm-cov,cargo-nextest
@ -32,7 +32,7 @@ jobs:
run: just test-coverage-codecov
- name: Upload coverage to Codecov
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
with:
files: codecov.json
fail_ci_if_error: true

View File

@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
with:
toolchain: nightly
components: rustfmt
@ -36,7 +36,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
with:
components: clippy
@ -55,7 +55,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
with:
toolchain: nightly
components: rust-docs
@ -72,12 +72,12 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Install Rust (${{ vars.RUST_VERSION_EXTERNAL_TYPES }})
uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
with:
toolchain: ${{ vars.RUST_VERSION_EXTERNAL_TYPES }}
- name: Install just
uses: taiki-e/install-action@ad95d4e02e061d4390c4b66ef5ed56c7fee3d2ce # v2.58.17
uses: taiki-e/install-action@f63c33fd96cc1e69a29bafd06541cf28588b43a4 # v2.58.21
with:
tool: just

481
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,11 @@
## Unreleased
- Update `TestRequest::set_payload` to generate "Content-Length" header
- Malformed websocket frames are now gracefully rejected.
## 3.11.1
- Prevent more hangs after client disconnects.
- More malformed WebSocket frames are now gracefully rejected.
- Using `TestRequest::set_payload()` now sets a Content-Length header.
## 3.11.0

View File

@ -1,6 +1,6 @@
[package]
name = "actix-http"
version = "3.11.0"
version = "3.11.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rob Ede <robjtede@icloud.com>"]
description = "HTTP types and services for the Actix ecosystem"
keywords = ["actix", "http", "framework", "async", "futures"]

View File

@ -5,11 +5,11 @@
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.11.0)](https://docs.rs/actix-http/3.11.0)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.11.1)](https://docs.rs/actix-http/3.11.1)
![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-http/3.11.0/status.svg)](https://deps.rs/crate/actix-http/3.11.0)
[![dependency status](https://deps.rs/crate/actix-http/3.11.1/status.svg)](https://deps.rs/crate/actix-http/3.11.1)
[![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

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

@ -13,6 +13,7 @@ macro_rules! register {
register!(finish => "(.*)", "(.*)", "(.*)", "(.*)")
}};
(finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{
#[expect(clippy::useless_concat)]
let arr = [
concat!("/authorizations"),
concat!("/authorizations/", $p1),
@ -179,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(|| {

View File

@ -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, "/")
@ -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%");
@ -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, "%");
@ -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");
@ -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);

View File

@ -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),
}
@ -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))),
@ -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);
}
}

View File

@ -2,7 +2,6 @@ use std::{
borrow::{Borrow, Cow},
collections::HashMap,
hash::{BuildHasher, Hash, Hasher},
mem,
};
use tracing::error;
@ -10,7 +9,7 @@ use tracing::error;
use crate::{
path::PathItem,
regex_set::{escape, Regex, RegexSet},
IntoPatterns, Patterns, Resource, ResourcePath,
IntoPatterns, Patterns, Resource,
};
const MAX_DYNAMIC_SEGMENTS: usize = 16;
@ -80,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.
///
@ -96,7 +94,7 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!resource.is_match("/user/"));
///
/// let mut path = Path::new("/user/123");
/// resource.capture_match_info(&mut path);
/// resource.resolve_path_if_match(&mut path);
/// assert_eq!(path.get("id").unwrap(), "123");
/// ```
///
@ -171,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.capture_match_info(&mut path);
/// resource.resolve_path_if_match(&mut path);
/// assert_eq!(path.get("tail").unwrap(), "main/LICENSE");
/// ```
///
@ -249,6 +247,18 @@ 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,
},
Dynamic {
matched_len: u16,
matched_vars: &'a [&'static str],
segments: [PathItem; MAX_DYNAMIC_SEGMENTS],
},
}
impl ResourceDef {
/// Constructs a new resource definition from patterns.
///
@ -623,18 +633,24 @@ impl ResourceDef {
///
/// let resource = ResourceDef::prefix("/user/{id}");
/// let mut path = Path::new("/user/123/stars");
/// assert!(resource.capture_match_info(&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.capture_match_info(&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 capture_match_info<R: Resource>(&self, resource: &mut R) -> bool {
self.capture_match_info_fn(resource, |_| true)
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.resource_path().resolve(match_info);
true
}
}
}
/// Collects dynamic segment values into `resource` after matching paths and executing
@ -644,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}");
@ -669,85 +686,72 @@ 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_fn<R, F>(&self, resource: &mut R, check_fn: F) -> bool
pub fn capture_match_info<R>(&self, resource: &mut R) -> Option<ResourceMatchInfo<'_>>
where
R: Resource,
F: FnOnce(&R) -> bool,
{
let mut segments = <[PathItem; MAX_DYNAMIC_SEGMENTS]>::default();
let path = resource.resource_path();
let path_str = path.unprocessed();
let (matched_len, matched_vars) = match &self.pat_type {
match &self.pat_type {
PatternType::Static(pattern) => match self.static_match(pattern, path_str) {
Some(len) => (len, None),
None => return false,
Some(len) => Some(ResourceMatchInfo::Static {
matched_len: len as u16,
}),
None => return None,
},
PatternType::Dynamic(re, names) => {
let captures = match re.captures(path.unprocessed()) {
let captures = match re.captures(path_str) {
Some(captures) => captures,
_ => return false,
_ => return None,
};
let mut segments = <[PathItem; MAX_DYNAMIC_SEGMENTS]>::default();
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 {
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) => {
let path = path.unprocessed();
let (pattern, names) = match re.first_match_idx(path) {
let (pattern, names) = match re.first_match_idx(path_str) {
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,
_ => return false,
_ => return None,
};
let mut segments = <[PathItem; MAX_DYNAMIC_SEGMENTS]>::default();
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 {
error!("Dynamic path match but not all segments found: {}", name);
return false;
return None;
}
}
(captures[1].len(), Some(names))
}
};
if !check_fn(resource) {
return false;
}
// Modify `path` to skip matched part and store matched segments
let path = resource.resource_path();
if let Some(vars) = matched_vars {
for i in 0..vars.len() {
path.add(vars[i], mem::take(&mut segments[i]));
Some(ResourceMatchInfo::Dynamic {
matched_len: captures[1].len() as u16,
matched_vars: names,
segments,
})
}
}
path.skip(matched_len as u16);
true
}
/// Assembles resource path using a closure that maps variable segment names to values.
@ -1171,7 +1175,7 @@ mod tests {
assert!(!re.is_match("/name~"));
let mut path = Path::new("/name");
assert!(re.capture_match_info(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.unprocessed(), "");
assert_eq!(re.find_match("/name"), Some(5));
@ -1189,7 +1193,7 @@ mod tests {
assert!(!re.is_match("/user/profile/profile"));
let mut path = Path::new("/user/profile");
assert!(re.capture_match_info(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.unprocessed(), "");
}
@ -1202,12 +1206,12 @@ mod tests {
assert!(!re.is_match("/user/2345/sdg"));
let mut path = Path::new("/user/profile");
assert!(re.capture_match_info(&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.capture_match_info(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "1245125");
assert_eq!(path.unprocessed(), "");
@ -1217,7 +1221,7 @@ mod tests {
assert!(!re.is_match("/resource"));
let mut path = Path::new("/v151/resource/adage32");
assert!(re.capture_match_info(&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(), "");
@ -1229,7 +1233,7 @@ mod tests {
assert!(!re.is_match("/XXXXXX"));
let mut path = Path::new("/012345");
assert!(re.capture_match_info(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "012345");
assert_eq!(path.unprocessed(), "");
}
@ -1249,12 +1253,12 @@ mod tests {
assert!(!re.is_match("/user/2345/sdg"));
let mut path = Path::new("/user/profile");
assert!(re.capture_match_info(&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.capture_match_info(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "1245125");
assert_eq!(path.unprocessed(), "");
@ -1263,7 +1267,7 @@ mod tests {
assert!(!re.is_match("/resource"));
let mut path = Path::new("/v151/resource/adage32");
assert!(re.capture_match_info(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("version").unwrap(), "151");
assert_eq!(path.get("id").unwrap(), "adage32");
@ -1277,7 +1281,7 @@ mod tests {
assert!(!re.is_match("/static/a"));
let mut path = Path::new("/012345");
assert!(re.capture_match_info(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "012345");
let re = ResourceDef::new([
@ -1314,7 +1318,7 @@ mod tests {
assert_eq!(re.find_match("/12345"), None);
let mut path = Path::new("/151/res");
assert!(re.capture_match_info(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "151");
assert_eq!(path.unprocessed(), "/res");
}
@ -1324,19 +1328,19 @@ mod tests {
let re = ResourceDef::new("/user/-{id}*");
let mut path = Path::new("/user/-profile");
assert!(re.capture_match_info(&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.capture_match_info(&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.capture_match_info(&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.capture_match_info(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "2345/sdg");
}
@ -1364,7 +1368,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.capture_match_info(&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(), "");
@ -1379,7 +1383,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.capture_match_info(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("x").unwrap(), "\n");
assert_eq!(path.get("y").unwrap(), "\n");
@ -1388,12 +1392,12 @@ mod tests {
let re = ResourceDef::new("/user/{id}*");
let mut path = Path::new("/user/a\nb/a\nb");
assert!(re.capture_match_info(&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.capture_match_info(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "a\nb/a\nb");
}
@ -1403,16 +1407,16 @@ mod tests {
let re = ResourceDef::new("/user/{id}/test");
let mut path = Path::new("/user/2345/test");
assert!(re.capture_match_info(&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.capture_match_info(&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.capture_match_info(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.get("id").unwrap(), "qwe%25");
}
@ -1429,11 +1433,11 @@ mod tests {
assert!(!re.is_match("/name~"));
let mut path = Path::new("/name");
assert!(re.capture_match_info(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.unprocessed(), "");
let mut path = Path::new("/name/test");
assert!(re.capture_match_info(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.unprocessed(), "/test");
assert_eq!(re.find_match("/name"), Some(5));
@ -1449,10 +1453,10 @@ mod tests {
assert!(!re.is_match("/name"));
let mut path = Path::new("/name/gs");
assert!(!re.capture_match_info(&mut path));
assert!(!re.resolve_path_if_match(&mut path));
let mut path = Path::new("/name//gs");
assert!(re.capture_match_info(&mut path));
assert!(re.resolve_path_if_match(&mut path));
assert_eq!(path.unprocessed(), "/gs");
let re = ResourceDef::root_prefix("name/");
@ -1462,7 +1466,7 @@ mod tests {
assert!(!re.is_match("/name"));
let mut path = Path::new("/name/gs");
assert!(!re.capture_match_info(&mut path));
assert!(!re.resolve_path_if_match(&mut path));
}
#[test]
@ -1481,13 +1485,13 @@ mod tests {
assert_eq!(re.find_match(""), None);
let mut path = Path::new("/test2/");
assert!(re.capture_match_info(&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.capture_match_info(&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");
@ -1543,7 +1547,7 @@ mod tests {
assert!(resource.resource_path_from_iter(
&mut s,
#[allow(clippy::useless_vec)]
&mut vec!["item", "item2"].iter()
&mut vec!["item", "item2"].iter(),
));
assert_eq!(s, "/user/item/item2/");
}
@ -1561,22 +1565,22 @@ mod tests {
let resource = ResourceDef::new(["/user/{id}", "/profile/{id}"]);
let mut path = Path::new("/user/123");
assert!(resource.capture_match_info(&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.capture_match_info(&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.capture_match_info(&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.capture_match_info(&mut path));
assert!(resource.resolve_path_if_match(&mut path));
assert!(path.get("id").is_none());
assert!(path.get("uid").is_some());
}

View File

@ -13,12 +13,16 @@ pub struct ResourceId(pub u16);
/// not required.
pub struct Router<T, U = ()> {
routes: Vec<(ResourceDef, T, U)>,
max_path_conflicts: u16,
}
impl<T, U> Router<T, U> {
/// Constructs new `RouterBuilder` with empty route list.
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).
@ -46,14 +50,24 @@ impl<T, U> Router<T, U> {
/// 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
/// 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
R: Resource,
F: FnMut(&R, &U) -> bool,
{
let mut next_resource_match_count = 1;
for (rdef, val, ctx) in self.routes.iter() {
if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) {
return Some((val, ResourceId(rdef.id())));
match rdef.capture_match_info(resource) {
None => {}
Some(match_info) => {
if check_fn(resource, ctx) {
resource.resource_path().resolve(match_info);
return Some((val, ResourceId(rdef.id())));
} else if next_resource_match_count == self.max_path_conflicts {
return None;
}
next_resource_match_count += 1;
}
}
}
@ -65,15 +79,25 @@ impl<T, U> Router<T, U> {
pub fn recognize_mut_fn<R, F>(
&mut self,
resource: &mut R,
mut check: F,
mut check_fn: F,
) -> Option<(&mut T, ResourceId)>
where
R: Resource,
F: FnMut(&R, &U) -> bool,
{
let mut matches = 0;
for (rdef, val, ctx) in self.routes.iter_mut() {
if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) {
return Some((val, ResourceId(rdef.id())));
match rdef.capture_match_info(resource) {
None => {}
Some(match_info) => {
matches += 1;
if check_fn(resource, ctx) {
resource.resource_path().resolve(match_info);
return Some((val, ResourceId(rdef.id())));
} else if matches == self.max_path_conflicts {
return None;
}
}
}
}
@ -84,6 +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<(usize, u16)>,
}
impl<T, U> RouterBuilder<T, U> {
@ -96,7 +121,18 @@ impl<T, U> RouterBuilder<T, U> {
val: T,
ctx: U,
) -> (&mut ResourceDef, &mut T, &mut U) {
if let Some((_, path_conflicts)) = self
.path_conflicts
.iter_mut()
.find(|(route_idx, _)| rdef.eq(&self.routes.get(*route_idx).unwrap().0))
{
*path_conflicts += 1;
} else {
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()
@ -106,8 +142,15 @@ 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()
.map(|(_, path_conflicts)| *path_conflicts)
.max()
.unwrap_or(1);
Router {
routes: self.routes,
max_path_conflicts,
}
}
}
@ -280,4 +323,42 @@ mod tests {
assert_eq!(*h, 11);
assert_eq!(&path["val"], "ttt");
}
#[test]
fn test_max_path_conflicts() {
let mut router = Router::<usize>::build();
router.path("/test", 10).0.set_id(0);
router.path("/test/{val}", 11).0.set_id(1);
let router = router.finish();
assert_eq!(1, router.max_path_conflicts);
let mut router = Router::<usize>::build();
router.path("/test", 10).0.set_id(0);
router.path("/test", 11).0.set_id(1);
router.path("/test2", 11).0.set_id(1);
router.path("/test2", 11).0.set_id(1);
router.path("/test2", 11).0.set_id(1);
let router = router.finish();
assert_eq!(3, router.max_path_conflicts);
let failures_until_fn_builder = |mut num_failures: u16| {
move |_: &Path<&str>, _: &()| {
if num_failures == 0 {
return true;
}
num_failures -= 1;
false
}
};
assert!(router
.recognize_fn(&mut Path::new("/test2"), failures_until_fn_builder(3))
.is_none());
assert!(router
.recognize_fn(&mut Path::new("/test2"), failures_until_fn_builder(2))
.is_some());
}
}

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.capture_match_info(&mut path));
assert!(re.resolve_path_if_match(&mut path));
path
}

View File

@ -3,7 +3,6 @@
- The return type for `ServiceRequest::app_data::<T>()` was changed from returning a `Data<T>` to simply a `T`. To access a `Data<T>` use `ServiceRequest::app_data::<Data<T>>()`.
- Cookie handling has been offloaded to the `cookie` crate:
- `USERINFO_ENCODE_SET` is no longer exposed. Percent-encoding is still supported; check docs.
- Some types now require lifetime parameters.

View File

@ -306,16 +306,16 @@ impl Service<ServiceRequest> for AppRouting {
actix_service::always_ready!();
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();
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 {
srv.call(req)
} else {
self.default.call(req)
return srv.call(req);
}
self.default.call(req)
}
}

View File

@ -510,16 +510,16 @@ impl Service<ServiceRequest> for ScopeService {
actix_service::always_ready!();
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();
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 {
srv.call(req)
} else {
self.default.call(req)
return srv.call(req);
}
self.default.call(req)
}
}

View File

@ -176,7 +176,7 @@ mod tests {
let resource = ResourceDef::new("/{value}/");
let mut req = TestRequest::with_uri("/32/").to_srv_request();
resource.capture_match_info(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.capture_match_info(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.capture_match_info(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.capture_match_info(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.capture_match_info(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();

View File

@ -13,6 +13,8 @@ fmt:
[private]
downgrade-for-msrv:
cargo {{ toolchain }} update -p=divan --precise=0.1.15 # next ver: 1.80.0
cargo {{ toolchain }} update -p=rayon --precise=1.10.0 # next ver: 1.80.0
cargo {{ toolchain }} update -p=rayon-core --precise=1.12.1 # next ver: 1.80.0
cargo {{ toolchain }} update -p=half --precise=2.4.1 # next ver: 1.81.0
cargo {{ toolchain }} update -p=idna_adapter --precise=1.2.0 # next ver: 1.82.0
cargo {{ toolchain }} update -p=litemap --precise=0.7.4 # next ver: 1.81.0
@ -50,8 +52,7 @@ clippy:
cargo {{ toolchain }} clippy --workspace --all-targets {{ all_crate_features }}
# Run Clippy over workspace using MSRV.
clippy-msrv:
@just toolchain={{ msrv_rustup }} downgrade-for-msrv
clippy-msrv: downgrade-for-msrv
@just toolchain={{ msrv_rustup }} clippy
# Test workspace code.
@ -62,8 +63,7 @@ test:
cargo {{ toolchain }} nextest run --no-tests=warn --workspace --exclude=actix-web-codegen --exclude=actix-multipart-derive {{ all_crate_features }} --filter-expr="not test(test_reading_deflate_encoding_large_random_rustls)"
# Test workspace using MSRV.
test-msrv:
@just toolchain={{ msrv_rustup }} downgrade-for-msrv
test-msrv: downgrade-for-msrv
@just toolchain={{ msrv_rustup }} test
# Test workspace docs.