summaryrefslogtreecommitdiff
path: root/vendor/matchit
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-07-02 18:36:06 -0600
committermo khan <mo@mokhan.ca>2025-07-02 18:36:06 -0600
commit8cdfa445d6629ffef4cb84967ff7017654045bc2 (patch)
tree22f0b0907c024c78d26a731e2e1f5219407d8102 /vendor/matchit
parent4351c74c7c5f97156bc94d3a8549b9940ac80e3f (diff)
chore: add vendor directory
Diffstat (limited to 'vendor/matchit')
-rw-r--r--vendor/matchit/.cargo-checksum.json1
-rw-r--r--vendor/matchit/Cargo.lock1229
-rw-r--r--vendor/matchit/Cargo.toml82
-rw-r--r--vendor/matchit/LICENSE21
-rw-r--r--vendor/matchit/LICENSE.httprouter29
-rw-r--r--vendor/matchit/README.md127
-rw-r--r--vendor/matchit/benches/bench.rs247
-rw-r--r--vendor/matchit/examples/hyper.rs87
-rw-r--r--vendor/matchit/src/error.rs127
-rw-r--r--vendor/matchit/src/escape.rs184
-rw-r--r--vendor/matchit/src/lib.rs131
-rw-r--r--vendor/matchit/src/params.rs262
-rw-r--r--vendor/matchit/src/router.rs147
-rw-r--r--vendor/matchit/src/tree.rs878
-rw-r--r--vendor/matchit/tests/insert.rs243
-rw-r--r--vendor/matchit/tests/match.rs1047
-rw-r--r--vendor/matchit/tests/remove.rs265
17 files changed, 5107 insertions, 0 deletions
diff --git a/vendor/matchit/.cargo-checksum.json b/vendor/matchit/.cargo-checksum.json
new file mode 100644
index 00000000..0b740b3d
--- /dev/null
+++ b/vendor/matchit/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.lock":"176d03166aad1265d1c8a1430b0d95fc0d9aeb24a4ba22a6eb99058fe8cbcccf","Cargo.toml":"6be08693223305210754898a92d1fa142a884bad57798e193331a2d87560461f","LICENSE":"de701d0618d694feb1af90f02181a1763d9b0bdeb70a3a592781e529077dba65","LICENSE.httprouter":"162ce11ad71338d0a0c6ebaf5c48af72c6ae237b468859d3656fe2d9ed3f3a85","README.md":"181706c7b32682e534462b54ae1bfd0bad2050552cf1bbf6f31f21a8a3bbc5a8","benches/bench.rs":"6e075954539bb0c49cece34fa5d4259cde4d6fb247bbb5e4e9735b161e1bf857","examples/hyper.rs":"bd51bb5a293a6fed5162d70f509061219c61c63b83da8248bde4d75a2f7ac025","src/error.rs":"325f4f1e302a191bfb0e0d3240b2a124b612b86aa4fbfe8d799427bc58e02eb8","src/escape.rs":"f4ad9f1f935941fd0487bd0ee86569a0a21a47f8a1e1dc98f9bce7bfcc3f8e03","src/lib.rs":"d9aacf8176a0c8b74861a7364447a55779611cbaad2c08ca0b498a28d6359a4d","src/params.rs":"18327c24f0031efb4485351cc3984a489c037ad5b767ebb75aaec14e25b8573c","src/router.rs":"79bebbf3bc4c1597c7822a50955f935545231dc82918c954388c116b937ff736","src/tree.rs":"5c25f9d1f3e1e2af538822903c2375e117cc1f8c270f8a1c91ce106f0f2bb939","tests/insert.rs":"7c10cc38e1fdbb2ba5ddc2961a24dfb11dca263abde2833e28cb77acdc988f15","tests/match.rs":"33bf4781f538e3499cb1e463ae212787e21df2b8f3cbf04c18982c4b0f1a1213","tests/remove.rs":"bc2de63e42ab925b6faa0343a173fa9d57c8d91b305bf476d4e14bd0d6965800"},"package":"47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"} \ No newline at end of file
diff --git a/vendor/matchit/Cargo.lock b/vendor/matchit/Cargo.lock
new file mode 100644
index 00000000..c017aedf
--- /dev/null
+++ b/vendor/matchit/Cargo.lock
@@ -0,0 +1,1229 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "actix-router"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ad299af73649e1fc893e333ccf86f377751eb95ff875d095131574c6f43452c"
+dependencies = [
+ "bytestring",
+ "http",
+ "log",
+ "regex",
+ "serde",
+]
+
+[[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "backtrace"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bstr"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
+dependencies = [
+ "lazy_static",
+ "memchr",
+ "regex-automata",
+ "serde",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "bytes"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
+
+[[package]]
+name = "bytestring"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7f83e57d9154148e355404702e2694463241880b939570d7c97c014da7a69a1"
+dependencies = [
+ "bytes",
+]
+
+[[package]]
+name = "cast"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "2.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
+dependencies = [
+ "bitflags",
+ "textwrap",
+ "unicode-width",
+]
+
+[[package]]
+name = "criterion"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f"
+dependencies = [
+ "atty",
+ "cast",
+ "clap",
+ "criterion-plot",
+ "csv",
+ "itertools",
+ "lazy_static",
+ "num-traits",
+ "oorandom",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_cbor",
+ "serde_derive",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876"
+dependencies = [
+ "cast",
+ "itertools",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
+dependencies = [
+ "cfg-if",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "crossbeam-utils",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "csv"
+version = "1.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
+dependencies = [
+ "bstr",
+ "csv-core",
+ "itoa 0.4.8",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "either"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "futures-channel"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
+
+[[package]]
+name = "futures-sink"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
+
+[[package]]
+name = "futures-task"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
+
+[[package]]
+name = "futures-util"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+]
+
+[[package]]
+name = "gimli"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
+
+[[package]]
+name = "gonzales"
+version = "0.0.3-beta"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47a63ae0a0e43eefd14cdf85f9d98396115f4473239585f111e5626c79be8007"
+dependencies = [
+ "smallvec",
+]
+
+[[package]]
+name = "h2"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "half"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "http"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa 1.0.4",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
+dependencies = [
+ "bytes",
+ "http",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
+name = "hyper"
+version = "0.14.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa 1.0.4",
+ "pin-project-lite",
+ "socket2 0.4.9",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
+
+[[package]]
+name = "itoa"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
+
+[[package]]
+name = "js-sys"
+version = "0.3.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.148"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
+
+[[package]]
+name = "lock_api"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "matchit"
+version = "0.8.4"
+dependencies = [
+ "actix-router",
+ "criterion",
+ "gonzales",
+ "hyper",
+ "path-tree",
+ "regex",
+ "route-recognizer",
+ "routefinder",
+ "tokio",
+ "tower",
+]
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "memoffset"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "object"
+version = "0.32.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
+
+[[package]]
+name = "oorandom"
+version = "11.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets",
+]
+
+[[package]]
+name = "path-tree"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d036097265640507a68750c543f2bd2f16565c5a9bb9675b1e213b9a422d3aa"
+
+[[package]]
+name = "pin-project"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.37",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "plotters"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97"
+dependencies = [
+ "num-traits",
+ "plotters-backend",
+ "plotters-svg",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "plotters-backend"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142"
+
+[[package]]
+name = "plotters-svg"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f"
+dependencies = [
+ "plotters-backend",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rayon"
+version = "1.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d"
+dependencies = [
+ "autocfg",
+ "crossbeam-deque",
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-utils",
+ "num_cpus",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
+
+[[package]]
+name = "route-recognizer"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746"
+
+[[package]]
+name = "routefinder"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54e6ab7fb7d4627afa0d64a31cdf9545a23906a6f107bd7c9a6e4ec8fb952ea5"
+dependencies = [
+ "smartcow",
+ "smartstring",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "ryu"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "serde"
+version = "1.0.147"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
+
+[[package]]
+name = "serde_cbor"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
+dependencies = [
+ "half",
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.147"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.103",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.87"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
+dependencies = [
+ "itoa 1.0.4",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "smartcow"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cd4be1039bf9f153b3a1a055043dc144360ec289854ece5dde68385f85d0faa"
+dependencies = [
+ "smartstring",
+]
+
+[[package]]
+name = "smartstring"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
+dependencies = [
+ "autocfg",
+ "static_assertions",
+ "version_check",
+]
+
+[[package]]
+name = "socket2"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "socket2"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "syn"
+version = "1.0.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "tinytemplate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "tokio"
+version = "1.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "num_cpus",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2 0.5.4",
+ "tokio-macros",
+ "windows-sys",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.37",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "tower"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project",
+ "pin-project-lite",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
+
+[[package]]
+name = "tower-service"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+
+[[package]]
+name = "tracing"
+version = "0.1.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+dependencies = [
+ "cfg-if",
+ "log",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.103",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.103",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
+
+[[package]]
+name = "web-sys"
+version = "0.3.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
diff --git a/vendor/matchit/Cargo.toml b/vendor/matchit/Cargo.toml
new file mode 100644
index 00000000..a2bf2392
--- /dev/null
+++ b/vendor/matchit/Cargo.toml
@@ -0,0 +1,82 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "matchit"
+version = "0.8.4"
+authors = ["Ibraheem Ahmed <ibraheem@ibraheem.ca>"]
+description = "A high performance, zero-copy URL router."
+readme = "README.md"
+keywords = [
+ "router",
+ "path",
+ "tree",
+ "match",
+ "url",
+]
+categories = [
+ "network-programming",
+ "algorithms",
+]
+license = "MIT AND BSD-3-Clause"
+repository = "https://github.com/ibraheemdev/matchit"
+
+[profile.release]
+opt-level = 3
+lto = true
+codegen-units = 1
+
+[[bench]]
+name = "bench"
+harness = false
+
+[dependencies]
+
+[dev-dependencies.actix-router]
+version = "0.2.7"
+
+[dev-dependencies.criterion]
+version = "0.3.4"
+
+[dev-dependencies.gonzales]
+version = "0.0.3-beta"
+
+[dev-dependencies.hyper]
+version = "0.14"
+features = ["full"]
+
+[dev-dependencies.path-tree]
+version = "0.2.2"
+
+[dev-dependencies.regex]
+version = "1.5.4"
+
+[dev-dependencies.route-recognizer]
+version = "0.3.0"
+
+[dev-dependencies.routefinder]
+version = "0.5.2"
+
+[dev-dependencies.tokio]
+version = "1"
+features = ["full"]
+
+[dev-dependencies.tower]
+version = "0.4"
+features = [
+ "make",
+ "util",
+]
+
+[features]
+__test_helpers = []
+default = []
diff --git a/vendor/matchit/LICENSE b/vendor/matchit/LICENSE
new file mode 100644
index 00000000..24411c7b
--- /dev/null
+++ b/vendor/matchit/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Ibraheem Ahmed
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/matchit/LICENSE.httprouter b/vendor/matchit/LICENSE.httprouter
new file mode 100644
index 00000000..875308f5
--- /dev/null
+++ b/vendor/matchit/LICENSE.httprouter
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2013, Julien Schmidt
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/matchit/README.md b/vendor/matchit/README.md
new file mode 100644
index 00000000..d54e7b64
--- /dev/null
+++ b/vendor/matchit/README.md
@@ -0,0 +1,127 @@
+# `matchit`
+
+[<img alt="crates.io" src="https://img.shields.io/crates/v/matchit?style=for-the-badge" height="25">](https://crates.io/crates/matchit)
+[<img alt="github" src="https://img.shields.io/badge/github-matchit-blue?style=for-the-badge" height="25">](https://github.com/ibraheemdev/matchit)
+[<img alt="docs.rs" src="https://img.shields.io/docsrs/matchit?style=for-the-badge" height="25">](https://docs.rs/matchit)
+
+A high performance, zero-copy URL router.
+
+```rust
+use matchit::Router;
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+ let mut router = Router::new();
+ router.insert("/home", "Welcome!")?;
+ router.insert("/users/{id}", "A User")?;
+
+ let matched = router.at("/users/978")?;
+ assert_eq!(matched.params.get("id"), Some("978"));
+ assert_eq!(*matched.value, "A User");
+
+ Ok(())
+}
+```
+
+## Parameters
+
+The router supports dynamic route segments. These can either be named or catch-all parameters.
+
+Named parameters like `/{id}` match anything until the next `/` or the end of the path. Note that named parameters must be followed
+by a `/` or the end of the route. Dynamic suffixes are not currently supported.
+
+```rust,ignore
+let mut m = Router::new();
+m.insert("/users/{id}", true)?;
+
+assert_eq!(m.at("/users/1")?.params.get("id"), Some("1"));
+assert_eq!(m.at("/users/23")?.params.get("id"), Some("23"));
+assert!(m.at("/users").is_err());
+```
+
+Catch-all parameters start with `*` and match anything until the end of the path. They must always be at the **end** of the route.
+
+```rust,ignore
+let mut m = Router::new();
+m.insert("/{*p}", true)?;
+
+assert_eq!(m.at("/foo.js")?.params.get("p"), Some("foo.js"));
+assert_eq!(m.at("/c/bar.css")?.params.get("p"), Some("c/bar.css"));
+
+// note that this will not match
+assert!(m.at("/").is_err());
+```
+
+The literal characters `{` and `}` may be included in a static route by escaping them with the same character. For example, the `{` character is escaped with `{{` and the `}` character is escaped with `}}`.
+
+```rust,ignore
+let mut m = Router::new();
+m.insert("/{{hello}}", true)?;
+m.insert("/{hello}", true)?;
+
+// match the static route
+assert!(m.at("/{hello}")?.value);
+
+// match the dynamic route
+assert_eq!(m.at("/hello")?.params.get("hello"), Some("hello"));
+```
+
+## Routing Priority
+
+Static and dynamic route segments are allowed to overlap. If they do, static segments will be given higher priority:
+
+```rust,ignore
+let mut m = Router::new();
+m.insert("/", "Welcome!").unwrap(); // priority: 1
+m.insert("/about", "About Me").unwrap(); // priority: 1
+m.insert("/{*filepath}", "...").unwrap(); // priority: 2
+```
+
+## How does it work?
+
+The router takes advantage of the fact that URL routes generally follow a hierarchical structure. Routes are stored them in a radix trie that makes heavy use of common prefixes.
+
+```text
+Priority Path Value
+9 \ 1
+3 ├s None
+2 |├earch\ 2
+1 |└upport\ 3
+2 ├blog\ 4
+1 | └{post} None
+1 | └\ 5
+2 ├about-us\ 6
+1 | └team\ 7
+1 └contact\ 8
+```
+
+This allows us to reduce the route search to a small number of branches. Child nodes on the same level of the tree are also prioritized
+by the number of children with registered values, increasing the chance of choosing the correct branch of the first try.
+
+## Benchmarks
+
+As it turns out, this method of routing is extremely fast. In a benchmark matching 4 paths against 130 registered routes, `matchit` find the correct routes
+in under 200 nanoseconds, an order of magnitude faster than most other routers. You can view the benchmark code [here](https://github.com/ibraheemdev/matchit/blob/master/benches/bench.rs).
+
+```text
+Compare Routers/matchit
+time: [175.96 ns 176.39 ns 176.84 ns]
+
+Compare Routers/actix
+time: [26.805 us 26.811 us 26.816 us]
+
+Compare Routers/path-tree
+time: [468.95 ns 470.34 ns 471.65 ns]
+
+Compare Routers/regex
+time: [22.539 us 22.584 us 22.639 us]
+
+Compare Routers/route-recognizer
+time: [3.7552 us 3.7732 us 3.8027 us]
+
+Compare Routers/routefinder
+time: [5.7313 us 5.7405 us 5.7514 us]
+```
+
+## Credits
+
+A lot of the code in this package was based on Julien Schmidt's [`httprouter`](https://github.com/julienschmidt/httprouter).
diff --git a/vendor/matchit/benches/bench.rs b/vendor/matchit/benches/bench.rs
new file mode 100644
index 00000000..b345b023
--- /dev/null
+++ b/vendor/matchit/benches/bench.rs
@@ -0,0 +1,247 @@
+use criterion::{black_box, criterion_group, criterion_main, Criterion};
+
+fn call() -> impl IntoIterator<Item = &'static str> {
+ [
+ "/user/repos",
+ "/repos/rust-lang/rust/stargazers",
+ "/orgs/rust-lang/public_members/nikomatsakis",
+ "/repos/rust-lang/rust/releases/1.51.0",
+ ]
+}
+
+fn compare_routers(c: &mut Criterion) {
+ let mut group = c.benchmark_group("Compare Routers");
+
+ let mut matchit = matchit::Router::new();
+ for route in register!(brackets) {
+ matchit.insert(route, true).unwrap();
+ }
+ group.bench_function("matchit", |b| {
+ b.iter(|| {
+ for route in black_box(call()) {
+ black_box(matchit.at(route).unwrap());
+ }
+ });
+ });
+
+ let mut path_tree = path_tree::PathTree::new();
+ for route in register!(colon) {
+ path_tree.insert(route, true);
+ }
+ group.bench_function("path-tree", |b| {
+ b.iter(|| {
+ for route in black_box(call()) {
+ black_box(path_tree.find(route).unwrap());
+ }
+ });
+ });
+
+ let gonzales = gonzales::RouterBuilder::new().build(register!(brackets));
+ group.bench_function("gonzales", |b| {
+ b.iter(|| {
+ for route in black_box(call()) {
+ black_box(gonzales.route(route).unwrap());
+ }
+ });
+ });
+
+ let mut actix = actix_router::Router::<bool>::build();
+ for route in register!(brackets) {
+ actix.path(route, true);
+ }
+ let actix = actix.finish();
+ group.bench_function("actix", |b| {
+ b.iter(|| {
+ for route in black_box(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 black_box(call()) {
+ black_box(regex_set.matches(route));
+ }
+ });
+ });
+
+ let mut route_recognizer = route_recognizer::Router::new();
+ for route in register!(colon) {
+ route_recognizer.add(route, true);
+ }
+ group.bench_function("route-recognizer", |b| {
+ b.iter(|| {
+ for route in black_box(call()) {
+ black_box(route_recognizer.recognize(route).unwrap());
+ }
+ });
+ });
+
+ let mut routefinder = routefinder::Router::new();
+ for route in register!(colon) {
+ routefinder.add(route, true).unwrap();
+ }
+ group.bench_function("routefinder", |b| {
+ b.iter(|| {
+ for route in black_box(call()) {
+ black_box(routefinder.best_match(route).unwrap());
+ }
+ });
+ });
+
+ group.finish();
+}
+
+criterion_group!(benches, compare_routers);
+criterion_main!(benches);
+
+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) => {{
+ [
+ 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),
+ ]
+ }};
+}
+
+use register;
diff --git a/vendor/matchit/examples/hyper.rs b/vendor/matchit/examples/hyper.rs
new file mode 100644
index 00000000..803af5f4
--- /dev/null
+++ b/vendor/matchit/examples/hyper.rs
@@ -0,0 +1,87 @@
+use std::collections::HashMap;
+use std::convert::Infallible;
+use std::sync::{Arc, Mutex};
+
+use hyper::server::Server;
+use hyper::service::{make_service_fn, service_fn};
+use hyper::{Body, Method, Request, Response};
+use tower::util::BoxCloneService;
+use tower::Service as _;
+
+// GET /
+async fn index(_req: Request<Body>) -> hyper::Result<Response<Body>> {
+ Ok(Response::new(Body::from("Hello, world!")))
+}
+
+// GET /blog
+async fn blog(_req: Request<Body>) -> hyper::Result<Response<Body>> {
+ Ok(Response::new(Body::from("...")))
+}
+
+// 404 handler
+async fn not_found(_req: Request<Body>) -> hyper::Result<Response<Body>> {
+ Ok(Response::builder().status(404).body(Body::empty()).unwrap())
+}
+
+// We can use `BoxCloneService` to erase the type of each handler service.
+//
+// We still need a `Mutex` around each service because `BoxCloneService` doesn't
+// require the service to implement `Sync`.
+type Service = Mutex<BoxCloneService<Request<Body>, Response<Body>, hyper::Error>>;
+
+// We use a `HashMap` to hold a `Router` for each HTTP method. This allows us
+// to register the same route for multiple methods.
+type Router = HashMap<Method, matchit::Router<Service>>;
+
+async fn route(router: Arc<Router>, req: Request<Body>) -> hyper::Result<Response<Body>> {
+ // find the subrouter for this request method
+ let router = match router.get(req.method()) {
+ Some(router) => router,
+ // if there are no routes for this method, respond with 405 Method Not Allowed
+ None => return Ok(Response::builder().status(405).body(Body::empty()).unwrap()),
+ };
+
+ // find the service for this request path
+ match router.at(req.uri().path()) {
+ Ok(found) => {
+ // lock the service for a very short time, just to clone the service
+ let mut service = found.value.lock().unwrap().clone();
+ service.call(req).await
+ }
+ // if we there is no matching service, call the 404 handler
+ Err(_) => not_found(req).await,
+ }
+}
+
+#[tokio::main]
+async fn main() {
+ // Create a router and register our routes.
+ let mut router = Router::new();
+
+ // GET / => `index`
+ router
+ .entry(Method::GET)
+ .or_default()
+ .insert("/", BoxCloneService::new(service_fn(index)).into())
+ .unwrap();
+
+ // GET /blog => `blog`
+ router
+ .entry(Method::GET)
+ .or_default()
+ .insert("/blog", BoxCloneService::new(service_fn(blog)).into())
+ .unwrap();
+
+ // boilerplate for the hyper service
+ let router = Arc::new(router);
+ let make_service = make_service_fn(|_| {
+ let router = router.clone();
+ async { Ok::<_, Infallible>(service_fn(move |request| route(router.clone(), request))) }
+ });
+
+ // run the server
+ Server::bind(&([127, 0, 0, 1], 3000).into())
+ .serve(make_service)
+ .await
+ .unwrap()
+}
diff --git a/vendor/matchit/src/error.rs b/vendor/matchit/src/error.rs
new file mode 100644
index 00000000..ab879f30
--- /dev/null
+++ b/vendor/matchit/src/error.rs
@@ -0,0 +1,127 @@
+use crate::escape::{UnescapedRef, UnescapedRoute};
+use crate::tree::{denormalize_params, Node};
+
+use std::fmt;
+
+/// Represents errors that can occur when inserting a new route.
+#[non_exhaustive]
+#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub enum InsertError {
+ /// Attempted to insert a path that conflicts with an existing route.
+ Conflict {
+ /// The existing route that the insertion is conflicting with.
+ with: String,
+ },
+ /// Only one parameter per route segment is allowed.
+ ///
+ /// Static segments are also allowed before a parameter, but not after it. For example,
+ /// `/foo-{bar}` is a valid route, but `/{bar}-foo` is not.
+ InvalidParamSegment,
+ /// Parameters must be registered with a valid name and matching braces.
+ ///
+ /// Note you can use `{{` or `}}` to escape literal brackets.
+ InvalidParam,
+ /// Catch-all parameters are only allowed at the end of a path.
+ InvalidCatchAll,
+}
+
+impl fmt::Display for InsertError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Conflict { with } => {
+ write!(
+ f,
+ "Insertion failed due to conflict with previously registered route: {}",
+ with
+ )
+ }
+ Self::InvalidParamSegment => {
+ write!(f, "Only one parameter is allowed per path segment")
+ }
+ Self::InvalidParam => write!(f, "Parameters must be registered with a valid name"),
+ Self::InvalidCatchAll => write!(
+ f,
+ "Catch-all parameters are only allowed at the end of a route"
+ ),
+ }
+ }
+}
+
+impl std::error::Error for InsertError {}
+
+impl InsertError {
+ /// Returns an error for a route conflict with the given node.
+ ///
+ /// This method attempts to find the full conflicting route.
+ pub(crate) fn conflict<T>(
+ route: &UnescapedRoute,
+ prefix: UnescapedRef<'_>,
+ current: &Node<T>,
+ ) -> Self {
+ let mut route = route.clone();
+
+ // The route is conflicting with the current node.
+ if prefix.unescaped() == current.prefix.unescaped() {
+ denormalize_params(&mut route, &current.remapping);
+ return InsertError::Conflict {
+ with: String::from_utf8(route.into_unescaped()).unwrap(),
+ };
+ }
+
+ // Remove the non-matching suffix from the route.
+ route.truncate(route.len() - prefix.len());
+
+ // Add the conflicting prefix.
+ if !route.ends_with(&current.prefix) {
+ route.append(&current.prefix);
+ }
+
+ // Add the prefixes of any conflicting children.
+ let mut child = current.children.first();
+ while let Some(node) = child {
+ route.append(&node.prefix);
+ child = node.children.first();
+ }
+
+ // Denormalize any route parameters.
+ let mut last = current;
+ while let Some(node) = last.children.first() {
+ last = node;
+ }
+ denormalize_params(&mut route, &last.remapping);
+
+ // Return the conflicting route.
+ InsertError::Conflict {
+ with: String::from_utf8(route.into_unescaped()).unwrap(),
+ }
+ }
+}
+
+/// A failed match attempt.
+///
+/// ```
+/// use matchit::{MatchError, Router};
+/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
+/// let mut router = Router::new();
+/// router.insert("/home", "Welcome!")?;
+/// router.insert("/blog", "Our blog.")?;
+///
+/// // no routes match
+/// if let Err(err) = router.at("/blo") {
+/// assert_eq!(err, MatchError::NotFound);
+/// }
+/// # Ok(())
+/// # }
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum MatchError {
+ /// No matching route was found.
+ NotFound,
+}
+
+impl fmt::Display for MatchError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "Matching route not found")
+ }
+}
+
+impl std::error::Error for MatchError {}
diff --git a/vendor/matchit/src/escape.rs b/vendor/matchit/src/escape.rs
new file mode 100644
index 00000000..ef8ddbfb
--- /dev/null
+++ b/vendor/matchit/src/escape.rs
@@ -0,0 +1,184 @@
+use std::{fmt, ops::Range};
+
+/// An unescaped route that keeps track of the position of escaped characters ('{{' or '}}').
+///
+/// Note that this type dereferences to `&[u8]`.
+#[derive(Clone, Default)]
+pub struct UnescapedRoute {
+ // The raw unescaped route.
+ inner: Vec<u8>,
+ escaped: Vec<usize>,
+}
+
+impl UnescapedRoute {
+ /// Unescapes escaped brackets ('{{' or '}}') in a route.
+ pub fn new(mut inner: Vec<u8>) -> UnescapedRoute {
+ let mut escaped = Vec::new();
+ let mut i = 0;
+
+ while let Some(&c) = inner.get(i) {
+ if (c == b'{' && inner.get(i + 1) == Some(&b'{'))
+ || (c == b'}' && inner.get(i + 1) == Some(&b'}'))
+ {
+ inner.remove(i);
+ escaped.push(i);
+ }
+
+ i += 1;
+ }
+
+ UnescapedRoute { inner, escaped }
+ }
+
+ /// Returns true if the character at the given index was escaped.
+ pub fn is_escaped(&self, i: usize) -> bool {
+ self.escaped.contains(&i)
+ }
+
+ /// Replaces the characters in the given range.
+ pub fn splice(
+ &mut self,
+ range: Range<usize>,
+ replace: Vec<u8>,
+ ) -> impl Iterator<Item = u8> + '_ {
+ // Ignore any escaped characters in the range being replaced.
+ self.escaped.retain(|x| !range.contains(x));
+
+ // Update the escaped indices.
+ let offset = (replace.len() as isize) - (range.len() as isize);
+ for i in &mut self.escaped {
+ if *i > range.end {
+ *i = i.checked_add_signed(offset).unwrap();
+ }
+ }
+
+ self.inner.splice(range, replace)
+ }
+
+ /// Appends another route to the end of this one.
+ pub fn append(&mut self, other: &UnescapedRoute) {
+ for i in &other.escaped {
+ self.escaped.push(self.inner.len() + i);
+ }
+
+ self.inner.extend_from_slice(&other.inner);
+ }
+
+ /// Truncates the route to the given length.
+ pub fn truncate(&mut self, to: usize) {
+ self.escaped.retain(|&x| x < to);
+ self.inner.truncate(to);
+ }
+
+ /// Returns a reference to this route.
+ pub fn as_ref(&self) -> UnescapedRef<'_> {
+ UnescapedRef {
+ inner: &self.inner,
+ escaped: &self.escaped,
+ offset: 0,
+ }
+ }
+
+ /// Returns a reference to the unescaped slice.
+ pub fn unescaped(&self) -> &[u8] {
+ &self.inner
+ }
+
+ /// Returns the unescaped route.
+ pub fn into_unescaped(self) -> Vec<u8> {
+ self.inner
+ }
+}
+
+impl std::ops::Deref for UnescapedRoute {
+ type Target = [u8];
+
+ fn deref(&self) -> &Self::Target {
+ &self.inner
+ }
+}
+
+impl fmt::Debug for UnescapedRoute {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Debug::fmt(std::str::from_utf8(&self.inner).unwrap(), f)
+ }
+}
+
+/// A reference to an `UnescapedRoute`.
+#[derive(Copy, Clone)]
+pub struct UnescapedRef<'a> {
+ pub inner: &'a [u8],
+ escaped: &'a [usize],
+ // An offset applied to each escaped index.
+ offset: isize,
+}
+
+impl<'a> UnescapedRef<'a> {
+ /// Converts this reference into an owned route.
+ pub fn to_owned(self) -> UnescapedRoute {
+ let mut escaped = Vec::new();
+ for &i in self.escaped {
+ let i = i.checked_add_signed(self.offset);
+
+ match i {
+ Some(i) if i < self.inner.len() => escaped.push(i),
+ _ => {}
+ }
+ }
+
+ UnescapedRoute {
+ escaped,
+ inner: self.inner.to_owned(),
+ }
+ }
+
+ /// Returns `true` if the character at the given index was escaped.
+ pub fn is_escaped(&self, i: usize) -> bool {
+ if let Some(i) = i.checked_add_signed(-self.offset) {
+ return self.escaped.contains(&i);
+ }
+
+ false
+ }
+
+ /// Slices the route with `start..`.
+ pub fn slice_off(&self, start: usize) -> UnescapedRef<'a> {
+ UnescapedRef {
+ inner: &self.inner[start..],
+ escaped: self.escaped,
+ offset: self.offset - (start as isize),
+ }
+ }
+
+ /// Slices the route with `..end`.
+ pub fn slice_until(&self, end: usize) -> UnescapedRef<'a> {
+ UnescapedRef {
+ inner: &self.inner[..end],
+ escaped: self.escaped,
+ offset: self.offset,
+ }
+ }
+
+ /// Returns a reference to the unescaped slice.
+ pub fn unescaped(&self) -> &[u8] {
+ self.inner
+ }
+}
+
+impl<'a> std::ops::Deref for UnescapedRef<'a> {
+ type Target = &'a [u8];
+
+ fn deref(&self) -> &Self::Target {
+ &self.inner
+ }
+}
+
+impl<'a> fmt::Debug for UnescapedRef<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("UnescapedRef")
+ .field("inner", &std::str::from_utf8(self.inner))
+ .field("escaped", &self.escaped)
+ .field("offset", &self.offset)
+ .finish()
+ }
+}
diff --git a/vendor/matchit/src/lib.rs b/vendor/matchit/src/lib.rs
new file mode 100644
index 00000000..cf128525
--- /dev/null
+++ b/vendor/matchit/src/lib.rs
@@ -0,0 +1,131 @@
+/*!
+A high performance, zero-copy URL router.
+
+```rust
+use matchit::Router;
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+ let mut router = Router::new();
+ router.insert("/home", "Welcome!")?;
+ router.insert("/users/{id}", "A User")?;
+
+ let matched = router.at("/users/978")?;
+ assert_eq!(matched.params.get("id"), Some("978"));
+ assert_eq!(*matched.value, "A User");
+
+ Ok(())
+}
+```
+
+# Parameters
+
+The router supports dynamic route segments. These can either be named or catch-all parameters.
+
+Named parameters like `/{id}` match anything until the next `/` or the end of the path. Note that named parameters must be followed
+by a `/` or the end of the route. Dynamic suffixes are not currently supported.
+
+```rust
+# use matchit::Router;
+# fn main() -> Result<(), Box<dyn std::error::Error>> {
+let mut m = Router::new();
+m.insert("/users/{id}", true)?;
+
+assert_eq!(m.at("/users/1")?.params.get("id"), Some("1"));
+assert_eq!(m.at("/users/23")?.params.get("id"), Some("23"));
+assert!(m.at("/users").is_err());
+# Ok(())
+# }
+```
+
+Catch-all parameters start with `*` and match anything until the end of the path. They must always be at the **end** of the route.
+
+```rust
+# use matchit::Router;
+# fn main() -> Result<(), Box<dyn std::error::Error>> {
+let mut m = Router::new();
+m.insert("/{*p}", true)?;
+
+assert_eq!(m.at("/foo.js")?.params.get("p"), Some("foo.js"));
+assert_eq!(m.at("/c/bar.css")?.params.get("p"), Some("c/bar.css"));
+
+// note that this will not match
+assert!(m.at("/").is_err());
+# Ok(())
+# }
+```
+
+The literal characters `{` and `}` may be included in a static route by escaping them with the same character. For example, the `{` character is escaped with `{{` and the `}` character is escaped with `}}`.
+
+```rust
+# use matchit::Router;
+# fn main() -> Result<(), Box<dyn std::error::Error>> {
+let mut m = Router::new();
+m.insert("/{{hello}}", true)?;
+m.insert("/{hello}", true)?;
+
+// match the static route
+assert!(m.at("/{hello}")?.value);
+
+// match the dynamic route
+assert_eq!(m.at("/hello")?.params.get("hello"), Some("hello"));
+# Ok(())
+# }
+```
+
+# Routing Priority
+
+Static and dynamic route segments are allowed to overlap. If they do, static segments will be given higher priority:
+
+```rust
+# use matchit::Router;
+# fn main() -> Result<(), Box<dyn std::error::Error>> {
+let mut m = Router::new();
+m.insert("/", "Welcome!").unwrap(); // priority: 1
+m.insert("/about", "About Me").unwrap(); // priority: 1
+m.insert("/{*filepath}", "...").unwrap(); // priority: 2
+# Ok(())
+# }
+```
+
+# How does it work?
+
+The router takes advantage of the fact that URL routes generally follow a hierarchical structure. Routes are stored them in a radix trie that makes heavy use of common prefixes.
+
+```text
+Priority Path Value
+9 \ 1
+3 ├s None
+2 |├earch\ 2
+1 |└upport\ 3
+2 ├blog\ 4
+1 | └{post} None
+1 | └\ 5
+2 ├about-us\ 6
+1 | └team\ 7
+1 └contact\ 8
+```
+
+This allows us to reduce the route search to a small number of branches. Child nodes on the same level of the tree are also prioritized
+by the number of children with registered values, increasing the chance of choosing the correct branch of the first try.
+
+As it turns out, this method of routing is extremely fast. See the [benchmark results](https://github.com/ibraheemdev/matchit?tab=readme-ov-file#benchmarks) for details.
+*/
+
+#![deny(rust_2018_idioms, clippy::all)]
+
+mod error;
+mod escape;
+mod params;
+mod router;
+mod tree;
+
+pub use error::{InsertError, MatchError};
+pub use params::{Params, ParamsIter};
+pub use router::{Match, Router};
+
+#[cfg(doctest)]
+mod readme {
+ #[allow(dead_code)]
+ #[doc = include_str!("../README.md")]
+ struct Readme;
+}
diff --git a/vendor/matchit/src/params.rs b/vendor/matchit/src/params.rs
new file mode 100644
index 00000000..625b544b
--- /dev/null
+++ b/vendor/matchit/src/params.rs
@@ -0,0 +1,262 @@
+use std::{fmt, iter, mem, slice};
+
+/// A single URL parameter, consisting of a key and a value.
+#[derive(PartialEq, Eq, Ord, PartialOrd, Default, Copy, Clone)]
+struct Param<'k, 'v> {
+ // Keys and values are stored as byte slices internally by the router
+ // to avoid UTF8 checks when slicing, but UTF8 is still respected,
+ // so these slices are valid strings.
+ key: &'k [u8],
+ value: &'v [u8],
+}
+
+impl<'k, 'v> Param<'k, 'v> {
+ const EMPTY: Param<'static, 'static> = Param {
+ key: b"",
+ value: b"",
+ };
+
+ // Returns the parameter key as a string.
+ fn key_str(&self) -> &'k str {
+ std::str::from_utf8(self.key).unwrap()
+ }
+
+ // Returns the parameter value as a string.
+ fn value_str(&self) -> &'v str {
+ std::str::from_utf8(self.value).unwrap()
+ }
+}
+
+/// A list of parameters returned by a route match.
+///
+/// ```rust
+/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
+/// # let mut router = matchit::Router::new();
+/// # router.insert("/users/{id}", true).unwrap();
+/// let matched = router.at("/users/1")?;
+///
+/// // Iterate through the keys and values.
+/// for (key, value) in matched.params.iter() {
+/// println!("key: {}, value: {}", key, value);
+/// }
+///
+/// // Get a specific value by name.
+/// let id = matched.params.get("id");
+/// assert_eq!(id, Some("1"));
+/// # Ok(())
+/// # }
+/// ```
+#[derive(PartialEq, Eq, Ord, PartialOrd, Clone)]
+pub struct Params<'k, 'v> {
+ kind: ParamsKind<'k, 'v>,
+}
+
+// Most routes have a small number of dynamic parameters, so we can avoid
+// heap allocations in the common case.
+const SMALL: usize = 3;
+
+// A list of parameters, optimized to avoid allocations when possible.
+#[derive(PartialEq, Eq, Ord, PartialOrd, Clone)]
+enum ParamsKind<'k, 'v> {
+ Small([Param<'k, 'v>; SMALL], usize),
+ Large(Vec<Param<'k, 'v>>),
+}
+
+impl<'k, 'v> Params<'k, 'v> {
+ pub(crate) fn new() -> Self {
+ Self {
+ kind: ParamsKind::Small([Param::EMPTY; 3], 0),
+ }
+ }
+
+ /// Returns the number of parameters.
+ pub fn len(&self) -> usize {
+ match self.kind {
+ ParamsKind::Small(_, len) => len,
+ ParamsKind::Large(ref vec) => vec.len(),
+ }
+ }
+
+ // Truncates the parameter list to the given length.
+ pub(crate) fn truncate(&mut self, n: usize) {
+ match &mut self.kind {
+ ParamsKind::Small(_, len) => *len = n,
+ ParamsKind::Large(vec) => vec.truncate(n),
+ }
+ }
+
+ /// Returns the value of the first parameter registered under the given key.
+ pub fn get(&self, key: impl AsRef<str>) -> Option<&'v str> {
+ let key = key.as_ref().as_bytes();
+
+ match &self.kind {
+ ParamsKind::Small(arr, len) => arr
+ .iter()
+ .take(*len)
+ .find(|param| param.key == key)
+ .map(Param::value_str),
+ ParamsKind::Large(vec) => vec
+ .iter()
+ .find(|param| param.key == key)
+ .map(Param::value_str),
+ }
+ }
+
+ /// Returns an iterator over the parameters in the list.
+ pub fn iter(&self) -> ParamsIter<'_, 'k, 'v> {
+ ParamsIter::new(self)
+ }
+
+ /// Returns `true` if there are no parameters in the list.
+ pub fn is_empty(&self) -> bool {
+ match self.kind {
+ ParamsKind::Small(_, len) => len == 0,
+ ParamsKind::Large(ref vec) => vec.is_empty(),
+ }
+ }
+
+ /// Inserts a key value parameter pair into the list.
+ pub(crate) fn push(&mut self, key: &'k [u8], value: &'v [u8]) {
+ #[cold]
+ fn drain_to_vec<T: Default>(len: usize, elem: T, arr: &mut [T; SMALL]) -> Vec<T> {
+ let mut vec = Vec::with_capacity(len + 1);
+ vec.extend(arr.iter_mut().map(mem::take));
+ vec.push(elem);
+ vec
+ }
+
+ let param = Param { key, value };
+ match &mut self.kind {
+ ParamsKind::Small(arr, len) => {
+ if *len == SMALL {
+ self.kind = ParamsKind::Large(drain_to_vec(*len, param, arr));
+ return;
+ }
+
+ arr[*len] = param;
+ *len += 1;
+ }
+ ParamsKind::Large(vec) => vec.push(param),
+ }
+ }
+
+ // Applies a transformation function to each key.
+ pub(crate) fn for_each_key_mut(&mut self, f: impl Fn((usize, &mut &'k [u8]))) {
+ match &mut self.kind {
+ ParamsKind::Small(arr, len) => arr
+ .iter_mut()
+ .take(*len)
+ .map(|param| &mut param.key)
+ .enumerate()
+ .for_each(f),
+ ParamsKind::Large(vec) => vec
+ .iter_mut()
+ .map(|param| &mut param.key)
+ .enumerate()
+ .for_each(f),
+ }
+ }
+}
+
+impl fmt::Debug for Params<'_, '_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_list().entries(self.iter()).finish()
+ }
+}
+
+/// An iterator over the keys and values of a route's [parameters](crate::Params).
+pub struct ParamsIter<'ps, 'k, 'v> {
+ kind: ParamsIterKind<'ps, 'k, 'v>,
+}
+
+impl<'ps, 'k, 'v> ParamsIter<'ps, 'k, 'v> {
+ fn new(params: &'ps Params<'k, 'v>) -> Self {
+ let kind = match &params.kind {
+ ParamsKind::Small(arr, len) => ParamsIterKind::Small(arr.iter().take(*len)),
+ ParamsKind::Large(vec) => ParamsIterKind::Large(vec.iter()),
+ };
+ Self { kind }
+ }
+}
+
+enum ParamsIterKind<'ps, 'k, 'v> {
+ Small(iter::Take<slice::Iter<'ps, Param<'k, 'v>>>),
+ Large(slice::Iter<'ps, Param<'k, 'v>>),
+}
+
+impl<'ps, 'k, 'v> Iterator for ParamsIter<'ps, 'k, 'v> {
+ type Item = (&'k str, &'v str);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ match self.kind {
+ ParamsIterKind::Small(ref mut iter) => {
+ iter.next().map(|p| (p.key_str(), p.value_str()))
+ }
+ ParamsIterKind::Large(ref mut iter) => {
+ iter.next().map(|p| (p.key_str(), p.value_str()))
+ }
+ }
+ }
+}
+
+impl ExactSizeIterator for ParamsIter<'_, '_, '_> {
+ fn len(&self) -> usize {
+ match self.kind {
+ ParamsIterKind::Small(ref iter) => iter.len(),
+ ParamsIterKind::Large(ref iter) => iter.len(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn heap_alloc() {
+ let vec = vec![
+ ("hello", "hello"),
+ ("world", "world"),
+ ("foo", "foo"),
+ ("bar", "bar"),
+ ("baz", "baz"),
+ ];
+
+ let mut params = Params::new();
+ for (key, value) in vec.clone() {
+ params.push(key.as_bytes(), value.as_bytes());
+ assert_eq!(params.get(key), Some(value));
+ }
+
+ match params.kind {
+ ParamsKind::Large(..) => {}
+ _ => panic!(),
+ }
+
+ assert!(params.iter().eq(vec.clone()));
+ }
+
+ #[test]
+ fn stack_alloc() {
+ let vec = vec![("hello", "hello"), ("world", "world"), ("baz", "baz")];
+
+ let mut params = Params::new();
+ for (key, value) in vec.clone() {
+ params.push(key.as_bytes(), value.as_bytes());
+ assert_eq!(params.get(key), Some(value));
+ }
+
+ match params.kind {
+ ParamsKind::Small(..) => {}
+ _ => panic!(),
+ }
+
+ assert!(params.iter().eq(vec.clone()));
+ }
+
+ #[test]
+ fn ignore_array_default() {
+ let params = Params::new();
+ assert!(params.get("").is_none());
+ }
+}
diff --git a/vendor/matchit/src/router.rs b/vendor/matchit/src/router.rs
new file mode 100644
index 00000000..8256c4bc
--- /dev/null
+++ b/vendor/matchit/src/router.rs
@@ -0,0 +1,147 @@
+use crate::tree::Node;
+use crate::{InsertError, MatchError, Params};
+
+/// A zero-copy URL router.
+///
+/// See [the crate documentation](crate) for details.
+#[derive(Clone, Debug)]
+pub struct Router<T> {
+ root: Node<T>,
+}
+
+impl<T> Default for Router<T> {
+ fn default() -> Self {
+ Self {
+ root: Node::default(),
+ }
+ }
+}
+
+impl<T> Router<T> {
+ /// Construct a new router.
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Insert a route into the router.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use matchit::Router;
+ /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
+ /// let mut router = Router::new();
+ /// router.insert("/home", "Welcome!")?;
+ /// router.insert("/users/{id}", "A User")?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn insert(&mut self, route: impl Into<String>, value: T) -> Result<(), InsertError> {
+ self.root.insert(route.into(), value)
+ }
+
+ /// Tries to find a value in the router matching the given path.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use matchit::Router;
+ /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
+ /// let mut router = Router::new();
+ /// router.insert("/home", "Welcome!")?;
+ ///
+ /// let matched = router.at("/home").unwrap();
+ /// assert_eq!(*matched.value, "Welcome!");
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn at<'path>(&self, path: &'path str) -> Result<Match<'_, 'path, &T>, MatchError> {
+ match self.root.at(path.as_bytes()) {
+ Ok((value, params)) => Ok(Match {
+ // Safety: We only expose `&mut T` through `&mut self`
+ value: unsafe { &*value.get() },
+ params,
+ }),
+ Err(e) => Err(e),
+ }
+ }
+
+ /// Tries to find a value in the router matching the given path,
+ /// returning a mutable reference.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use matchit::Router;
+ /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
+ /// let mut router = Router::new();
+ /// router.insert("/", 1)?;
+ ///
+ /// *router.at_mut("/").unwrap().value += 1;
+ /// assert_eq!(*router.at("/").unwrap().value, 2);
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn at_mut<'path>(
+ &mut self,
+ path: &'path str,
+ ) -> Result<Match<'_, 'path, &mut T>, MatchError> {
+ match self.root.at(path.as_bytes()) {
+ Ok((value, params)) => Ok(Match {
+ // Safety: We have `&mut self`
+ value: unsafe { &mut *value.get() },
+ params,
+ }),
+ Err(e) => Err(e),
+ }
+ }
+
+ /// Remove a given route from the router.
+ ///
+ /// Returns the value stored under the route if it was found.
+ /// If the route was not found or invalid, `None` is returned.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use matchit::Router;
+ /// let mut router = Router::new();
+ ///
+ /// router.insert("/home", "Welcome!");
+ /// assert_eq!(router.remove("/home"), Some("Welcome!"));
+ /// assert_eq!(router.remove("/home"), None);
+ ///
+ /// router.insert("/home/{id}/", "Hello!");
+ /// assert_eq!(router.remove("/home/{id}/"), Some("Hello!"));
+ /// assert_eq!(router.remove("/home/{id}/"), None);
+ ///
+ /// router.insert("/home/{id}/", "Hello!");
+ /// // the route does not match
+ /// assert_eq!(router.remove("/home/{user}"), None);
+ /// assert_eq!(router.remove("/home/{id}/"), Some("Hello!"));
+ ///
+ /// router.insert("/home/{id}/", "Hello!");
+ /// // invalid route
+ /// assert_eq!(router.remove("/home/{id"), None);
+ /// assert_eq!(router.remove("/home/{id}/"), Some("Hello!"));
+ /// ```
+ pub fn remove(&mut self, path: impl Into<String>) -> Option<T> {
+ self.root.remove(path.into())
+ }
+
+ #[cfg(feature = "__test_helpers")]
+ pub fn check_priorities(&self) -> Result<u32, (u32, u32)> {
+ self.root.check_priorities()
+ }
+}
+
+/// A successful match consisting of the registered value
+/// and URL parameters, returned by [`Router::at`](Router::at).
+#[derive(Debug)]
+pub struct Match<'k, 'v, V> {
+ /// The value stored under the matched node.
+ pub value: V,
+
+ /// The route parameters. See [parameters](crate#parameters) for more details.
+ pub params: Params<'k, 'v>,
+}
diff --git a/vendor/matchit/src/tree.rs b/vendor/matchit/src/tree.rs
new file mode 100644
index 00000000..69a01495
--- /dev/null
+++ b/vendor/matchit/src/tree.rs
@@ -0,0 +1,878 @@
+use crate::escape::{UnescapedRef, UnescapedRoute};
+use crate::{InsertError, MatchError, Params};
+
+use std::cell::UnsafeCell;
+use std::cmp::min;
+use std::ops::Range;
+use std::{fmt, mem};
+
+/// A radix tree used for URL path matching.
+///
+/// See [the crate documentation](crate) for details.
+pub struct Node<T> {
+ // This node's prefix.
+ pub(crate) prefix: UnescapedRoute,
+ // The priority of this node.
+ //
+ // Nodes with more children are higher priority and searched first.
+ pub(crate) priority: u32,
+ // Whether this node contains a wildcard child.
+ pub(crate) wild_child: bool,
+ // The first character of any static children, for fast linear search.
+ pub(crate) indices: Vec<u8>,
+ // The type of this node.
+ pub(crate) node_type: NodeType,
+ pub(crate) children: Vec<Self>,
+ // The value stored at this node.
+ //
+ // See `Node::at` for why an `UnsafeCell` is necessary.
+ value: Option<UnsafeCell<T>>,
+ // Parameter name remapping, stored at nodes that hold values.
+ pub(crate) remapping: ParamRemapping,
+}
+
+/// The types of nodes a tree can hold.
+#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)]
+pub(crate) enum NodeType {
+ /// The root path.
+ Root,
+ /// A route parameter, e.g. `/{id}`.
+ Param,
+ /// A catch-all parameter, e.g. `/*file`.
+ CatchAll,
+ /// A static prefix, e.g. `/foo`.
+ Static,
+}
+
+/// Safety: We expose `value` per Rust's usual borrowing rules, so we can just
+/// delegate these traits.
+unsafe impl<T: Send> Send for Node<T> {}
+unsafe impl<T: Sync> Sync for Node<T> {}
+
+impl<T> Node<T> {
+ // Insert a route into the tree.
+ pub fn insert(&mut self, route: String, val: T) -> Result<(), InsertError> {
+ let route = UnescapedRoute::new(route.into_bytes());
+ let (route, remapping) = normalize_params(route)?;
+ let mut remaining = route.as_ref();
+
+ self.priority += 1;
+
+ // If the tree is empty, insert the root node.
+ if self.prefix.is_empty() && self.children.is_empty() {
+ let last = self.insert_route(remaining, val)?;
+ last.remapping = remapping;
+ self.node_type = NodeType::Root;
+ return Ok(());
+ }
+
+ let mut current = self;
+ 'walk: loop {
+ // Find the common prefix between the route and the current node.
+ let len = min(remaining.len(), current.prefix.len());
+ let common_prefix = (0..len)
+ .find(|&i| {
+ remaining[i] != current.prefix[i]
+ // Make sure not confuse the start of a wildcard with an escaped `{`.
+ || remaining.is_escaped(i) != current.prefix.is_escaped(i)
+ })
+ .unwrap_or(len);
+
+ // If this node has a longer prefix than we need, we have to fork and extract the
+ // common prefix into a shared parent.
+ if current.prefix.len() > common_prefix {
+ // Move the non-matching suffix into a child node.
+ let suffix = current.prefix.as_ref().slice_off(common_prefix).to_owned();
+ let child = Node {
+ prefix: suffix,
+ value: current.value.take(),
+ indices: current.indices.clone(),
+ wild_child: current.wild_child,
+ children: mem::take(&mut current.children),
+ remapping: mem::take(&mut current.remapping),
+ priority: current.priority - 1,
+ node_type: NodeType::Static,
+ };
+
+ // The current node now only holds the common prefix.
+ current.children = vec![child];
+ current.indices = vec![current.prefix[common_prefix]];
+ current.prefix = current
+ .prefix
+ .as_ref()
+ .slice_until(common_prefix)
+ .to_owned();
+ current.wild_child = false;
+ continue;
+ }
+
+ if remaining.len() == common_prefix {
+ // This node must not already contain a value.
+ if current.value.is_some() {
+ return Err(InsertError::conflict(&route, remaining, current));
+ }
+
+ // Insert the value.
+ current.value = Some(UnsafeCell::new(val));
+ current.remapping = remapping;
+ return Ok(());
+ }
+
+ // Otherwise, the route has a remaining non-matching suffix.
+ //
+ // We have to search deeper.
+ remaining = remaining.slice_off(common_prefix);
+ let next = remaining[0];
+
+ // After matching against a wildcard the next character is always `/`.
+ //
+ // Continue searching in the child node if it already exists.
+ if current.node_type == NodeType::Param && current.children.len() == 1 {
+ debug_assert_eq!(next, b'/');
+ current = &mut current.children[0];
+ current.priority += 1;
+ continue 'walk;
+ }
+
+ // Find a child node that matches the next character in the route.
+ for mut i in 0..current.indices.len() {
+ if next == current.indices[i] {
+ // Make sure not confuse the start of a wildcard with an escaped `{` or `}`.
+ if matches!(next, b'{' | b'}') && !remaining.is_escaped(0) {
+ continue;
+ }
+
+ // Continue searching in the child.
+ i = current.update_child_priority(i);
+ current = &mut current.children[i];
+ continue 'walk;
+ }
+ }
+
+ // We couldn't find a matching child.
+ //
+ // If we're not inserting a wildcard we have to create a child.
+ if (!matches!(next, b'{') || remaining.is_escaped(0))
+ && current.node_type != NodeType::CatchAll
+ {
+ current.indices.push(next);
+ let mut child = current.add_child(Node::default());
+ child = current.update_child_priority(child);
+
+ // Insert into the newly created node.
+ let last = current.children[child].insert_route(remaining, val)?;
+ last.remapping = remapping;
+ return Ok(());
+ }
+
+ // We're trying to insert a wildcard.
+ //
+ // If this node already has a wildcard child, we have to make sure it matches.
+ if current.wild_child {
+ // Wildcards are always the last child.
+ current = current.children.last_mut().unwrap();
+ current.priority += 1;
+
+ // Make sure the route parameter matches.
+ if let Some(wildcard) = remaining.get(..current.prefix.len()) {
+ if *wildcard != *current.prefix {
+ return Err(InsertError::conflict(&route, remaining, current));
+ }
+ }
+
+ // Catch-all routes cannot have children.
+ if current.node_type == NodeType::CatchAll {
+ return Err(InsertError::conflict(&route, remaining, current));
+ }
+
+ // Continue with the wildcard node.
+ continue 'walk;
+ }
+
+ // Otherwise, create a new node for the wildcard and insert the route.
+ let last = current.insert_route(remaining, val)?;
+ last.remapping = remapping;
+ return Ok(());
+ }
+ }
+
+ /// Removes a route from the tree, returning the value if the route already existed.
+ ///
+ /// The provided path should be the same as the one used to insert the route, including
+ /// wildcards.
+ pub fn remove(&mut self, route: String) -> Option<T> {
+ let route = UnescapedRoute::new(route.into_bytes());
+ let (route, remapping) = normalize_params(route).ok()?;
+ let mut remaining = route.unescaped();
+
+ // Check if we are removing the root node.
+ if remaining == self.prefix.unescaped() {
+ let value = self.value.take().map(UnsafeCell::into_inner);
+
+ // If the root node has no children, we can reset it.
+ if self.children.is_empty() {
+ *self = Node::default();
+ }
+
+ return value;
+ }
+
+ let mut current = self;
+ 'walk: loop {
+ // The path is longer than this node's prefix, search deeper.
+ if remaining.len() > current.prefix.len() {
+ let (prefix, rest) = remaining.split_at(current.prefix.len());
+
+ // The prefix matches.
+ if prefix == current.prefix.unescaped() {
+ let first = rest[0];
+ remaining = rest;
+
+ // If there is a single child node, we can continue searching in the child.
+ if current.children.len() == 1 {
+ // The route matches, remove the node.
+ if current.children[0].prefix.unescaped() == remaining {
+ return current.remove_child(0, &remapping);
+ }
+
+ // Otherwise, continue searching.
+ current = &mut current.children[0];
+ continue 'walk;
+ }
+
+ // Find a child node that matches the next character in the route.
+ if let Some(i) = current.indices.iter().position(|&c| c == first) {
+ // The route matches, remove the node.
+ if current.children[i].prefix.unescaped() == remaining {
+ return current.remove_child(i, &remapping);
+ }
+
+ // Otherwise, continue searching.
+ current = &mut current.children[i];
+ continue 'walk;
+ }
+
+ // If the node has a matching wildcard child, continue searching in the child.
+ if current.wild_child
+ && remaining.first().zip(remaining.get(2)) == Some((&b'{', &b'}'))
+ {
+ // The route matches, remove the node.
+ if current.children.last_mut().unwrap().prefix.unescaped() == remaining {
+ return current.remove_child(current.children.len() - 1, &remapping);
+ }
+
+ current = current.children.last_mut().unwrap();
+ continue 'walk;
+ }
+ }
+ }
+
+ // Could not find a match.
+ return None;
+ }
+ }
+
+ /// Remove the child node at the given index, if the route parameters match.
+ fn remove_child(&mut self, i: usize, remapping: &ParamRemapping) -> Option<T> {
+ // Require an exact match to remove a route.
+ //
+ // For example, `/{a}` cannot be used to remove `/{b}`.
+ if self.children[i].remapping != *remapping {
+ return None;
+ }
+
+ // If the node does not have any children, we can remove it completely.
+ let value = if self.children[i].children.is_empty() {
+ // Removing a single child with no indices.
+ if self.children.len() == 1 && self.indices.is_empty() {
+ self.wild_child = false;
+ self.children.remove(0).value.take()
+ } else {
+ // Remove the child node.
+ let child = self.children.remove(i);
+
+ match child.node_type {
+ // Remove the index if we removed a static prefix.
+ NodeType::Static => {
+ self.indices.remove(i);
+ }
+ // Otherwise, we removed a wildcard.
+ _ => self.wild_child = false,
+ }
+
+ child.value
+ }
+ }
+ // Otherwise, remove the value but preserve the node.
+ else {
+ self.children[i].value.take()
+ };
+
+ value.map(UnsafeCell::into_inner)
+ }
+
+ // Adds a child to this node, keeping wildcards at the end.
+ fn add_child(&mut self, child: Node<T>) -> usize {
+ let len = self.children.len();
+
+ if self.wild_child && len > 0 {
+ self.children.insert(len - 1, child);
+ len - 1
+ } else {
+ self.children.push(child);
+ len
+ }
+ }
+
+ // Increments priority of the given child node, reordering the children if necessary.
+ //
+ // Returns the new index of the node.
+ fn update_child_priority(&mut self, i: usize) -> usize {
+ self.children[i].priority += 1;
+ let priority = self.children[i].priority;
+
+ // Move the node to the front as necessary.
+ let mut updated = i;
+ while updated > 0 && self.children[updated - 1].priority < priority {
+ self.children.swap(updated - 1, updated);
+ updated -= 1;
+ }
+
+ // Update the position of the indices to match.
+ if updated != i {
+ self.indices[updated..=i].rotate_right(1);
+ }
+
+ updated
+ }
+
+ // Insert a route at this node.
+ fn insert_route(
+ &mut self,
+ mut prefix: UnescapedRef<'_>,
+ val: T,
+ ) -> Result<&mut Node<T>, InsertError> {
+ let mut current = self;
+
+ loop {
+ // Search for a wildcard segment.
+ let wildcard = match find_wildcard(prefix)? {
+ Some(wildcard) => wildcard,
+ // There is no wildcard, simply insert into the current node.
+ None => {
+ current.value = Some(UnsafeCell::new(val));
+ current.prefix = prefix.to_owned();
+ return Ok(current);
+ }
+ };
+
+ // Insering a catch-all route.
+ if prefix[wildcard.clone()][1] == b'*' {
+ // Ensure there is no suffix after the parameter, e.g. `/foo/{*x}/bar`.
+ if wildcard.end != prefix.len() {
+ return Err(InsertError::InvalidCatchAll);
+ }
+
+ // Add the prefix before the wildcard into the current node.
+ if wildcard.start > 0 {
+ current.prefix = prefix.slice_until(wildcard.start).to_owned();
+ prefix = prefix.slice_off(wildcard.start);
+ }
+
+ // Add the catch-all as a child node.
+ let child = Self {
+ prefix: prefix.to_owned(),
+ node_type: NodeType::CatchAll,
+ value: Some(UnsafeCell::new(val)),
+ priority: 1,
+ ..Self::default()
+ };
+
+ let i = current.add_child(child);
+ current.wild_child = true;
+ return Ok(&mut current.children[i]);
+ }
+
+ // Otherwise, we're inserting a regular route parameter.
+ assert_eq!(prefix[wildcard.clone()][0], b'{');
+
+ // Add the prefix before the wildcard into the current node.
+ if wildcard.start > 0 {
+ current.prefix = prefix.slice_until(wildcard.start).to_owned();
+ prefix = prefix.slice_off(wildcard.start);
+ }
+
+ // Add the parameter as a child node.
+ let child = Self {
+ node_type: NodeType::Param,
+ prefix: prefix.slice_until(wildcard.len()).to_owned(),
+ ..Self::default()
+ };
+
+ let child = current.add_child(child);
+ current.wild_child = true;
+ current = &mut current.children[child];
+ current.priority += 1;
+
+ // If the route doesn't end in the wildcard, we have to insert the suffix as a child.
+ if wildcard.len() < prefix.len() {
+ prefix = prefix.slice_off(wildcard.len());
+ let child = Self {
+ priority: 1,
+ ..Self::default()
+ };
+
+ let child = current.add_child(child);
+ current = &mut current.children[child];
+ continue;
+ }
+
+ // Finally, insert the value.
+ current.value = Some(UnsafeCell::new(val));
+ return Ok(current);
+ }
+ }
+}
+
+/// A wildcard node that was skipped during a tree search.
+///
+/// Contains the state necessary to backtrack to the given node.
+struct Skipped<'n, 'p, T> {
+ // The node that was skipped.
+ node: &'n Node<T>,
+ /// The path at the time we skipped this node.
+ path: &'p [u8],
+ // The number of parameters that were present.
+ params: usize,
+}
+
+#[rustfmt::skip]
+macro_rules! backtracker {
+ ($skipped_nodes:ident, $path:ident, $current:ident, $params:ident, $backtracking:ident, $walk:lifetime) => {
+ macro_rules! try_backtrack {
+ () => {
+ // Try backtracking to any matching wildcard nodes that we skipped while
+ // traversing the tree.
+ while let Some(skipped) = $skipped_nodes.pop() {
+ if skipped.path.ends_with($path) {
+ // Restore the search state.
+ $path = skipped.path;
+ $current = &skipped.node;
+ $params.truncate(skipped.params);
+ $backtracking = true;
+ continue $walk;
+ }
+ }
+ };
+ }
+ };
+}
+
+impl<T> Node<T> {
+ // Returns the node matching the given path.
+ //
+ // Returning an `UnsafeCell` allows us to avoid duplicating the logic between `Node::at` and
+ // `Node::at_mut`, as Rust doesn't have a great way of abstracting over mutability.
+ pub fn at<'node, 'path>(
+ &'node self,
+ full_path: &'path [u8],
+ ) -> Result<(&'node UnsafeCell<T>, Params<'node, 'path>), MatchError> {
+ let mut current = self;
+ let mut path = full_path;
+ let mut backtracking = false;
+ let mut params = Params::new();
+ let mut skipped_nodes: Vec<Skipped<'_, '_, T>> = Vec::new();
+
+ 'walk: loop {
+ // Initialize the backtracker.
+ backtracker!(skipped_nodes, path, current, params, backtracking, 'walk);
+
+ // Reached the end of the search.
+ if path.len() <= current.prefix.len() {
+ // Check for an exact match.
+ if *path == *current.prefix {
+ // Found the matching value.
+ if let Some(ref value) = current.value {
+ // Remap the keys of any route parameters we accumulated during the search.
+ params.for_each_key_mut(|(i, key)| *key = &current.remapping[i]);
+ return Ok((value, params));
+ }
+ }
+
+ // Try backtracking in case we skipped a wildcard that may match.
+ try_backtrack!();
+
+ // Otherwise, there are no matching routes in the tree.
+ return Err(MatchError::NotFound);
+ }
+
+ // Otherwise, the path is longer than this node's prefix, search deeper.
+ let (prefix, rest) = path.split_at(current.prefix.len());
+
+ // The prefix does not match.
+ if *prefix != *current.prefix {
+ // Try backtracking in case we skipped a wildcard that may match.
+ try_backtrack!();
+
+ // Otherwise, there are no matching routes in the tree.
+ return Err(MatchError::NotFound);
+ }
+
+ let previous = path;
+ path = rest;
+
+ // If we are currently backtracking, avoid searching static children
+ // that we already searched.
+ if !backtracking {
+ let next = path[0];
+
+ // Find a child node that matches the next character in the path.
+ if let Some(i) = current.indices.iter().position(|&c| c == next) {
+ // Keep track of wildcard routes that we skip.
+ //
+ // We may end up needing to backtrack later in case we do not find a
+ // match.
+ if current.wild_child {
+ skipped_nodes.push(Skipped {
+ path: previous,
+ node: current,
+ params: params.len(),
+ });
+ }
+
+ // Continue searching.
+ current = &current.children[i];
+ continue 'walk;
+ }
+ }
+
+ // We didn't find a matching static child.
+ //
+ // If there are no wildcards, then there are no matching routes in the tree.
+ if !current.wild_child {
+ // Try backtracking in case we skipped a wildcard that may match.
+ try_backtrack!();
+ return Err(MatchError::NotFound);
+ }
+
+ // Continue searching in the wildcard child, which is kept at the end of the list.
+ current = current.children.last().unwrap();
+ match current.node_type {
+ // Match against a route parameter.
+ NodeType::Param => {
+ // Check for more path segments.
+ let i = match path.iter().position(|&c| c == b'/') {
+ // Found another segment.
+ Some(i) => i,
+ // This is the last path segment.
+ None => {
+ let value = match current.value {
+ // Found the matching value.
+ Some(ref value) => value,
+ None => {
+ // Try backtracking in case we skipped a wildcard that may match.
+ try_backtrack!();
+
+ // Otherwise, there are no matching routes in the tree.
+ return Err(MatchError::NotFound);
+ }
+ };
+
+ // Store the parameter value.
+ // Parameters are normalized so the key is irrelevant for now.
+ params.push(b"", path);
+
+ // Remap the keys of any route parameters we accumulated during the search.
+ params.for_each_key_mut(|(i, key)| *key = &current.remapping[i]);
+
+ return Ok((value, params));
+ }
+ };
+
+ // Found another path segment.
+ let (param, rest) = path.split_at(i);
+
+ // If there is a static child, continue the search.
+ if let [child] = current.children.as_slice() {
+ // Store the parameter value.
+ // Parameters are normalized so the key is irrelevant for now.
+ params.push(b"", param);
+
+ // Continue searching.
+ path = rest;
+ current = child;
+ backtracking = false;
+ continue 'walk;
+ }
+
+ // Try backtracking in case we skipped a wildcard that may match.
+ try_backtrack!();
+
+ // Otherwise, there are no matching routes in the tree.
+ return Err(MatchError::NotFound);
+ }
+ NodeType::CatchAll => {
+ // Catch-all segments are only allowed at the end of the route, meaning
+ // this node must contain the value.
+ let value = match current.value {
+ // Found the matching value.
+ Some(ref value) => value,
+ // Otherwise, there are no matching routes in the tree.
+ None => return Err(MatchError::NotFound),
+ };
+
+ // Remap the keys of any route parameters we accumulated during the search.
+ params.for_each_key_mut(|(i, key)| *key = &current.remapping[i]);
+
+ // Store the final catch-all parameter (`{*...}`).
+ let key = &current.prefix[2..current.prefix.len() - 1];
+ params.push(key, path);
+
+ return Ok((value, params));
+ }
+ _ => unreachable!(),
+ }
+ }
+ }
+
+ /// Test helper that ensures route priorities are consistent.
+ #[cfg(feature = "__test_helpers")]
+ pub fn check_priorities(&self) -> Result<u32, (u32, u32)> {
+ let mut priority: u32 = 0;
+ for child in &self.children {
+ priority += child.check_priorities()?;
+ }
+
+ if self.value.is_some() {
+ priority += 1;
+ }
+
+ if self.priority != priority {
+ return Err((self.priority, priority));
+ }
+
+ Ok(priority)
+ }
+}
+
+/// An ordered list of route parameters keys for a specific route.
+///
+/// To support conflicting routes like `/{a}/foo` and `/{b}/bar`, route parameters
+/// are normalized before being inserted into the tree. Parameter remapping are
+/// stored at nodes containing values, containing the "true" names of all route parameters
+/// for the given route.
+type ParamRemapping = Vec<Vec<u8>>;
+
+/// Returns `path` with normalized route parameters, and a parameter remapping
+/// to store at the node for this route.
+///
+/// Note that the parameter remapping may contain unescaped characters.
+fn normalize_params(
+ mut path: UnescapedRoute,
+) -> Result<(UnescapedRoute, ParamRemapping), InsertError> {
+ let mut start = 0;
+ let mut original = ParamRemapping::new();
+
+ // Parameter names are normalized alphabetically.
+ let mut next = b'a';
+
+ loop {
+ // Find a wildcard to normalize.
+ let mut wildcard = match find_wildcard(path.as_ref().slice_off(start))? {
+ Some(wildcard) => wildcard,
+ // No wildcard, we are done.
+ None => return Ok((path, original)),
+ };
+
+ wildcard.start += start;
+ wildcard.end += start;
+
+ // Ensure the parameter has a valid name.
+ if wildcard.len() < 2 {
+ return Err(InsertError::InvalidParam);
+ }
+
+ // We don't need to normalize catch-all parameters, as they are always
+ // at the end of a route.
+ if path[wildcard.clone()][1] == b'*' {
+ start = wildcard.end;
+ continue;
+ }
+
+ // Normalize the parameter.
+ let removed = path.splice(wildcard.clone(), vec![b'{', next, b'}']);
+
+ // Preserve the original name for remapping.
+ let mut removed = removed.skip(1).collect::<Vec<_>>();
+ removed.pop();
+ original.push(removed);
+
+ next += 1;
+ if next > b'z' {
+ panic!("Too many route parameters.");
+ }
+
+ // Continue the search after the parameter we just normalized.
+ start = wildcard.start + 3;
+ }
+}
+
+/// Restores `route` to it's original, denormalized form.
+pub(crate) fn denormalize_params(route: &mut UnescapedRoute, params: &ParamRemapping) {
+ let mut start = 0;
+ let mut i = 0;
+
+ loop {
+ // Find a wildcard to denormalize.
+ let mut wildcard = match find_wildcard(route.as_ref().slice_off(start)).unwrap() {
+ Some(w) => w,
+ None => return,
+ };
+
+ wildcard.start += start;
+ wildcard.end += start;
+
+ // Get the corresponding parameter remapping.
+ let mut next = match params.get(i) {
+ Some(param) => param.clone(),
+ None => return,
+ };
+
+ // Denormalize this parameter.
+ next.insert(0, b'{');
+ next.push(b'}');
+ let _ = route.splice(wildcard.clone(), next.clone());
+
+ i += 1;
+ start = wildcard.start + next.len();
+ }
+}
+
+// Searches for a wildcard segment and checks the path for invalid characters.
+fn find_wildcard(path: UnescapedRef<'_>) -> Result<Option<Range<usize>>, InsertError> {
+ for (start, &c) in path.iter().enumerate() {
+ // Found an unescaped closing brace without a corresponding opening brace.
+ if c == b'}' && !path.is_escaped(start) {
+ return Err(InsertError::InvalidParam);
+ }
+
+ // Keep going until we find an unescaped opening brace.
+ if c != b'{' || path.is_escaped(start) {
+ continue;
+ }
+
+ // Ensure there is a non-empty parameter name.
+ if path.get(start + 1) == Some(&b'}') {
+ return Err(InsertError::InvalidParam);
+ }
+
+ // Find the corresponding closing brace.
+ for (i, &c) in path.iter().enumerate().skip(start + 2) {
+ match c {
+ b'}' => {
+ // This closing brace was escaped, keep searching.
+ if path.is_escaped(i) {
+ continue;
+ }
+
+ // Ensure catch-all parameters have a non-empty name.
+ if path.get(i - 1) == Some(&b'*') {
+ return Err(InsertError::InvalidParam);
+ }
+
+ if let Some(&c) = path.get(i + 1) {
+ // Prefixes after route parameters are not supported.
+ if c != b'/' {
+ return Err(InsertError::InvalidParamSegment);
+ }
+ }
+
+ return Ok(Some(start..i + 1));
+ }
+ // `*` and `/` are invalid in parameter names.
+ b'*' | b'/' => return Err(InsertError::InvalidParam),
+ _ => {}
+ }
+ }
+
+ // Missing closing brace.
+ return Err(InsertError::InvalidParam);
+ }
+
+ Ok(None)
+}
+
+impl<T> Clone for Node<T>
+where
+ T: Clone,
+{
+ fn clone(&self) -> Self {
+ let value = self.value.as_ref().map(|value| {
+ // Safety: We only expose `&mut T` through `&mut self`.
+ let value = unsafe { &*value.get() };
+ UnsafeCell::new(value.clone())
+ });
+
+ Self {
+ value,
+ prefix: self.prefix.clone(),
+ wild_child: self.wild_child,
+ node_type: self.node_type.clone(),
+ indices: self.indices.clone(),
+ children: self.children.clone(),
+ remapping: self.remapping.clone(),
+ priority: self.priority,
+ }
+ }
+}
+
+impl<T> Default for Node<T> {
+ fn default() -> Self {
+ Self {
+ remapping: ParamRemapping::new(),
+ prefix: UnescapedRoute::default(),
+ wild_child: false,
+ node_type: NodeType::Static,
+ indices: Vec::new(),
+ children: Vec::new(),
+ value: None,
+ priority: 0,
+ }
+ }
+}
+
+impl<T> fmt::Debug for Node<T>
+where
+ T: fmt::Debug,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ // Safety: We only expose `&mut T` through `&mut self`.
+ let value = unsafe { self.value.as_ref().map(|x| &*x.get()) };
+
+ let mut f = f.debug_struct("Node");
+ f.field("value", &value)
+ .field("prefix", &self.prefix)
+ .field("node_type", &self.node_type)
+ .field("children", &self.children);
+
+ // Extra information for debugging purposes.
+ #[cfg(test)]
+ {
+ let indices = self
+ .indices
+ .iter()
+ .map(|&x| char::from_u32(x as _))
+ .collect::<Vec<_>>();
+
+ let params = self
+ .remapping
+ .iter()
+ .map(|x| std::str::from_utf8(x).unwrap())
+ .collect::<Vec<_>>();
+
+ f.field("indices", &indices).field("params", &params);
+ }
+
+ f.finish()
+ }
+}
diff --git a/vendor/matchit/tests/insert.rs b/vendor/matchit/tests/insert.rs
new file mode 100644
index 00000000..5513c1c6
--- /dev/null
+++ b/vendor/matchit/tests/insert.rs
@@ -0,0 +1,243 @@
+use matchit::{InsertError, Router};
+
+struct InsertTest(Vec<(&'static str, Result<(), InsertError>)>);
+
+impl InsertTest {
+ fn run(self) {
+ let mut router = Router::new();
+ for (route, expected) in self.0 {
+ let got = router.insert(route, route.to_owned());
+ assert_eq!(got, expected, "{route}");
+ }
+ }
+}
+
+fn conflict(with: &'static str) -> InsertError {
+ InsertError::Conflict { with: with.into() }
+}
+
+#[test]
+fn wildcard_conflict() {
+ InsertTest(vec![
+ ("/cmd/{tool}/{sub}", Ok(())),
+ ("/cmd/vet", Ok(())),
+ ("/foo/bar", Ok(())),
+ ("/foo/{name}", Ok(())),
+ ("/foo/{names}", Err(conflict("/foo/{name}"))),
+ ("/cmd/{*path}", Err(conflict("/cmd/{tool}/{sub}"))),
+ ("/cmd/{xxx}/names", Ok(())),
+ ("/cmd/{tool}/{xxx}/foo", Ok(())),
+ ("/src/{*filepath}", Ok(())),
+ ("/src/{file}", Err(conflict("/src/{*filepath}"))),
+ ("/src/static.json", Ok(())),
+ ("/src/$filepathx", Ok(())),
+ ("/src/", Ok(())),
+ ("/src/foo/bar", Ok(())),
+ ("/src1/", Ok(())),
+ ("/src1/{*filepath}", Ok(())),
+ ("/src2{*filepath}", Ok(())),
+ ("/src2/{*filepath}", Ok(())),
+ ("/src2/", Ok(())),
+ ("/src2", Ok(())),
+ ("/src3", Ok(())),
+ ("/src3/{*filepath}", Ok(())),
+ ("/search/{query}", Ok(())),
+ ("/search/valid", Ok(())),
+ ("/user_{name}", Ok(())),
+ ("/user_x", Ok(())),
+ ("/user_{bar}", Err(conflict("/user_{name}"))),
+ ("/id{id}", Ok(())),
+ ("/id/{id}", Ok(())),
+ ])
+ .run()
+}
+
+#[test]
+fn invalid_catchall() {
+ InsertTest(vec![
+ ("/non-leading-{*catchall}", Ok(())),
+ ("/foo/bar{*catchall}", Ok(())),
+ ("/src/{*filepath}/x", Err(InsertError::InvalidCatchAll)),
+ ("/src2/", Ok(())),
+ ("/src2/{*filepath}/x", Err(InsertError::InvalidCatchAll)),
+ ])
+ .run()
+}
+
+#[test]
+fn catchall_root_conflict() {
+ InsertTest(vec![("/", Ok(())), ("/{*filepath}", Ok(()))]).run()
+}
+
+#[test]
+fn child_conflict() {
+ InsertTest(vec![
+ ("/cmd/vet", Ok(())),
+ ("/cmd/{tool}", Ok(())),
+ ("/cmd/{tool}/{sub}", Ok(())),
+ ("/cmd/{tool}/misc", Ok(())),
+ ("/cmd/{tool}/{bad}", Err(conflict("/cmd/{tool}/{sub}"))),
+ ("/src/AUTHORS", Ok(())),
+ ("/src/{*filepath}", Ok(())),
+ ("/user_x", Ok(())),
+ ("/user_{name}", Ok(())),
+ ("/id/{id}", Ok(())),
+ ("/id{id}", Ok(())),
+ ("/{id}", Ok(())),
+ ("/{*filepath}", Err(conflict("/{id}"))),
+ ])
+ .run()
+}
+
+#[test]
+fn duplicates() {
+ InsertTest(vec![
+ ("/", Ok(())),
+ ("/", Err(conflict("/"))),
+ ("/doc/", Ok(())),
+ ("/doc/", Err(conflict("/doc/"))),
+ ("/src/{*filepath}", Ok(())),
+ ("/src/{*filepath}", Err(conflict("/src/{*filepath}"))),
+ ("/search/{query}", Ok(())),
+ ("/search/{query}", Err(conflict("/search/{query}"))),
+ ("/user_{name}", Ok(())),
+ ("/user_{name}", Err(conflict("/user_{name}"))),
+ ])
+ .run()
+}
+
+#[test]
+fn unnamed_param() {
+ InsertTest(vec![
+ ("/{}", Err(InsertError::InvalidParam)),
+ ("/user{}/", Err(InsertError::InvalidParam)),
+ ("/cmd/{}/", Err(InsertError::InvalidParam)),
+ ("/src/{*}", Err(InsertError::InvalidParam)),
+ ])
+ .run()
+}
+
+#[test]
+fn double_params() {
+ InsertTest(vec![
+ ("/{foo}{bar}", Err(InsertError::InvalidParamSegment)),
+ ("/{foo}{bar}/", Err(InsertError::InvalidParamSegment)),
+ ("/{foo}{{*bar}/", Err(InsertError::InvalidParamSegment)),
+ ])
+ .run()
+}
+
+#[test]
+fn normalized_conflict() {
+ InsertTest(vec![
+ ("/x/{foo}/bar", Ok(())),
+ ("/x/{bar}/bar", Err(conflict("/x/{foo}/bar"))),
+ ("/{y}/bar/baz", Ok(())),
+ ("/{y}/baz/baz", Ok(())),
+ ("/{z}/bar/bat", Ok(())),
+ ("/{z}/bar/baz", Err(conflict("/{y}/bar/baz"))),
+ ])
+ .run()
+}
+
+#[test]
+fn more_conflicts() {
+ InsertTest(vec![
+ ("/con{tact}", Ok(())),
+ ("/who/are/{*you}", Ok(())),
+ ("/who/foo/hello", Ok(())),
+ ("/whose/{users}/{name}", Ok(())),
+ ("/who/are/foo", Ok(())),
+ ("/who/are/foo/bar", Ok(())),
+ ("/con{nection}", Err(conflict("/con{tact}"))),
+ (
+ "/whose/{users}/{user}",
+ Err(conflict("/whose/{users}/{name}")),
+ ),
+ ])
+ .run()
+}
+
+#[test]
+fn catchall_static_overlap() {
+ InsertTest(vec![
+ ("/bar", Ok(())),
+ ("/bar/", Ok(())),
+ ("/bar/{*foo}", Ok(())),
+ ])
+ .run();
+
+ InsertTest(vec![
+ ("/foo", Ok(())),
+ ("/{*bar}", Ok(())),
+ ("/bar", Ok(())),
+ ("/baz", Ok(())),
+ ("/baz/{split}", Ok(())),
+ ("/", Ok(())),
+ ("/{*bar}", Err(conflict("/{*bar}"))),
+ ("/{*zzz}", Err(conflict("/{*bar}"))),
+ ("/{xxx}", Err(conflict("/{*bar}"))),
+ ])
+ .run();
+
+ InsertTest(vec![
+ ("/{*bar}", Ok(())),
+ ("/bar", Ok(())),
+ ("/bar/x", Ok(())),
+ ("/bar_{x}", Ok(())),
+ ("/bar_{x}", Err(conflict("/bar_{x}"))),
+ ("/bar_{x}/y", Ok(())),
+ ("/bar/{x}", Ok(())),
+ ])
+ .run();
+}
+
+#[test]
+fn duplicate_conflict() {
+ InsertTest(vec![
+ ("/hey", Ok(())),
+ ("/hey/users", Ok(())),
+ ("/hey/user", Ok(())),
+ ("/hey/user", Err(conflict("/hey/user"))),
+ ])
+ .run()
+}
+
+#[test]
+fn invalid_param() {
+ InsertTest(vec![
+ ("{", Err(InsertError::InvalidParam)),
+ ("}", Err(InsertError::InvalidParam)),
+ ("x{y", Err(InsertError::InvalidParam)),
+ ("x}", Err(InsertError::InvalidParam)),
+ ("/{foo}s", Err(InsertError::InvalidParamSegment)),
+ ])
+ .run();
+}
+
+#[test]
+fn escaped_param() {
+ InsertTest(vec![
+ ("{{", Ok(())),
+ ("}}", Ok(())),
+ ("xx}}", Ok(())),
+ ("}}yy", Ok(())),
+ ("}}yy{{}}", Ok(())),
+ ("}}yy{{}}{{}}y{{", Ok(())),
+ ("}}yy{{}}{{}}y{{", Err(conflict("}yy{}{}y{"))),
+ ("/{{yy", Ok(())),
+ ("/{yy}", Ok(())),
+ ("/foo", Ok(())),
+ ("/foo/{{", Ok(())),
+ ("/foo/{{/{x}", Ok(())),
+ ("/foo/{ba{{r}", Ok(())),
+ ("/bar/{ba}}r}", Ok(())),
+ ("/xxx/{x{{}}y}", Ok(())),
+ ])
+ .run()
+}
+
+#[test]
+fn bare_catchall() {
+ InsertTest(vec![("{*foo}", Ok(())), ("foo/{*bar}", Ok(()))]).run()
+}
diff --git a/vendor/matchit/tests/match.rs b/vendor/matchit/tests/match.rs
new file mode 100644
index 00000000..90130fbc
--- /dev/null
+++ b/vendor/matchit/tests/match.rs
@@ -0,0 +1,1047 @@
+use matchit::{MatchError, Router};
+
+// https://github.com/ibraheemdev/matchit/issues/22
+#[test]
+fn partial_overlap() {
+ let mut x = Router::new();
+ x.insert("/foo_bar", "Welcome!").unwrap();
+ x.insert("/foo/bar", "Welcome!").unwrap();
+ assert_eq!(x.at("/foo/").unwrap_err(), MatchError::NotFound);
+
+ let mut x = Router::new();
+ x.insert("/foo", "Welcome!").unwrap();
+ x.insert("/foo/bar", "Welcome!").unwrap();
+ assert_eq!(x.at("/foo/").unwrap_err(), MatchError::NotFound);
+}
+
+// https://github.com/ibraheemdev/matchit/issues/31
+#[test]
+fn wildcard_overlap() {
+ let mut router = Router::new();
+ router.insert("/path/foo", "foo").unwrap();
+ router.insert("/path/{*rest}", "wildcard").unwrap();
+
+ assert_eq!(router.at("/path/foo").map(|m| *m.value), Ok("foo"));
+ assert_eq!(router.at("/path/bar").map(|m| *m.value), Ok("wildcard"));
+ assert_eq!(router.at("/path/foo/").map(|m| *m.value), Ok("wildcard"));
+
+ let mut router = Router::new();
+ router.insert("/path/foo/{arg}", "foo").unwrap();
+ router.insert("/path/{*rest}", "wildcard").unwrap();
+
+ assert_eq!(router.at("/path/foo/myarg").map(|m| *m.value), Ok("foo"));
+ assert_eq!(
+ router.at("/path/foo/myarg/").map(|m| *m.value),
+ Ok("wildcard")
+ );
+ assert_eq!(
+ router.at("/path/foo/myarg/bar/baz").map(|m| *m.value),
+ Ok("wildcard")
+ );
+}
+
+// https://github.com/ibraheemdev/matchit/issues/12
+#[test]
+fn overlapping_param_backtracking() {
+ let mut matcher = Router::new();
+
+ matcher.insert("/{object}/{id}", "object with id").unwrap();
+ matcher
+ .insert("/secret/{id}/path", "secret with id and path")
+ .unwrap();
+
+ let matched = matcher.at("/secret/978/path").unwrap();
+ assert_eq!(matched.params.get("id"), Some("978"));
+
+ let matched = matcher.at("/something/978").unwrap();
+ assert_eq!(matched.params.get("id"), Some("978"));
+ assert_eq!(matched.params.get("object"), Some("something"));
+
+ let matched = matcher.at("/secret/978").unwrap();
+ assert_eq!(matched.params.get("id"), Some("978"));
+}
+
+struct MatchTest {
+ routes: Vec<&'static str>,
+ matches: Vec<(
+ &'static str,
+ &'static str,
+ Result<Vec<(&'static str, &'static str)>, ()>,
+ )>,
+}
+
+impl MatchTest {
+ fn run(self) {
+ let mut router = Router::new();
+
+ for route in self.routes {
+ assert_eq!(router.insert(route, route.to_owned()), Ok(()), "{route}");
+ }
+
+ router.check_priorities().unwrap();
+
+ for (path, route, params) in self.matches {
+ match router.at(path) {
+ Ok(x) => {
+ assert_eq!(x.value, route);
+
+ let got = x.params.iter().collect::<Vec<_>>();
+ assert_eq!(params.unwrap(), got);
+
+ router.at_mut(path).unwrap().value.push_str("Z");
+ assert!(router.at(path).unwrap().value.contains("Z"));
+ router.at_mut(path).unwrap().value.pop();
+ }
+ Err(_) => params.unwrap_err(),
+ }
+ }
+ }
+}
+
+macro_rules! p {
+ ($($k:expr => $v:expr),* $(,)?) => {
+ Ok(vec![$(($k, $v)),*])
+ };
+}
+
+// https://github.com/ibraheemdev/matchit/issues/42
+#[test]
+fn bare_catchall() {
+ MatchTest {
+ routes: vec!["{*foo}", "foo/{*bar}"],
+ matches: vec![
+ ("x/y", "{*foo}", p! { "foo" => "x/y" }),
+ ("/x/y", "{*foo}", p! { "foo" => "/x/y" }),
+ ("/foo/x/y", "{*foo}", p! { "foo" => "/foo/x/y" }),
+ ("foo/x/y", "foo/{*bar}", p! { "bar" => "x/y" }),
+ ],
+ }
+ .run()
+}
+
+#[test]
+fn normalized() {
+ MatchTest {
+ routes: vec![
+ "/x/{foo}/bar",
+ "/x/{bar}/baz",
+ "/{foo}/{baz}/bax",
+ "/{foo}/{bar}/baz",
+ "/{fod}/{baz}/{bax}/foo",
+ "/{fod}/baz/bax/foo",
+ "/{foo}/baz/bax",
+ "/{bar}/{bay}/bay",
+ "/s",
+ "/s/s",
+ "/s/s/s",
+ "/s/s/s/s",
+ "/s/s/{s}/x",
+ "/s/s/{y}/d",
+ ],
+ matches: vec![
+ ("/x/foo/bar", "/x/{foo}/bar", p! { "foo" => "foo" }),
+ ("/x/foo/baz", "/x/{bar}/baz", p! { "bar" => "foo" }),
+ (
+ "/y/foo/baz",
+ "/{foo}/{bar}/baz",
+ p! { "foo" => "y", "bar" => "foo" },
+ ),
+ (
+ "/y/foo/bax",
+ "/{foo}/{baz}/bax",
+ p! { "foo" => "y", "baz" => "foo" },
+ ),
+ (
+ "/y/baz/baz",
+ "/{foo}/{bar}/baz",
+ p! { "foo" => "y", "bar" => "baz" },
+ ),
+ ("/y/baz/bax/foo", "/{fod}/baz/bax/foo", p! { "fod" => "y" }),
+ (
+ "/y/baz/b/foo",
+ "/{fod}/{baz}/{bax}/foo",
+ p! { "fod" => "y", "baz" => "baz", "bax" => "b" },
+ ),
+ ("/y/baz/bax", "/{foo}/baz/bax", p! { "foo" => "y" }),
+ (
+ "/z/bar/bay",
+ "/{bar}/{bay}/bay",
+ p! { "bar" => "z", "bay" => "bar" },
+ ),
+ ("/s", "/s", p! {}),
+ ("/s/s", "/s/s", p! {}),
+ ("/s/s/s", "/s/s/s", p! {}),
+ ("/s/s/s/s", "/s/s/s/s", p! {}),
+ ("/s/s/s/x", "/s/s/{s}/x", p! { "s" => "s" }),
+ ("/s/s/s/d", "/s/s/{y}/d", p! { "y" => "s" }),
+ ],
+ }
+ .run()
+}
+
+#[test]
+fn blog() {
+ MatchTest {
+ routes: vec![
+ "/{page}",
+ "/posts/{year}/{month}/{post}",
+ "/posts/{year}/{month}/index",
+ "/posts/{year}/top",
+ "/static/{*path}",
+ "/favicon.ico",
+ ],
+ matches: vec![
+ ("/about", "/{page}", p! { "page" => "about" }),
+ (
+ "/posts/2021/01/rust",
+ "/posts/{year}/{month}/{post}",
+ p! { "year" => "2021", "month" => "01", "post" => "rust" },
+ ),
+ (
+ "/posts/2021/01/index",
+ "/posts/{year}/{month}/index",
+ p! { "year" => "2021", "month" => "01" },
+ ),
+ (
+ "/posts/2021/top",
+ "/posts/{year}/top",
+ p! { "year" => "2021" },
+ ),
+ (
+ "/static/foo.png",
+ "/static/{*path}",
+ p! { "path" => "foo.png" },
+ ),
+ ("/favicon.ico", "/favicon.ico", p! {}),
+ ],
+ }
+ .run()
+}
+
+#[test]
+fn double_overlap() {
+ MatchTest {
+ routes: vec![
+ "/{object}/{id}",
+ "/secret/{id}/path",
+ "/secret/978",
+ "/other/{object}/{id}/",
+ "/other/an_object/{id}",
+ "/other/static/path",
+ "/other/long/static/path/",
+ ],
+ matches: vec![
+ (
+ "/secret/978/path",
+ "/secret/{id}/path",
+ p! { "id" => "978" },
+ ),
+ (
+ "/some_object/978",
+ "/{object}/{id}",
+ p! { "object" => "some_object", "id" => "978" },
+ ),
+ ("/secret/978", "/secret/978", p! {}),
+ ("/super_secret/978/", "/{object}/{id}", Err(())),
+ (
+ "/other/object/1/",
+ "/other/{object}/{id}/",
+ p! { "object" => "object", "id" => "1" },
+ ),
+ ("/other/object/1/2", "/other/{object}/{id}", Err(())),
+ (
+ "/other/an_object/1",
+ "/other/an_object/{id}",
+ p! { "id" => "1" },
+ ),
+ ("/other/static/path", "/other/static/path", p! {}),
+ (
+ "/other/long/static/path/",
+ "/other/long/static/path/",
+ p! {},
+ ),
+ ],
+ }
+ .run()
+}
+
+#[test]
+fn catchall_off_by_one() {
+ MatchTest {
+ routes: vec!["/foo/{*catchall}", "/bar", "/bar/", "/bar/{*catchall}"],
+ matches: vec![
+ ("/foo", "", Err(())),
+ ("/foo/", "", Err(())),
+ ("/foo/x", "/foo/{*catchall}", p! { "catchall" => "x" }),
+ ("/bar", "/bar", p! {}),
+ ("/bar/", "/bar/", p! {}),
+ ("/bar/x", "/bar/{*catchall}", p! { "catchall" => "x" }),
+ ],
+ }
+ .run()
+}
+
+#[test]
+fn overlap() {
+ MatchTest {
+ routes: vec![
+ "/foo",
+ "/bar",
+ "/{*bar}",
+ "/baz",
+ "/baz/",
+ "/baz/x",
+ "/baz/{xxx}",
+ "/",
+ "/xxx/{*x}",
+ "/xxx/",
+ ],
+ matches: vec![
+ ("/foo", "/foo", p! {}),
+ ("/bar", "/bar", p! {}),
+ ("/baz", "/baz", p! {}),
+ ("/baz/", "/baz/", p! {}),
+ ("/baz/x", "/baz/x", p! {}),
+ ("/???", "/{*bar}", p! { "bar" => "???" }),
+ ("/", "/", p! {}),
+ ("", "", Err(())),
+ ("/xxx/y", "/xxx/{*x}", p! { "x" => "y" }),
+ ("/xxx/", "/xxx/", p! {}),
+ ("/xxx", "/{*bar}", p! { "bar" => "xxx" }),
+ ],
+ }
+ .run()
+}
+
+#[test]
+fn missing_trailing_slash_param() {
+ MatchTest {
+ routes: vec!["/foo/{object}/{id}", "/foo/bar/baz", "/foo/secret/978/"],
+ matches: vec![
+ ("/foo/secret/978/", "/foo/secret/978/", p! {}),
+ (
+ "/foo/secret/978",
+ "/foo/{object}/{id}",
+ p! { "object" => "secret", "id" => "978" },
+ ),
+ ],
+ }
+ .run()
+}
+
+#[test]
+fn extra_trailing_slash_param() {
+ MatchTest {
+ routes: vec!["/foo/{object}/{id}", "/foo/bar/baz", "/foo/secret/978"],
+ matches: vec![
+ ("/foo/secret/978/", "", Err(())),
+ ("/foo/secret/978", "/foo/secret/978", p! {}),
+ ],
+ }
+ .run()
+}
+
+#[test]
+fn missing_trailing_slash_catch_all() {
+ MatchTest {
+ routes: vec!["/foo/{*bar}", "/foo/bar/baz", "/foo/secret/978/"],
+ matches: vec![
+ (
+ "/foo/secret/978",
+ "/foo/{*bar}",
+ p! { "bar" => "secret/978" },
+ ),
+ ("/foo/secret/978/", "/foo/secret/978/", p! {}),
+ ],
+ }
+ .run()
+}
+
+#[test]
+fn extra_trailing_slash_catch_all() {
+ MatchTest {
+ routes: vec!["/foo/{*bar}", "/foo/bar/baz", "/foo/secret/978"],
+ matches: vec![
+ (
+ "/foo/secret/978/",
+ "/foo/{*bar}",
+ p! { "bar" => "secret/978/" },
+ ),
+ ("/foo/secret/978", "/foo/secret/978", p! {}),
+ ],
+ }
+ .run()
+}
+
+#[test]
+fn double_overlap_trailing_slash() {
+ MatchTest {
+ routes: vec![
+ "/{object}/{id}",
+ "/secret/{id}/path",
+ "/secret/978/",
+ "/other/{object}/{id}/",
+ "/other/an_object/{id}",
+ "/other/static/path",
+ "/other/long/static/path/",
+ ],
+ matches: vec![
+ ("/secret/978/path/", "", Err(())),
+ ("/object/id/", "", Err(())),
+ ("/object/id/path", "", Err(())),
+ ("/other/object/1", "", Err(())),
+ ("/other/object/1/2", "", Err(())),
+ (
+ "/other/an_object/1/",
+ "/other/{object}/{id}/",
+ p! { "object" => "an_object", "id" => "1" },
+ ),
+ (
+ "/other/static/path/",
+ "/other/{object}/{id}/",
+ p! { "object" => "static", "id" => "path" },
+ ),
+ ("/other/long/static/path", "", Err(())),
+ ("/other/object/static/path", "", Err(())),
+ ],
+ }
+ .run()
+}
+
+#[test]
+fn trailing_slash_overlap() {
+ MatchTest {
+ routes: vec!["/foo/{x}/baz/", "/foo/{x}/baz", "/foo/bar/bar"],
+ matches: vec![
+ ("/foo/x/baz/", "/foo/{x}/baz/", p! { "x" => "x" }),
+ ("/foo/x/baz", "/foo/{x}/baz", p! { "x" => "x" }),
+ ("/foo/bar/bar", "/foo/bar/bar", p! {}),
+ ],
+ }
+ .run()
+}
+
+#[test]
+fn trailing_slash() {
+ MatchTest {
+ routes: vec![
+ "/hi",
+ "/b/",
+ "/search/{query}",
+ "/cmd/{tool}/",
+ "/src/{*filepath}",
+ "/x",
+ "/x/y",
+ "/y/",
+ "/y/z",
+ "/0/{id}",
+ "/0/{id}/1",
+ "/1/{id}/",
+ "/1/{id}/2",
+ "/aa",
+ "/a/",
+ "/admin",
+ "/admin/static",
+ "/admin/{category}",
+ "/admin/{category}/{page}",
+ "/doc",
+ "/doc/rust_faq.html",
+ "/doc/rust1.26.html",
+ "/no/a",
+ "/no/b",
+ "/no/a/b/{*other}",
+ "/api/{page}/{name}",
+ "/api/hello/{name}/bar/",
+ "/api/bar/{name}",
+ "/api/baz/foo",
+ "/api/baz/foo/bar",
+ "/foo/{p}",
+ ],
+ matches: vec![
+ ("/hi/", "", Err(())),
+ ("/b", "", Err(())),
+ ("/search/rustacean/", "", Err(())),
+ ("/cmd/vet", "", Err(())),
+ ("/src", "", Err(())),
+ ("/src/", "", Err(())),
+ ("/x/", "", Err(())),
+ ("/y", "", Err(())),
+ ("/0/rust/", "", Err(())),
+ ("/1/rust", "", Err(())),
+ ("/a", "", Err(())),
+ ("/admin/", "", Err(())),
+ ("/doc/", "", Err(())),
+ ("/admin/static/", "", Err(())),
+ ("/admin/cfg/", "", Err(())),
+ ("/admin/cfg/users/", "", Err(())),
+ ("/api/hello/x/bar", "", Err(())),
+ ("/api/baz/foo/", "", Err(())),
+ ("/api/baz/bax/", "", Err(())),
+ ("/api/bar/huh/", "", Err(())),
+ ("/api/baz/foo/bar/", "", Err(())),
+ ("/api/world/abc/", "", Err(())),
+ ("/foo/pp/", "", Err(())),
+ ("/", "", Err(())),
+ ("/no", "", Err(())),
+ ("/no/", "", Err(())),
+ ("/no/a/b", "", Err(())),
+ ("/no/a/b/", "", Err(())),
+ ("/_", "", Err(())),
+ ("/_/", "", Err(())),
+ ("/api", "", Err(())),
+ ("/api/", "", Err(())),
+ ("/api/hello/x/foo", "", Err(())),
+ ("/api/baz/foo/bad", "", Err(())),
+ ("/foo/p/p", "", Err(())),
+ ],
+ }
+ .run()
+}
+
+#[test]
+fn backtracking_trailing_slash() {
+ MatchTest {
+ routes: vec!["/a/{b}/{c}", "/a/b/{c}/d/"],
+ matches: vec![("/a/b/c/d", "", Err(()))],
+ }
+ .run()
+}
+
+#[test]
+fn root_trailing_slash() {
+ MatchTest {
+ routes: vec!["/foo", "/bar", "/{baz}"],
+ matches: vec![("/", "", Err(()))],
+ }
+ .run()
+}
+
+#[test]
+fn catchall_overlap() {
+ MatchTest {
+ routes: vec!["/yyy/{*x}", "/yyy{*x}"],
+ matches: vec![
+ ("/yyy/y", "/yyy/{*x}", p! { "x" => "y" }),
+ ("/yyy/", "/yyy{*x}", p! { "x" => "/" }),
+ ],
+ }
+ .run();
+}
+
+#[test]
+fn escaped() {
+ MatchTest {
+ routes: vec![
+ "/",
+ "/{{",
+ "/}}",
+ "/{{x",
+ "/}}y{{",
+ "/xy{{",
+ "/{{/xyz",
+ "/{ba{{r}",
+ "/{ba{{r}/",
+ "/{ba{{r}/x",
+ "/baz/{xxx}",
+ "/baz/{xxx}/xy{{",
+ "/baz/{xxx}/}}xy{{{{",
+ "/{{/{x}",
+ "/xxx/",
+ "/xxx/{x}}{{}}}}{{}}{{{{}}y}",
+ ],
+ matches: vec![
+ ("/", "/", p! {}),
+ ("/{", "/{{", p! {}),
+ ("/}", "/}}", p! {}),
+ ("/{x", "/{{x", p! {}),
+ ("/}y{", "/}}y{{", p! {}),
+ ("/xy{", "/xy{{", p! {}),
+ ("/{/xyz", "/{{/xyz", p! {}),
+ ("/foo", "/{ba{{r}", p! { "ba{r" => "foo" }),
+ ("/{{", "/{ba{{r}", p! { "ba{r" => "{{" }),
+ ("/{{}}/", "/{ba{{r}/", p! { "ba{r" => "{{}}" }),
+ ("/{{}}{{/x", "/{ba{{r}/x", p! { "ba{r" => "{{}}{{" }),
+ ("/baz/x", "/baz/{xxx}", p! { "xxx" => "x" }),
+ ("/baz/x/xy{", "/baz/{xxx}/xy{{", p! { "xxx" => "x" }),
+ ("/baz/x/xy{{", "", Err(())),
+ ("/baz/x/}xy{{", "/baz/{xxx}/}}xy{{{{", p! { "xxx" => "x" }),
+ ("/{/{{", "/{{/{x}", p! { "x" => "{{" }),
+ ("/xxx", "/{ba{{r}", p! { "ba{r" => "xxx" }),
+ ("/xxx/", "/xxx/", p!()),
+ (
+ "/xxx/foo",
+ "/xxx/{x}}{{}}}}{{}}{{{{}}y}",
+ p! { "x}{}}{}{{}y" => "foo" },
+ ),
+ ],
+ }
+ .run()
+}
+
+#[test]
+fn basic() {
+ MatchTest {
+ routes: vec![
+ "/hi",
+ "/contact",
+ "/co",
+ "/c",
+ "/a",
+ "/ab",
+ "/doc/",
+ "/doc/rust_faq.html",
+ "/doc/rust1.26.html",
+ "/ʯ",
+ "/β",
+ "/sd!here",
+ "/sd$here",
+ "/sd&here",
+ "/sd'here",
+ "/sd(here",
+ "/sd)here",
+ "/sd+here",
+ "/sd,here",
+ "/sd;here",
+ "/sd=here",
+ ],
+ matches: vec![
+ ("/a", "/a", p! {}),
+ ("", "/", Err(())),
+ ("/hi", "/hi", p! {}),
+ ("/contact", "/contact", p! {}),
+ ("/co", "/co", p! {}),
+ ("", "/con", Err(())),
+ ("", "/cona", Err(())),
+ ("", "/no", Err(())),
+ ("/ab", "/ab", p! {}),
+ ("/ʯ", "/ʯ", p! {}),
+ ("/β", "/β", p! {}),
+ ("/sd!here", "/sd!here", p! {}),
+ ("/sd$here", "/sd$here", p! {}),
+ ("/sd&here", "/sd&here", p! {}),
+ ("/sd'here", "/sd'here", p! {}),
+ ("/sd(here", "/sd(here", p! {}),
+ ("/sd)here", "/sd)here", p! {}),
+ ("/sd+here", "/sd+here", p! {}),
+ ("/sd,here", "/sd,here", p! {}),
+ ("/sd;here", "/sd;here", p! {}),
+ ("/sd=here", "/sd=here", p! {}),
+ ],
+ }
+ .run()
+}
+
+#[test]
+fn wildcard() {
+ MatchTest {
+ routes: vec![
+ "/",
+ "/cmd/{tool}/",
+ "/cmd/{tool2}/{sub}",
+ "/cmd/whoami",
+ "/cmd/whoami/root",
+ "/cmd/whoami/root/",
+ "/src",
+ "/src/",
+ "/src/{*filepath}",
+ "/search/",
+ "/search/{query}",
+ "/search/actix-web",
+ "/search/google",
+ "/user_{name}",
+ "/user_{name}/about",
+ "/files/{dir}/{*filepath}",
+ "/doc/",
+ "/doc/rust_faq.html",
+ "/doc/rust1.26.html",
+ "/info/{user}/public",
+ "/info/{user}/project/{project}",
+ "/info/{user}/project/rustlang",
+ "/aa/{*xx}",
+ "/ab/{*xx}",
+ "/ab/hello{*xx}",
+ "/{cc}",
+ "/c1/{dd}/e",
+ "/c1/{dd}/e1",
+ "/{cc}/cc",
+ "/{cc}/{dd}/ee",
+ "/{cc}/{dd}/{ee}/ff",
+ "/{cc}/{dd}/{ee}/{ff}/gg",
+ "/{cc}/{dd}/{ee}/{ff}/{gg}/hh",
+ "/get/test/abc/",
+ "/get/{param}/abc/",
+ "/something/{paramname}/thirdthing",
+ "/something/secondthing/test",
+ "/get/abc",
+ "/get/{param}",
+ "/get/abc/123abc",
+ "/get/abc/{param}",
+ "/get/abc/123abc/xxx8",
+ "/get/abc/123abc/{param}",
+ "/get/abc/123abc/xxx8/1234",
+ "/get/abc/123abc/xxx8/{param}",
+ "/get/abc/123abc/xxx8/1234/ffas",
+ "/get/abc/123abc/xxx8/1234/{param}",
+ "/get/abc/123abc/xxx8/1234/kkdd/12c",
+ "/get/abc/123abc/xxx8/1234/kkdd/{param}",
+ "/get/abc/{param}/test",
+ "/get/abc/123abd/{param}",
+ "/get/abc/123abddd/{param}",
+ "/get/abc/123/{param}",
+ "/get/abc/123abg/{param}",
+ "/get/abc/123abf/{param}",
+ "/get/abc/123abfff/{param}",
+ ],
+ matches: vec![
+ ("/", "/", p! {}),
+ ("/cmd/test", "/cmd/{tool}/", Err(())),
+ ("/cmd/test/", "/cmd/{tool}/", p! { "tool" => "test" }),
+ (
+ "/cmd/test/3",
+ "/cmd/{tool2}/{sub}",
+ p! { "tool2" => "test", "sub" => "3" },
+ ),
+ ("/cmd/who", "/cmd/{tool}/", Err(())),
+ ("/cmd/who/", "/cmd/{tool}/", p! { "tool" => "who" }),
+ ("/cmd/whoami", "/cmd/whoami", p! {}),
+ ("/cmd/whoami/", "/cmd/{tool}/", p! { "tool" => "whoami" }),
+ (
+ "/cmd/whoami/r",
+ "/cmd/{tool2}/{sub}",
+ p! { "tool2" => "whoami", "sub" => "r" },
+ ),
+ ("/cmd/whoami/r/", "/cmd/{tool}/{sub}", Err(())),
+ ("/cmd/whoami/root", "/cmd/whoami/root", p! {}),
+ ("/cmd/whoami/root/", "/cmd/whoami/root/", p! {}),
+ ("/src", "/src", p! {}),
+ ("/src/", "/src/", p! {}),
+ (
+ "/src/some/file.png",
+ "/src/{*filepath}",
+ p! { "filepath" => "some/file.png" },
+ ),
+ ("/search/", "/search/", p! {}),
+ (
+ "/search/actix",
+ "/search/{query}",
+ p! { "query" => "actix" },
+ ),
+ ("/search/actix-web", "/search/actix-web", p! {}),
+ (
+ "/search/someth!ng+in+ünìcodé",
+ "/search/{query}",
+ p! { "query" => "someth!ng+in+ünìcodé" },
+ ),
+ ("/search/someth!ng+in+ünìcodé/", "", Err(())),
+ (
+ "/user_rustacean",
+ "/user_{name}",
+ p! { "name" => "rustacean" },
+ ),
+ (
+ "/user_rustacean/about",
+ "/user_{name}/about",
+ p! { "name" => "rustacean" },
+ ),
+ (
+ "/files/js/inc/framework.js",
+ "/files/{dir}/{*filepath}",
+ p! { "dir" => "js", "filepath" => "inc/framework.js" },
+ ),
+ (
+ "/info/gordon/public",
+ "/info/{user}/public",
+ p! { "user" => "gordon" },
+ ),
+ (
+ "/info/gordon/project/rust",
+ "/info/{user}/project/{project}",
+ p! { "user" => "gordon", "project" => "rust" },
+ ),
+ (
+ "/info/gordon/project/rustlang",
+ "/info/{user}/project/rustlang",
+ p! { "user" => "gordon" },
+ ),
+ ("/aa/", "/", Err(())),
+ ("/aa/aa", "/aa/{*xx}", p! { "xx" => "aa" }),
+ ("/ab/ab", "/ab/{*xx}", p! { "xx" => "ab" }),
+ ("/ab/hello-world", "/ab/hello{*xx}", p! { "xx" => "-world" }),
+ ("/a", "/{cc}", p! { "cc" => "a" }),
+ ("/all", "/{cc}", p! { "cc" => "all" }),
+ ("/d", "/{cc}", p! { "cc" => "d" }),
+ ("/ad", "/{cc}", p! { "cc" => "ad" }),
+ ("/dd", "/{cc}", p! { "cc" => "dd" }),
+ ("/dddaa", "/{cc}", p! { "cc" => "dddaa" }),
+ ("/aa", "/{cc}", p! { "cc" => "aa" }),
+ ("/aaa", "/{cc}", p! { "cc" => "aaa" }),
+ ("/aaa/cc", "/{cc}/cc", p! { "cc" => "aaa" }),
+ ("/ab", "/{cc}", p! { "cc" => "ab" }),
+ ("/abb", "/{cc}", p! { "cc" => "abb" }),
+ ("/abb/cc", "/{cc}/cc", p! { "cc" => "abb" }),
+ ("/allxxxx", "/{cc}", p! { "cc" => "allxxxx" }),
+ ("/alldd", "/{cc}", p! { "cc" => "alldd" }),
+ ("/all/cc", "/{cc}/cc", p! { "cc" => "all" }),
+ ("/a/cc", "/{cc}/cc", p! { "cc" => "a" }),
+ ("/c1/d/e", "/c1/{dd}/e", p! { "dd" => "d" }),
+ ("/c1/d/e1", "/c1/{dd}/e1", p! { "dd" => "d" }),
+ (
+ "/c1/d/ee",
+ "/{cc}/{dd}/ee",
+ p! { "cc" => "c1", "dd" => "d" },
+ ),
+ ("/cc/cc", "/{cc}/cc", p! { "cc" => "cc" }),
+ ("/ccc/cc", "/{cc}/cc", p! { "cc" => "ccc" }),
+ ("/deedwjfs/cc", "/{cc}/cc", p! { "cc" => "deedwjfs" }),
+ ("/acllcc/cc", "/{cc}/cc", p! { "cc" => "acllcc" }),
+ ("/get/test/abc/", "/get/test/abc/", p! {}),
+ ("/get/te/abc/", "/get/{param}/abc/", p! { "param" => "te" }),
+ (
+ "/get/testaa/abc/",
+ "/get/{param}/abc/",
+ p! { "param" => "testaa" },
+ ),
+ ("/get/xx/abc/", "/get/{param}/abc/", p! { "param" => "xx" }),
+ ("/get/tt/abc/", "/get/{param}/abc/", p! { "param" => "tt" }),
+ ("/get/a/abc/", "/get/{param}/abc/", p! { "param" => "a" }),
+ ("/get/t/abc/", "/get/{param}/abc/", p! { "param" => "t" }),
+ ("/get/aa/abc/", "/get/{param}/abc/", p! { "param" => "aa" }),
+ (
+ "/get/abas/abc/",
+ "/get/{param}/abc/",
+ p! { "param" => "abas" },
+ ),
+ (
+ "/something/secondthing/test",
+ "/something/secondthing/test",
+ p! {},
+ ),
+ (
+ "/something/abcdad/thirdthing",
+ "/something/{paramname}/thirdthing",
+ p! { "paramname" => "abcdad" },
+ ),
+ (
+ "/something/secondthingaaaa/thirdthing",
+ "/something/{paramname}/thirdthing",
+ p! { "paramname" => "secondthingaaaa" },
+ ),
+ (
+ "/something/se/thirdthing",
+ "/something/{paramname}/thirdthing",
+ p! { "paramname" => "se" },
+ ),
+ (
+ "/something/s/thirdthing",
+ "/something/{paramname}/thirdthing",
+ p! { "paramname" => "s" },
+ ),
+ ("/c/d/ee", "/{cc}/{dd}/ee", p! { "cc" => "c", "dd" => "d" }),
+ (
+ "/c/d/e/ff",
+ "/{cc}/{dd}/{ee}/ff",
+ p! { "cc" => "c", "dd" => "d", "ee" => "e" },
+ ),
+ (
+ "/c/d/e/f/gg",
+ "/{cc}/{dd}/{ee}/{ff}/gg",
+ p! { "cc" => "c", "dd" => "d", "ee" => "e", "ff" => "f" },
+ ),
+ (
+ "/c/d/e/f/g/hh",
+ "/{cc}/{dd}/{ee}/{ff}/{gg}/hh",
+ p! { "cc" => "c", "dd" => "d", "ee" => "e", "ff" => "f", "gg" => "g" },
+ ),
+ (
+ "/cc/dd/ee/ff/gg/hh",
+ "/{cc}/{dd}/{ee}/{ff}/{gg}/hh",
+ p! { "cc" => "cc", "dd" => "dd", "ee" => "ee", "ff" => "ff", "gg" => "gg" },
+ ),
+ ("/get/abc", "/get/abc", p! {}),
+ ("/get/a", "/get/{param}", p! { "param" => "a" }),
+ ("/get/abz", "/get/{param}", p! { "param" => "abz" }),
+ ("/get/12a", "/get/{param}", p! { "param" => "12a" }),
+ ("/get/abcd", "/get/{param}", p! { "param" => "abcd" }),
+ ("/get/abc/123abc", "/get/abc/123abc", p! {}),
+ ("/get/abc/12", "/get/abc/{param}", p! { "param" => "12" }),
+ (
+ "/get/abc/123ab",
+ "/get/abc/{param}",
+ p! { "param" => "123ab" },
+ ),
+ ("/get/abc/xyz", "/get/abc/{param}", p! { "param" => "xyz" }),
+ (
+ "/get/abc/123abcddxx",
+ "/get/abc/{param}",
+ p! { "param" => "123abcddxx" },
+ ),
+ ("/get/abc/123abc/xxx8", "/get/abc/123abc/xxx8", p! {}),
+ (
+ "/get/abc/123abc/x",
+ "/get/abc/123abc/{param}",
+ p! { "param" => "x" },
+ ),
+ (
+ "/get/abc/123abc/xxx",
+ "/get/abc/123abc/{param}",
+ p! { "param" => "xxx" },
+ ),
+ (
+ "/get/abc/123abc/abc",
+ "/get/abc/123abc/{param}",
+ p! { "param" => "abc" },
+ ),
+ (
+ "/get/abc/123abc/xxx8xxas",
+ "/get/abc/123abc/{param}",
+ p! { "param" => "xxx8xxas" },
+ ),
+ (
+ "/get/abc/123abc/xxx8/1234",
+ "/get/abc/123abc/xxx8/1234",
+ p! {},
+ ),
+ (
+ "/get/abc/123abc/xxx8/1",
+ "/get/abc/123abc/xxx8/{param}",
+ p! { "param" => "1" },
+ ),
+ (
+ "/get/abc/123abc/xxx8/123",
+ "/get/abc/123abc/xxx8/{param}",
+ p! { "param" => "123" },
+ ),
+ (
+ "/get/abc/123abc/xxx8/78k",
+ "/get/abc/123abc/xxx8/{param}",
+ p! { "param" => "78k" },
+ ),
+ (
+ "/get/abc/123abc/xxx8/1234xxxd",
+ "/get/abc/123abc/xxx8/{param}",
+ p! { "param" => "1234xxxd" },
+ ),
+ (
+ "/get/abc/123abc/xxx8/1234/ffas",
+ "/get/abc/123abc/xxx8/1234/ffas",
+ p! {},
+ ),
+ (
+ "/get/abc/123abc/xxx8/1234/f",
+ "/get/abc/123abc/xxx8/1234/{param}",
+ p! { "param" => "f" },
+ ),
+ (
+ "/get/abc/123abc/xxx8/1234/ffa",
+ "/get/abc/123abc/xxx8/1234/{param}",
+ p! { "param" => "ffa" },
+ ),
+ (
+ "/get/abc/123abc/xxx8/1234/kka",
+ "/get/abc/123abc/xxx8/1234/{param}",
+ p! { "param" => "kka" },
+ ),
+ (
+ "/get/abc/123abc/xxx8/1234/ffas321",
+ "/get/abc/123abc/xxx8/1234/{param}",
+ p! { "param" => "ffas321" },
+ ),
+ (
+ "/get/abc/123abc/xxx8/1234/kkdd/12c",
+ "/get/abc/123abc/xxx8/1234/kkdd/12c",
+ p! {},
+ ),
+ (
+ "/get/abc/123abc/xxx8/1234/kkdd/1",
+ "/get/abc/123abc/xxx8/1234/kkdd/{param}",
+ p! { "param" => "1" },
+ ),
+ (
+ "/get/abc/123abc/xxx8/1234/kkdd/12",
+ "/get/abc/123abc/xxx8/1234/kkdd/{param}",
+ p! { "param" => "12" },
+ ),
+ (
+ "/get/abc/123abc/xxx8/1234/kkdd/12b",
+ "/get/abc/123abc/xxx8/1234/kkdd/{param}",
+ p! { "param" => "12b" },
+ ),
+ (
+ "/get/abc/123abc/xxx8/1234/kkdd/34",
+ "/get/abc/123abc/xxx8/1234/kkdd/{param}",
+ p! { "param" => "34" },
+ ),
+ (
+ "/get/abc/123abc/xxx8/1234/kkdd/12c2e3",
+ "/get/abc/123abc/xxx8/1234/kkdd/{param}",
+ p! { "param" => "12c2e3" },
+ ),
+ (
+ "/get/abc/12/test",
+ "/get/abc/{param}/test",
+ p! { "param" => "12" },
+ ),
+ (
+ "/get/abc/123abdd/test",
+ "/get/abc/{param}/test",
+ p! { "param" => "123abdd" },
+ ),
+ (
+ "/get/abc/123abdddf/test",
+ "/get/abc/{param}/test",
+ p! { "param" => "123abdddf" },
+ ),
+ (
+ "/get/abc/123ab/test",
+ "/get/abc/{param}/test",
+ p! { "param" => "123ab" },
+ ),
+ (
+ "/get/abc/123abgg/test",
+ "/get/abc/{param}/test",
+ p! { "param" => "123abgg" },
+ ),
+ (
+ "/get/abc/123abff/test",
+ "/get/abc/{param}/test",
+ p! { "param" => "123abff" },
+ ),
+ (
+ "/get/abc/123abffff/test",
+ "/get/abc/{param}/test",
+ p! { "param" => "123abffff" },
+ ),
+ (
+ "/get/abc/123abd/test",
+ "/get/abc/123abd/{param}",
+ p! { "param" => "test" },
+ ),
+ (
+ "/get/abc/123abddd/test",
+ "/get/abc/123abddd/{param}",
+ p! { "param" => "test" },
+ ),
+ (
+ "/get/abc/123/test22",
+ "/get/abc/123/{param}",
+ p! { "param" => "test22" },
+ ),
+ (
+ "/get/abc/123abg/test",
+ "/get/abc/123abg/{param}",
+ p! { "param" => "test" },
+ ),
+ (
+ "/get/abc/123abf/testss",
+ "/get/abc/123abf/{param}",
+ p! { "param" => "testss" },
+ ),
+ (
+ "/get/abc/123abfff/te",
+ "/get/abc/123abfff/{param}",
+ p! { "param" => "te" },
+ ),
+ ],
+ }
+ .run()
+}
diff --git a/vendor/matchit/tests/remove.rs b/vendor/matchit/tests/remove.rs
new file mode 100644
index 00000000..3237ccde
--- /dev/null
+++ b/vendor/matchit/tests/remove.rs
@@ -0,0 +1,265 @@
+use matchit::Router;
+
+struct RemoveTest {
+ routes: Vec<&'static str>,
+ ops: Vec<(Operation, &'static str, Option<&'static str>)>,
+ remaining: Vec<&'static str>,
+}
+
+enum Operation {
+ Insert,
+ Remove,
+}
+
+use Operation::*;
+
+impl RemoveTest {
+ fn run(self) {
+ let mut router = Router::new();
+
+ for route in self.routes.iter() {
+ assert_eq!(router.insert(*route, route.to_owned()), Ok(()), "{route}");
+ }
+
+ for (op, route, expected) in self.ops.iter() {
+ match op {
+ Insert => {
+ assert_eq!(router.insert(*route, route), Ok(()), "{route}")
+ }
+ Remove => {
+ assert_eq!(router.remove(*route), *expected, "removing {route}",)
+ }
+ }
+ }
+
+ for route in self.remaining {
+ assert!(matches!(router.at(route), Ok(_)), "remaining {route}");
+ }
+ }
+}
+
+#[test]
+fn normalized() {
+ RemoveTest {
+ routes: vec![
+ "/x/{foo}/bar",
+ "/x/{bar}/baz",
+ "/{foo}/{baz}/bax",
+ "/{foo}/{bar}/baz",
+ "/{fod}/{baz}/{bax}/foo",
+ "/{fod}/baz/bax/foo",
+ "/{foo}/baz/bax",
+ "/{bar}/{bay}/bay",
+ "/s",
+ "/s/s",
+ "/s/s/s",
+ "/s/s/s/s",
+ "/s/s/{s}/x",
+ "/s/s/{y}/d",
+ ],
+ ops: vec![
+ (Remove, "/x/{foo}/bar", Some("/x/{foo}/bar")),
+ (Remove, "/x/{bar}/baz", Some("/x/{bar}/baz")),
+ (Remove, "/{foo}/{baz}/bax", Some("/{foo}/{baz}/bax")),
+ (Remove, "/{foo}/{bar}/baz", Some("/{foo}/{bar}/baz")),
+ (
+ Remove,
+ "/{fod}/{baz}/{bax}/foo",
+ Some("/{fod}/{baz}/{bax}/foo"),
+ ),
+ (Remove, "/{fod}/baz/bax/foo", Some("/{fod}/baz/bax/foo")),
+ (Remove, "/{foo}/baz/bax", Some("/{foo}/baz/bax")),
+ (Remove, "/{bar}/{bay}/bay", Some("/{bar}/{bay}/bay")),
+ (Remove, "/s", Some("/s")),
+ (Remove, "/s/s", Some("/s/s")),
+ (Remove, "/s/s/s", Some("/s/s/s")),
+ (Remove, "/s/s/s/s", Some("/s/s/s/s")),
+ (Remove, "/s/s/{s}/x", Some("/s/s/{s}/x")),
+ (Remove, "/s/s/{y}/d", Some("/s/s/{y}/d")),
+ ],
+ remaining: vec![],
+ }
+ .run();
+}
+
+#[test]
+fn test() {
+ RemoveTest {
+ routes: vec!["/home", "/home/{id}"],
+ ops: vec![
+ (Remove, "/home", Some("/home")),
+ (Remove, "/home", None),
+ (Remove, "/home/{id}", Some("/home/{id}")),
+ (Remove, "/home/{id}", None),
+ ],
+ remaining: vec![],
+ }
+ .run();
+}
+
+#[test]
+fn blog() {
+ RemoveTest {
+ routes: vec![
+ "/{page}",
+ "/posts/{year}/{month}/{post}",
+ "/posts/{year}/{month}/index",
+ "/posts/{year}/top",
+ "/static/{*path}",
+ "/favicon.ico",
+ ],
+ ops: vec![
+ (Remove, "/{page}", Some("/{page}")),
+ (
+ Remove,
+ "/posts/{year}/{month}/{post}",
+ Some("/posts/{year}/{month}/{post}"),
+ ),
+ (
+ Remove,
+ "/posts/{year}/{month}/index",
+ Some("/posts/{year}/{month}/index"),
+ ),
+ (Remove, "/posts/{year}/top", Some("/posts/{year}/top")),
+ (Remove, "/static/{*path}", Some("/static/{*path}")),
+ (Remove, "/favicon.ico", Some("/favicon.ico")),
+ ],
+ remaining: vec![],
+ }
+ .run()
+}
+
+#[test]
+fn catchall() {
+ RemoveTest {
+ routes: vec!["/foo/{*catchall}", "/bar", "/bar/", "/bar/{*catchall}"],
+ ops: vec![
+ (Remove, "/foo/{*catchall}", Some("/foo/{*catchall}")),
+ (Remove, "/bar/", Some("/bar/")),
+ (Insert, "/foo/*catchall", Some("/foo/*catchall")),
+ (Remove, "/bar/{*catchall}", Some("/bar/{*catchall}")),
+ ],
+ remaining: vec!["/bar", "/foo/*catchall"],
+ }
+ .run();
+}
+
+#[test]
+fn overlapping_routes() {
+ RemoveTest {
+ routes: vec![
+ "/home",
+ "/home/{id}",
+ "/users",
+ "/users/{id}",
+ "/users/{id}/posts",
+ "/users/{id}/posts/{post_id}",
+ "/articles",
+ "/articles/{category}",
+ "/articles/{category}/{id}",
+ ],
+ ops: vec![
+ (Remove, "/home", Some("/home")),
+ (Insert, "/home", Some("/home")),
+ (Remove, "/home/{id}", Some("/home/{id}")),
+ (Insert, "/home/{id}", Some("/home/{id}")),
+ (Remove, "/users", Some("/users")),
+ (Insert, "/users", Some("/users")),
+ (Remove, "/users/{id}", Some("/users/{id}")),
+ (Insert, "/users/{id}", Some("/users/{id}")),
+ (Remove, "/users/{id}/posts", Some("/users/{id}/posts")),
+ (Insert, "/users/{id}/posts", Some("/users/{id}/posts")),
+ (
+ Remove,
+ "/users/{id}/posts/{post_id}",
+ Some("/users/{id}/posts/{post_id}"),
+ ),
+ (
+ Insert,
+ "/users/{id}/posts/{post_id}",
+ Some("/users/{id}/posts/{post_id}"),
+ ),
+ (Remove, "/articles", Some("/articles")),
+ (Insert, "/articles", Some("/articles")),
+ (Remove, "/articles/{category}", Some("/articles/{category}")),
+ (Insert, "/articles/{category}", Some("/articles/{category}")),
+ (
+ Remove,
+ "/articles/{category}/{id}",
+ Some("/articles/{category}/{id}"),
+ ),
+ (
+ Insert,
+ "/articles/{category}/{id}",
+ Some("/articles/{category}/{id}"),
+ ),
+ ],
+ remaining: vec![
+ "/home",
+ "/home/{id}",
+ "/users",
+ "/users/{id}",
+ "/users/{id}/posts",
+ "/users/{id}/posts/{post_id}",
+ "/articles",
+ "/articles/{category}",
+ "/articles/{category}/{id}",
+ ],
+ }
+ .run();
+}
+
+#[test]
+fn trailing_slash() {
+ RemoveTest {
+ routes: vec!["/{home}/", "/foo"],
+ ops: vec![
+ (Remove, "/", None),
+ (Remove, "/{home}", None),
+ (Remove, "/foo/", None),
+ (Remove, "/foo", Some("/foo")),
+ (Remove, "/{home}", None),
+ (Remove, "/{home}/", Some("/{home}/")),
+ ],
+ remaining: vec![],
+ }
+ .run();
+}
+
+#[test]
+fn remove_root() {
+ RemoveTest {
+ routes: vec!["/"],
+ ops: vec![(Remove, "/", Some("/"))],
+ remaining: vec![],
+ }
+ .run();
+}
+
+#[test]
+fn check_escaped_params() {
+ RemoveTest {
+ routes: vec![
+ "/foo/{id}",
+ "/foo/{id}/bar",
+ "/bar/{user}/{id}",
+ "/bar/{user}/{id}/baz",
+ "/baz/{product}/{user}/{id}",
+ ],
+ ops: vec![
+ (Remove, "/foo/{a}", None),
+ (Remove, "/foo/{a}/bar", None),
+ (Remove, "/bar/{a}/{b}", None),
+ (Remove, "/bar/{a}/{b}/baz", None),
+ (Remove, "/baz/{a}/{b}/{c}", None),
+ ],
+ remaining: vec![
+ "/foo/{id}",
+ "/foo/{id}/bar",
+ "/bar/{user}/{id}",
+ "/bar/{user}/{id}/baz",
+ "/baz/{product}/{user}/{id}",
+ ],
+ }
+ .run();
+}