{
type Target = T;
fn deref(&self) -> &Self::Target {
- &self.head.as_ref()
+ self.head.as_ref()
}
}
diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs
index 09c6dd296..401e9745c 100644
--- a/actix-http/src/request.rs
+++ b/actix-http/src/request.rs
@@ -15,7 +15,7 @@ use crate::{
HttpMessage,
};
-/// Request
+/// An HTTP request.
pub struct Request {
pub(crate) payload: Payload
,
pub(crate) head: Message,
diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md
index 0b6affa3c..1e768ddf5 100644
--- a/actix-multipart/CHANGES.md
+++ b/actix-multipart/CHANGES.md
@@ -1,6 +1,7 @@
# Changes
## Unreleased - 2021-xx-xx
+* Minimum supported Rust version (MSRV) is now 1.51.
## 0.4.0-beta.5 - 2021-06-17
diff --git a/actix-multipart/README.md b/actix-multipart/README.md
index 78855b815..aed16721c 100644
--- a/actix-multipart/README.md
+++ b/actix-multipart/README.md
@@ -4,7 +4,7 @@
[](https://crates.io/crates/actix-multipart)
[](https://docs.rs/actix-multipart/0.4.0-beta.5)
-[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
+[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)

[](https://deps.rs/crate/actix-multipart/0.4.0-beta.5)
@@ -14,4 +14,4 @@
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-multipart)
-- Minimum Supported Rust Version (MSRV): 1.46.0
+- Minimum Supported Rust Version (MSRV): 1.51.0
diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md
new file mode 100644
index 000000000..804f7778d
--- /dev/null
+++ b/actix-router/CHANGES.md
@@ -0,0 +1,124 @@
+# Changes
+
+## Unreleased - 2021-xx-xx
+* Introduce `ResourceDef::join`. [#380]
+* Disallow prefix routes with tail segments. [#379]
+* Enforce path separators on dynamic prefixes. [#378]
+* Improve malformed path error message. [#384]
+* Prefix segments now always end with with a segment delimiter or end-of-input. [#2355]
+* Prefix segments with trailing slashes define a trailing empty segment. [#2355]
+* Minimum supported Rust version (MSRV) is now 1.51.
+
+[#378]: https://github.com/actix/actix-net/pull/378
+[#379]: https://github.com/actix/actix-net/pull/379
+[#380]: https://github.com/actix/actix-net/pull/380
+[#384]: https://github.com/actix/actix-net/pull/384
+[#2355]: https://github.com/actix/actix-web/pull/2355
+
+
+## 0.5.0-beta.1 - 2021-07-20
+* Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366]
+* Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373]
+* Fix segment interpolation leaving `Path` in unintended state after matching. [#368]
+* Fix `ResourceDef` `PartialEq` implementation. [#373]
+* Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372]
+* Implement `IntoPatterns` for `bytestring::ByteString`. [#372]
+* Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370]
+* Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371]
+* `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373]
+* Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371]
+* Rename `ResourceDef::{is_prefix_match => find_match}`. [#373]
+* Rename `ResourceDef::{match_path => capture_match_info}`. [#373]
+* Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373]
+* Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373]
+* Rename `Router::{*_checked => *_fn}`. [#373]
+* Return type of `ResourceDef::name` is now `Option<&str>`. [#373]
+* Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373]
+
+[#368]: https://github.com/actix/actix-net/pull/368
+[#366]: https://github.com/actix/actix-net/pull/366
+[#368]: https://github.com/actix/actix-net/pull/368
+[#370]: https://github.com/actix/actix-net/pull/370
+[#371]: https://github.com/actix/actix-net/pull/371
+[#372]: https://github.com/actix/actix-net/pull/372
+[#373]: https://github.com/actix/actix-net/pull/373
+
+
+## 0.4.0 - 2021-06-06
+* When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357]
+* Path tail patterns now match new lines (`\n`) in request URL. [#360]
+* Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359]
+* Methods `Path::{add, add_static}` now take `impl Into>`. [#345]
+
+[#345]: https://github.com/actix/actix-net/pull/345
+[#357]: https://github.com/actix/actix-net/pull/357
+[#359]: https://github.com/actix/actix-net/pull/359
+[#360]: https://github.com/actix/actix-net/pull/360
+
+
+## 0.3.0 - 2019-12-31
+* Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0
+
+
+## 0.2.7 - 2021-02-06
+* Add `Router::recognize_checked` [#247]
+
+[#247]: https://github.com/actix/actix-net/pull/247
+
+
+## 0.2.6 - 2021-01-09
+* Use `bytestring` version range compatible with Bytes v1.0. [#246]
+
+[#246]: https://github.com/actix/actix-net/pull/246
+
+
+## 0.2.5 - 2020-09-20
+* Fix `from_hex()` method
+
+
+## 0.2.4 - 2019-12-31
+* Add `ResourceDef::resource_path_named()` path generation method
+
+
+## 0.2.3 - 2019-12-25
+* Add impl `IntoPattern` for `&String`
+
+
+## 0.2.2 - 2019-12-25
+* Use `IntoPattern` for `RouterBuilder::path()`
+
+
+## 0.2.1 - 2019-12-25
+* Add `IntoPattern` trait
+* Add multi-pattern resources
+
+
+## 0.2.0 - 2019-12-07
+* Update http to 0.2
+* Update regex to 1.3
+* Use bytestring instead of string
+
+
+## 0.1.5 - 2019-05-15
+* Remove debug prints
+
+
+## 0.1.4 - 2019-05-15
+* Fix checked resource match
+
+
+## 0.1.3 - 2019-04-22
+* Added support for `remainder match` (i.e "/path/{tail}*")
+
+
+## 0.1.2 - 2019-04-07
+* Export `Quoter` type
+* Allow to reset `Path` instance
+
+
+## 0.1.1 - 2019-04-03
+* Get dynamic segment by name instead of iterator.
+
+
+## 0.1.0 - 2019-03-09
+* Initial release
diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml
new file mode 100644
index 000000000..2a2ce1cc1
--- /dev/null
+++ b/actix-router/Cargo.toml
@@ -0,0 +1,38 @@
+[package]
+name = "actix-router"
+version = "0.5.0-beta.1"
+authors = [
+ "Nikolay Kim ",
+ "Ali MJ Al-Nasrawy ",
+ "Rob Ede ",
+]
+description = "Resource path matching and router"
+keywords = ["actix", "router", "routing"]
+repository = "https://github.com/actix/actix-net.git"
+license = "MIT OR Apache-2.0"
+edition = "2018"
+
+[lib]
+name = "actix_router"
+path = "src/lib.rs"
+
+[features]
+default = ["http"]
+
+[dependencies]
+bytestring = ">=0.1.5, <2"
+firestorm = "0.4"
+http = { version = "0.2.3", optional = true }
+log = "0.4"
+regex = "1.5"
+serde = "1"
+
+[dev-dependencies]
+criterion = { version = "0.3", features = ["html_reports"] }
+firestorm = { version = "0.4", features = ["enable_system_time"] }
+http = "0.2.3"
+serde = { version = "1", features = ["derive"] }
+
+[[bench]]
+name = "router"
+harness = false
diff --git a/actix-router/LICENSE-APACHE b/actix-router/LICENSE-APACHE
new file mode 120000
index 000000000..965b606f3
--- /dev/null
+++ b/actix-router/LICENSE-APACHE
@@ -0,0 +1 @@
+../LICENSE-APACHE
\ No newline at end of file
diff --git a/actix-router/LICENSE-MIT b/actix-router/LICENSE-MIT
new file mode 120000
index 000000000..76219eb72
--- /dev/null
+++ b/actix-router/LICENSE-MIT
@@ -0,0 +1 @@
+../LICENSE-MIT
\ No newline at end of file
diff --git a/actix-router/benches/router.rs b/actix-router/benches/router.rs
new file mode 100644
index 000000000..a428b9f13
--- /dev/null
+++ b/actix-router/benches/router.rs
@@ -0,0 +1,194 @@
+//! Based on https://github.com/ibraheemdev/matchit/blob/master/benches/bench.rs
+
+use criterion::{black_box, criterion_group, criterion_main, Criterion};
+
+macro_rules! register {
+ (colon) => {{
+ register!(finish => ":p1", ":p2", ":p3", ":p4")
+ }};
+ (brackets) => {{
+ register!(finish => "{p1}", "{p2}", "{p3}", "{p4}")
+ }};
+ (regex) => {{
+ register!(finish => "(.*)", "(.*)", "(.*)", "(.*)")
+ }};
+ (finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{
+ let arr = [
+ concat!("/authorizations"),
+ concat!("/authorizations/", $p1),
+ concat!("/applications/", $p1, "/tokens/", $p2),
+ concat!("/events"),
+ concat!("/repos/", $p1, "/", $p2, "/events"),
+ concat!("/networks/", $p1, "/", $p2, "/events"),
+ concat!("/orgs/", $p1, "/events"),
+ concat!("/users/", $p1, "/received_events"),
+ concat!("/users/", $p1, "/received_events/public"),
+ concat!("/users/", $p1, "/events"),
+ concat!("/users/", $p1, "/events/public"),
+ concat!("/users/", $p1, "/events/orgs/", $p2),
+ concat!("/feeds"),
+ concat!("/notifications"),
+ concat!("/repos/", $p1, "/", $p2, "/notifications"),
+ concat!("/notifications/threads/", $p1),
+ concat!("/notifications/threads/", $p1, "/subscription"),
+ concat!("/repos/", $p1, "/", $p2, "/stargazers"),
+ concat!("/users/", $p1, "/starred"),
+ concat!("/user/starred"),
+ concat!("/user/starred/", $p1, "/", $p2),
+ concat!("/repos/", $p1, "/", $p2, "/subscribers"),
+ concat!("/users/", $p1, "/subscriptions"),
+ concat!("/user/subscriptions"),
+ concat!("/repos/", $p1, "/", $p2, "/subscription"),
+ concat!("/user/subscriptions/", $p1, "/", $p2),
+ concat!("/users/", $p1, "/gists"),
+ concat!("/gists"),
+ concat!("/gists/", $p1),
+ concat!("/gists/", $p1, "/star"),
+ concat!("/repos/", $p1, "/", $p2, "/git/blobs/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/git/commits/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/git/refs"),
+ concat!("/repos/", $p1, "/", $p2, "/git/tags/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/git/trees/", $p3),
+ concat!("/issues"),
+ concat!("/user/issues"),
+ concat!("/orgs/", $p1, "/issues"),
+ concat!("/repos/", $p1, "/", $p2, "/issues"),
+ concat!("/repos/", $p1, "/", $p2, "/issues/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/assignees"),
+ concat!("/repos/", $p1, "/", $p2, "/assignees/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/comments"),
+ concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/events"),
+ concat!("/repos/", $p1, "/", $p2, "/labels"),
+ concat!("/repos/", $p1, "/", $p2, "/labels/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/labels"),
+ concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3, "/labels"),
+ concat!("/repos/", $p1, "/", $p2, "/milestones/"),
+ concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3),
+ concat!("/emojis"),
+ concat!("/gitignore/templates"),
+ concat!("/gitignore/templates/", $p1),
+ concat!("/meta"),
+ concat!("/rate_limit"),
+ concat!("/users/", $p1, "/orgs"),
+ concat!("/user/orgs"),
+ concat!("/orgs/", $p1),
+ concat!("/orgs/", $p1, "/members"),
+ concat!("/orgs/", $p1, "/members", $p2),
+ concat!("/orgs/", $p1, "/public_members"),
+ concat!("/orgs/", $p1, "/public_members/", $p2),
+ concat!("/orgs/", $p1, "/teams"),
+ concat!("/teams/", $p1),
+ concat!("/teams/", $p1, "/members"),
+ concat!("/teams/", $p1, "/members", $p2),
+ concat!("/teams/", $p1, "/repos"),
+ concat!("/teams/", $p1, "/repos/", $p2, "/", $p3),
+ concat!("/user/teams"),
+ concat!("/repos/", $p1, "/", $p2, "/pulls"),
+ concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/commits"),
+ concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/files"),
+ concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/merge"),
+ concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/comments"),
+ concat!("/user/repos"),
+ concat!("/users/", $p1, "/repos"),
+ concat!("/orgs/", $p1, "/repos"),
+ concat!("/repositories"),
+ concat!("/repos/", $p1, "/", $p2),
+ concat!("/repos/", $p1, "/", $p2, "/contributors"),
+ concat!("/repos/", $p1, "/", $p2, "/languages"),
+ concat!("/repos/", $p1, "/", $p2, "/teams"),
+ concat!("/repos/", $p1, "/", $p2, "/tags"),
+ concat!("/repos/", $p1, "/", $p2, "/branches"),
+ concat!("/repos/", $p1, "/", $p2, "/branches/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/collaborators"),
+ concat!("/repos/", $p1, "/", $p2, "/collaborators/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/comments"),
+ concat!("/repos/", $p1, "/", $p2, "/commits/", $p3, "/comments"),
+ concat!("/repos/", $p1, "/", $p2, "/commits"),
+ concat!("/repos/", $p1, "/", $p2, "/commits/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/readme"),
+ concat!("/repos/", $p1, "/", $p2, "/keys"),
+ concat!("/repos/", $p1, "/", $p2, "/keys", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/downloads"),
+ concat!("/repos/", $p1, "/", $p2, "/downloads", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/forks"),
+ concat!("/repos/", $p1, "/", $p2, "/hooks"),
+ concat!("/repos/", $p1, "/", $p2, "/hooks", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/releases"),
+ concat!("/repos/", $p1, "/", $p2, "/releases/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/releases/", $p3, "/assets"),
+ concat!("/repos/", $p1, "/", $p2, "/stats/contributors"),
+ concat!("/repos/", $p1, "/", $p2, "/stats/commit_activity"),
+ concat!("/repos/", $p1, "/", $p2, "/stats/code_frequency"),
+ concat!("/repos/", $p1, "/", $p2, "/stats/participation"),
+ concat!("/repos/", $p1, "/", $p2, "/stats/punch_card"),
+ concat!("/repos/", $p1, "/", $p2, "/statuses/", $p3),
+ concat!("/search/repositories"),
+ concat!("/search/code"),
+ concat!("/search/issues"),
+ concat!("/search/users"),
+ concat!("/legacy/issues/search/", $p1, "/", $p2, "/", $p3, "/", $p4),
+ concat!("/legacy/repos/search/", $p1),
+ concat!("/legacy/user/search/", $p1),
+ concat!("/legacy/user/email/", $p1),
+ concat!("/users/", $p1),
+ concat!("/user"),
+ concat!("/users"),
+ concat!("/user/emails"),
+ concat!("/users/", $p1, "/followers"),
+ concat!("/user/followers"),
+ concat!("/users/", $p1, "/following"),
+ concat!("/user/following"),
+ concat!("/user/following/", $p1),
+ concat!("/users/", $p1, "/following", $p2),
+ concat!("/users/", $p1, "/keys"),
+ concat!("/user/keys"),
+ concat!("/user/keys/", $p1),
+ ];
+ std::array::IntoIter::new(arr)
+ }};
+}
+
+fn call() -> impl Iterator- {
+ let arr = [
+ "/authorizations",
+ "/user/repos",
+ "/repos/rust-lang/rust/stargazers",
+ "/orgs/rust-lang/public_members/nikomatsakis",
+ "/repos/rust-lang/rust/releases/1.51.0",
+ ];
+
+ std::array::IntoIter::new(arr)
+}
+
+fn compare_routers(c: &mut Criterion) {
+ let mut group = c.benchmark_group("Compare Routers");
+
+ let mut actix = actix_router::Router::::build();
+ for route in register!(brackets) {
+ actix.path(route, true);
+ }
+ let actix = actix.finish();
+ group.bench_function("actix", |b| {
+ b.iter(|| {
+ for route in call() {
+ let mut path = actix_router::Path::new(route);
+ black_box(actix.recognize(&mut path).unwrap());
+ }
+ });
+ });
+
+ let regex_set = regex::RegexSet::new(register!(regex)).unwrap();
+ group.bench_function("regex", |b| {
+ b.iter(|| {
+ for route in call() {
+ black_box(regex_set.matches(route));
+ }
+ });
+ });
+
+ group.finish();
+}
+
+criterion_group!(benches, compare_routers);
+criterion_main!(benches);
diff --git a/actix-router/examples/flamegraph.rs b/actix-router/examples/flamegraph.rs
new file mode 100644
index 000000000..798cc22d9
--- /dev/null
+++ b/actix-router/examples/flamegraph.rs
@@ -0,0 +1,169 @@
+macro_rules! register {
+ (brackets) => {{
+ register!(finish => "{p1}", "{p2}", "{p3}", "{p4}")
+ }};
+ (finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{
+ let arr = [
+ concat!("/authorizations"),
+ concat!("/authorizations/", $p1),
+ concat!("/applications/", $p1, "/tokens/", $p2),
+ concat!("/events"),
+ concat!("/repos/", $p1, "/", $p2, "/events"),
+ concat!("/networks/", $p1, "/", $p2, "/events"),
+ concat!("/orgs/", $p1, "/events"),
+ concat!("/users/", $p1, "/received_events"),
+ concat!("/users/", $p1, "/received_events/public"),
+ concat!("/users/", $p1, "/events"),
+ concat!("/users/", $p1, "/events/public"),
+ concat!("/users/", $p1, "/events/orgs/", $p2),
+ concat!("/feeds"),
+ concat!("/notifications"),
+ concat!("/repos/", $p1, "/", $p2, "/notifications"),
+ concat!("/notifications/threads/", $p1),
+ concat!("/notifications/threads/", $p1, "/subscription"),
+ concat!("/repos/", $p1, "/", $p2, "/stargazers"),
+ concat!("/users/", $p1, "/starred"),
+ concat!("/user/starred"),
+ concat!("/user/starred/", $p1, "/", $p2),
+ concat!("/repos/", $p1, "/", $p2, "/subscribers"),
+ concat!("/users/", $p1, "/subscriptions"),
+ concat!("/user/subscriptions"),
+ concat!("/repos/", $p1, "/", $p2, "/subscription"),
+ concat!("/user/subscriptions/", $p1, "/", $p2),
+ concat!("/users/", $p1, "/gists"),
+ concat!("/gists"),
+ concat!("/gists/", $p1),
+ concat!("/gists/", $p1, "/star"),
+ concat!("/repos/", $p1, "/", $p2, "/git/blobs/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/git/commits/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/git/refs"),
+ concat!("/repos/", $p1, "/", $p2, "/git/tags/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/git/trees/", $p3),
+ concat!("/issues"),
+ concat!("/user/issues"),
+ concat!("/orgs/", $p1, "/issues"),
+ concat!("/repos/", $p1, "/", $p2, "/issues"),
+ concat!("/repos/", $p1, "/", $p2, "/issues/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/assignees"),
+ concat!("/repos/", $p1, "/", $p2, "/assignees/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/comments"),
+ concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/events"),
+ concat!("/repos/", $p1, "/", $p2, "/labels"),
+ concat!("/repos/", $p1, "/", $p2, "/labels/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/labels"),
+ concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3, "/labels"),
+ concat!("/repos/", $p1, "/", $p2, "/milestones/"),
+ concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3),
+ concat!("/emojis"),
+ concat!("/gitignore/templates"),
+ concat!("/gitignore/templates/", $p1),
+ concat!("/meta"),
+ concat!("/rate_limit"),
+ concat!("/users/", $p1, "/orgs"),
+ concat!("/user/orgs"),
+ concat!("/orgs/", $p1),
+ concat!("/orgs/", $p1, "/members"),
+ concat!("/orgs/", $p1, "/members", $p2),
+ concat!("/orgs/", $p1, "/public_members"),
+ concat!("/orgs/", $p1, "/public_members/", $p2),
+ concat!("/orgs/", $p1, "/teams"),
+ concat!("/teams/", $p1),
+ concat!("/teams/", $p1, "/members"),
+ concat!("/teams/", $p1, "/members", $p2),
+ concat!("/teams/", $p1, "/repos"),
+ concat!("/teams/", $p1, "/repos/", $p2, "/", $p3),
+ concat!("/user/teams"),
+ concat!("/repos/", $p1, "/", $p2, "/pulls"),
+ concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/commits"),
+ concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/files"),
+ concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/merge"),
+ concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/comments"),
+ concat!("/user/repos"),
+ concat!("/users/", $p1, "/repos"),
+ concat!("/orgs/", $p1, "/repos"),
+ concat!("/repositories"),
+ concat!("/repos/", $p1, "/", $p2),
+ concat!("/repos/", $p1, "/", $p2, "/contributors"),
+ concat!("/repos/", $p1, "/", $p2, "/languages"),
+ concat!("/repos/", $p1, "/", $p2, "/teams"),
+ concat!("/repos/", $p1, "/", $p2, "/tags"),
+ concat!("/repos/", $p1, "/", $p2, "/branches"),
+ concat!("/repos/", $p1, "/", $p2, "/branches/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/collaborators"),
+ concat!("/repos/", $p1, "/", $p2, "/collaborators/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/comments"),
+ concat!("/repos/", $p1, "/", $p2, "/commits/", $p3, "/comments"),
+ concat!("/repos/", $p1, "/", $p2, "/commits"),
+ concat!("/repos/", $p1, "/", $p2, "/commits/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/readme"),
+ concat!("/repos/", $p1, "/", $p2, "/keys"),
+ concat!("/repos/", $p1, "/", $p2, "/keys", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/downloads"),
+ concat!("/repos/", $p1, "/", $p2, "/downloads", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/forks"),
+ concat!("/repos/", $p1, "/", $p2, "/hooks"),
+ concat!("/repos/", $p1, "/", $p2, "/hooks", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/releases"),
+ concat!("/repos/", $p1, "/", $p2, "/releases/", $p3),
+ concat!("/repos/", $p1, "/", $p2, "/releases/", $p3, "/assets"),
+ concat!("/repos/", $p1, "/", $p2, "/stats/contributors"),
+ concat!("/repos/", $p1, "/", $p2, "/stats/commit_activity"),
+ concat!("/repos/", $p1, "/", $p2, "/stats/code_frequency"),
+ concat!("/repos/", $p1, "/", $p2, "/stats/participation"),
+ concat!("/repos/", $p1, "/", $p2, "/stats/punch_card"),
+ concat!("/repos/", $p1, "/", $p2, "/statuses/", $p3),
+ concat!("/search/repositories"),
+ concat!("/search/code"),
+ concat!("/search/issues"),
+ concat!("/search/users"),
+ concat!("/legacy/issues/search/", $p1, "/", $p2, "/", $p3, "/", $p4),
+ concat!("/legacy/repos/search/", $p1),
+ concat!("/legacy/user/search/", $p1),
+ concat!("/legacy/user/email/", $p1),
+ concat!("/users/", $p1),
+ concat!("/user"),
+ concat!("/users"),
+ concat!("/user/emails"),
+ concat!("/users/", $p1, "/followers"),
+ concat!("/user/followers"),
+ concat!("/users/", $p1, "/following"),
+ concat!("/user/following"),
+ concat!("/user/following/", $p1),
+ concat!("/users/", $p1, "/following", $p2),
+ concat!("/users/", $p1, "/keys"),
+ concat!("/user/keys"),
+ concat!("/user/keys/", $p1),
+ ];
+
+ arr.to_vec()
+ }};
+}
+
+static PATHS: [&str; 5] = [
+ "/authorizations",
+ "/user/repos",
+ "/repos/rust-lang/rust/stargazers",
+ "/orgs/rust-lang/public_members/nikomatsakis",
+ "/repos/rust-lang/rust/releases/1.51.0",
+];
+
+fn main() {
+ let mut router = actix_router::Router::::build();
+
+ for route in register!(brackets) {
+ router.path(route, true);
+ }
+
+ let actix = router.finish();
+
+ if firestorm::enabled() {
+ firestorm::bench("target", || {
+ for &route in &PATHS {
+ let mut path = actix_router::Path::new(route);
+ actix.recognize(&mut path).unwrap();
+ }
+ })
+ .unwrap();
+ }
+}
diff --git a/actix-router/src/de.rs b/actix-router/src/de.rs
new file mode 100644
index 000000000..775c48b8a
--- /dev/null
+++ b/actix-router/src/de.rs
@@ -0,0 +1,723 @@
+use serde::de::{self, Deserializer, Error as DeError, Visitor};
+use serde::forward_to_deserialize_any;
+
+use crate::path::{Path, PathIter};
+use crate::ResourcePath;
+
+macro_rules! unsupported_type {
+ ($trait_fn:ident, $name:expr) => {
+ fn $trait_fn(self, _: V) -> Result
+ where
+ V: Visitor<'de>,
+ {
+ Err(de::value::Error::custom(concat!(
+ "unsupported type: ",
+ $name
+ )))
+ }
+ };
+}
+
+macro_rules! parse_single_value {
+ ($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
+ fn $trait_fn(self, visitor: V) -> Result
+ where
+ V: Visitor<'de>,
+ {
+ if self.path.segment_count() != 1 {
+ Err(de::value::Error::custom(
+ format!(
+ "wrong number of parameters: {} expected 1",
+ self.path.segment_count()
+ )
+ .as_str(),
+ ))
+ } else {
+ let v = self.path[0].parse().map_err(|_| {
+ de::value::Error::custom(format!(
+ "can not parse {:?} to a {}",
+ &self.path[0], $tp
+ ))
+ })?;
+ visitor.$visit_fn(v)
+ }
+ }
+ };
+}
+
+pub struct PathDeserializer<'de, T: ResourcePath> {
+ path: &'de Path,
+}
+
+impl<'de, T: ResourcePath + 'de> PathDeserializer<'de, T> {
+ pub fn new(path: &'de Path) -> Self {
+ PathDeserializer { path }
+ }
+}
+
+impl<'de, T: ResourcePath + 'de> Deserializer<'de> for PathDeserializer<'de, T> {
+ type Error = de::value::Error;
+
+ fn deserialize_map(self, visitor: V) -> Result
+ where
+ V: Visitor<'de>,
+ {
+ visitor.visit_map(ParamsDeserializer {
+ params: self.path.iter(),
+ current: None,
+ })
+ }
+
+ fn deserialize_struct(
+ self,
+ _: &'static str,
+ _: &'static [&'static str],
+ visitor: V,
+ ) -> Result
+ where
+ V: Visitor<'de>,
+ {
+ self.deserialize_map(visitor)
+ }
+
+ fn deserialize_unit(self, visitor: V) -> Result
+ where
+ V: Visitor<'de>,
+ {
+ visitor.visit_unit()
+ }
+
+ fn deserialize_unit_struct(
+ self,
+ _: &'static str,
+ visitor: V,
+ ) -> Result
+ where
+ V: Visitor<'de>,
+ {
+ self.deserialize_unit(visitor)
+ }
+
+ fn deserialize_newtype_struct(
+ self,
+ _: &'static str,
+ visitor: V,
+ ) -> Result
+ where
+ V: Visitor<'de>,
+ {
+ visitor.visit_newtype_struct(self)
+ }
+
+ fn deserialize_tuple(self, len: usize, visitor: V) -> Result
+ where
+ V: Visitor<'de>,
+ {
+ if self.path.segment_count() < len {
+ Err(de::value::Error::custom(
+ format!(
+ "wrong number of parameters: {} expected {}",
+ self.path.segment_count(),
+ len
+ )
+ .as_str(),
+ ))
+ } else {
+ visitor.visit_seq(ParamsSeq {
+ params: self.path.iter(),
+ })
+ }
+ }
+
+ fn deserialize_tuple_struct(
+ self,
+ _: &'static str,
+ len: usize,
+ visitor: V,
+ ) -> Result
+ where
+ V: Visitor<'de>,
+ {
+ if self.path.segment_count() < len {
+ Err(de::value::Error::custom(
+ format!(
+ "wrong number of parameters: {} expected {}",
+ self.path.segment_count(),
+ len
+ )
+ .as_str(),
+ ))
+ } else {
+ visitor.visit_seq(ParamsSeq {
+ params: self.path.iter(),
+ })
+ }
+ }
+
+ fn deserialize_enum(
+ self,
+ _: &'static str,
+ _: &'static [&'static str],
+ visitor: V,
+ ) -> Result
+ where
+ V: Visitor<'de>,
+ {
+ if self.path.is_empty() {
+ Err(de::value::Error::custom("expected at least one parameters"))
+ } else {
+ visitor.visit_enum(ValueEnum {
+ value: &self.path[0],
+ })
+ }
+ }
+
+ fn deserialize_str(self, visitor: V) -> Result
+ where
+ V: Visitor<'de>,
+ {
+ if self.path.segment_count() != 1 {
+ Err(de::value::Error::custom(
+ format!(
+ "wrong number of parameters: {} expected 1",
+ self.path.segment_count()
+ )
+ .as_str(),
+ ))
+ } else {
+ visitor.visit_str(&self.path[0])
+ }
+ }
+
+ fn deserialize_seq(self, visitor: V) -> Result
+ where
+ V: Visitor<'de>,
+ {
+ visitor.visit_seq(ParamsSeq {
+ params: self.path.iter(),
+ })
+ }
+
+ unsupported_type!(deserialize_any, "'any'");
+ unsupported_type!(deserialize_bytes, "bytes");
+ unsupported_type!(deserialize_option, "Option");
+ unsupported_type!(deserialize_identifier, "identifier");
+ unsupported_type!(deserialize_ignored_any, "ignored_any");
+
+ parse_single_value!(deserialize_bool, visit_bool, "bool");
+ parse_single_value!(deserialize_i8, visit_i8, "i8");
+ parse_single_value!(deserialize_i16, visit_i16, "i16");
+ parse_single_value!(deserialize_i32, visit_i32, "i32");
+ parse_single_value!(deserialize_i64, visit_i64, "i64");
+ parse_single_value!(deserialize_u8, visit_u8, "u8");
+ parse_single_value!(deserialize_u16, visit_u16, "u16");
+ parse_single_value!(deserialize_u32, visit_u32, "u32");
+ parse_single_value!(deserialize_u64, visit_u64, "u64");
+ parse_single_value!(deserialize_f32, visit_f32, "f32");
+ parse_single_value!(deserialize_f64, visit_f64, "f64");
+ parse_single_value!(deserialize_string, visit_string, "String");
+ parse_single_value!(deserialize_byte_buf, visit_string, "String");
+ parse_single_value!(deserialize_char, visit_char, "char");
+}
+
+struct ParamsDeserializer<'de, T: ResourcePath> {
+ params: PathIter<'de, T>,
+ current: Option<(&'de str, &'de str)>,
+}
+
+impl<'de, T: ResourcePath> de::MapAccess<'de> for ParamsDeserializer<'de, T> {
+ type Error = de::value::Error;
+
+ fn next_key_seed(&mut self, seed: K) -> Result