diff options
Diffstat (limited to 'vendor/matchit')
| -rw-r--r-- | vendor/matchit/.cargo-checksum.json | 1 | ||||
| -rw-r--r-- | vendor/matchit/Cargo.lock | 1229 | ||||
| -rw-r--r-- | vendor/matchit/Cargo.toml | 82 | ||||
| -rw-r--r-- | vendor/matchit/LICENSE | 21 | ||||
| -rw-r--r-- | vendor/matchit/LICENSE.httprouter | 29 | ||||
| -rw-r--r-- | vendor/matchit/README.md | 127 | ||||
| -rw-r--r-- | vendor/matchit/benches/bench.rs | 247 | ||||
| -rw-r--r-- | vendor/matchit/examples/hyper.rs | 87 | ||||
| -rw-r--r-- | vendor/matchit/src/error.rs | 127 | ||||
| -rw-r--r-- | vendor/matchit/src/escape.rs | 184 | ||||
| -rw-r--r-- | vendor/matchit/src/lib.rs | 131 | ||||
| -rw-r--r-- | vendor/matchit/src/params.rs | 262 | ||||
| -rw-r--r-- | vendor/matchit/src/router.rs | 147 | ||||
| -rw-r--r-- | vendor/matchit/src/tree.rs | 878 | ||||
| -rw-r--r-- | vendor/matchit/tests/insert.rs | 243 | ||||
| -rw-r--r-- | vendor/matchit/tests/match.rs | 1047 | ||||
| -rw-r--r-- | vendor/matchit/tests/remove.rs | 265 |
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, ¤t.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(¤t.prefix) { + route.append(¤t.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 ¶ms.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 = ¤t.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 = ¤t.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 = ¤t.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 = ¤t.remapping[i]); + + // Store the final catch-all parameter (`{*...}`). + let key = ¤t.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", ¶ms); + } + + 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(); +} |
