matchit-0.7.3/.cargo_vcs_info.json0000644000000001360000000000100124740ustar { "git": { "sha1": "64af4bd02757c7d12412d871c3143b18de60e9df" }, "path_in_vcs": "" }matchit-0.7.3/.github/workflows/rust.yml000064400000000000000000000035661046102023000164130ustar 00000000000000on: [push, pull_request] name: Rust jobs: check: name: Cargo Check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: 1.56 override: true - uses: actions-rs/cargo@v1 with: command: check test: name: Test Suite runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - uses: actions-rs/cargo@v1 with: command: test args: --all-targets --all-features doc: name: Documentation runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - uses: actions-rs/cargo@v1 with: command: test args: --doc fmt: name: Format runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - run: rustup component add rustfmt - uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check clippy: name: Clippy Lints runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - run: rustup component add clippy - uses: actions-rs/cargo@v1 with: command: clippy args: --all-targets --all-features -- --warn warnings --warn clippy::all --warn clippy::pedantic --warn clippy::cargo --warn clippy::nursery matchit-0.7.3/.gitignore000064400000000000000000000000231046102023000132470ustar 00000000000000/target Cargo.lock matchit-0.7.3/.rustfmt.toml000064400000000000000000000000441046102023000137410ustar 00000000000000edition="2018" reorder_imports=true matchit-0.7.3/Cargo.lock0000644000000753730000000000100104660ustar # 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.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[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.7.3" 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" matchit-0.7.3/Cargo.toml0000644000000031030000000000100104670ustar # 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.7.3" authors = ["Ibraheem Ahmed "] 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 = [] matchit-0.7.3/Cargo.toml.orig0000644000000016050000000000100114330ustar [package] name = "matchit" version = "0.7.3" license = "MIT AND BSD-3-Clause" authors = ["Ibraheem Ahmed "] edition = "2021" description = "A high performance, zero-copy URL router." categories = ["network-programming", "algorithms"] keywords = ["router", "path", "tree", "match", "url"] repository = "https://github.com/ibraheemdev/matchit" readme = "README.md" [dependencies] [dev-dependencies] # Benchmarks criterion = "0.3.4" actix-router = "0.2.7" regex = "1.5.4" route-recognizer = "0.3.0" gonzales = "0.0.3-beta" path-tree = "0.2.2" routefinder = "0.5.2" # examples tower = { version = "0.4", features = ["make", "util"] } tokio = { version = "1", features = ["full"] } hyper = { version = "0.14", features = ["full"] } [features] default = [] __test_helpers = [] [[bench]] name = "bench" harness = false [profile.release] lto = true opt-level = 3 codegen-units = 1 matchit-0.7.3/Cargo.toml.orig000064400000000000000000000016051046102023000141550ustar 00000000000000[package] name = "matchit" version = "0.7.3" license = "MIT AND BSD-3-Clause" authors = ["Ibraheem Ahmed "] edition = "2021" description = "A high performance, zero-copy URL router." categories = ["network-programming", "algorithms"] keywords = ["router", "path", "tree", "match", "url"] repository = "https://github.com/ibraheemdev/matchit" readme = "README.md" [dependencies] [dev-dependencies] # Benchmarks criterion = "0.3.4" actix-router = "0.2.7" regex = "1.5.4" route-recognizer = "0.3.0" gonzales = "0.0.3-beta" path-tree = "0.2.2" routefinder = "0.5.2" # examples tower = { version = "0.4", features = ["make", "util"] } tokio = { version = "1", features = ["full"] } hyper = { version = "0.14", features = ["full"] } [features] default = [] __test_helpers = [] [[bench]] name = "bench" harness = false [profile.release] lto = true opt-level = 3 codegen-units = 1 matchit-0.7.3/LICENSE000064400000000000000000000020571046102023000122750ustar 00000000000000MIT 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. matchit-0.7.3/LICENSE.httprouter000064400000000000000000000027621046102023000145170ustar 00000000000000BSD 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. matchit-0.7.3/README.md000064400000000000000000000072451046102023000125530ustar 00000000000000# `matchit` [![Documentation](https://img.shields.io/badge/docs-0.7.3-4d76ae?style=for-the-badge)](https://docs.rs/matchit) [![Version](https://img.shields.io/crates/v/matchit?style=for-the-badge)](https://crates.io/crates/matchit) [![License](https://img.shields.io/crates/l/matchit?style=for-the-badge)](https://crates.io/crates/matchit) A high performance, zero-copy URL router. ```rust use matchit::Router; fn main() -> Result<(), Box> { 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 Along with static routes, the router also supports dynamic route segments. These can either be named or catch-all parameters: ### Named Parameters Named parameters like `/:id` match anything until the next `/` or the end of the path: ```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 Catch-all parameters start with `*` and match everything after the `/`. 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 would not match: assert_eq!(m.at("/").is_err()); ``` ## 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: [197.57 ns 198.74 ns 199.83 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). matchit-0.7.3/examples/hyper.rs000064400000000000000000000054701046102023000146050ustar 00000000000000use 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) -> hyper::Result> { Ok(Response::new(Body::from("Hello, world!"))) } // GET /blog async fn blog(_req: Request) -> hyper::Result> { Ok(Response::new(Body::from("..."))) } // 404 handler async fn not_found(_req: Request) -> hyper::Result> { 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, Response, 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>; async fn route(router: Arc, req: Request) -> hyper::Result> { // 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() } matchit-0.7.3/src/error.rs000064400000000000000000000074721046102023000135640ustar 00000000000000use 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. TooManyParams, /// Parameters must be registered with a name. UnnamedParam, /// 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::TooManyParams => write!(f, "only one parameter is allowed per path segment"), Self::UnnamedParam => write!(f, "parameters must be registered with a 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 { pub(crate) fn conflict(route: &[u8], prefix: &[u8], current: &Node) -> Self { let mut route = route[..route.len() - prefix.len()].to_owned(); if !route.ends_with(¤t.prefix) { route.extend_from_slice(¤t.prefix); } let mut last = current; while let Some(node) = last.children.first() { last = node; } let mut current = current.children.first(); while let Some(node) = current { route.extend_from_slice(&node.prefix); current = node.children.first(); } denormalize_params(&mut route, &last.param_remapping); InsertError::Conflict { with: String::from_utf8(route).unwrap(), } } } /// A failed match attempt. /// /// ``` /// use matchit::{MatchError, Router}; /// # fn main() -> Result<(), Box> { /// let mut router = Router::new(); /// router.insert("/home", "Welcome!")?; /// router.insert("/blog/", "Our blog.")?; /// /// // a route exists without the trailing slash /// if let Err(err) = router.at("/home/") { /// assert_eq!(err, MatchError::ExtraTrailingSlash); /// } /// /// // a route exists with a trailing slash /// if let Err(err) = router.at("/blog") { /// assert_eq!(err, MatchError::MissingTrailingSlash); /// } /// /// // no routes match /// if let Err(err) = router.at("/foobar") { /// assert_eq!(err, MatchError::NotFound); /// } /// # Ok(()) /// # } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum MatchError { /// The path was missing a trailing slash. MissingTrailingSlash, /// The path had an extra trailing slash. ExtraTrailingSlash, /// No matching route was found. NotFound, } impl MatchError { pub(crate) fn unsure(full_path: &[u8]) -> Self { if full_path[full_path.len() - 1] == b'/' { MatchError::ExtraTrailingSlash } else { MatchError::MissingTrailingSlash } } } impl fmt::Display for MatchError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let msg = match self { MatchError::MissingTrailingSlash => "match error: expected trailing slash", MatchError::ExtraTrailingSlash => "match error: found extra trailing slash", MatchError::NotFound => "match error: route not found", }; write!(f, "{}", msg) } } impl std::error::Error for MatchError {} matchit-0.7.3/src/lib.rs000064400000000000000000000073761046102023000132040ustar 00000000000000//! # `matchit` //! //! [![Documentation](https://img.shields.io/badge/docs-0.7.3-4d76ae?style=for-the-badge)](https://docs.rs/matchit) //! [![Version](https://img.shields.io/crates/v/matchit?style=for-the-badge)](https://crates.io/crates/matchit) //! [![License](https://img.shields.io/crates/l/matchit?style=for-the-badge)](https://crates.io/crates/matchit) //! //! A blazing fast URL router. //! //! ```rust //! use matchit::Router; //! //! # fn main() -> Result<(), Box> { //! 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 //! //! Along with static routes, the router also supports dynamic route segments. These can either be named or catch-all parameters: //! //! ### Named Parameters //! //! Named parameters like `/:id` match anything until the next `/` or the end of the path: //! //! ```rust //! # use matchit::Router; //! # fn main() -> Result<(), Box> { //! 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 //! //! Catch-all parameters start with `*` and match everything after the `/`. They must always be at the **end** of the route: //! //! ```rust //! # use matchit::Router; //! # fn main() -> Result<(), Box> { //! 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 would not match //! assert!(m.at("/").is_err()); //! //! # 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> { //! 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. #![deny(rust_2018_idioms, clippy::all)] mod error; mod params; mod router; mod tree; pub use error::{InsertError, MatchError}; pub use params::{Params, ParamsIter}; pub use router::{Match, Router}; #[cfg(doctest)] mod test_readme { macro_rules! doc_comment { ($x:expr) => { #[doc = $x] extern "C" {} }; } doc_comment!(include_str!("../README.md")); } matchit-0.7.3/src/params.rs000064400000000000000000000162331046102023000137110ustar 00000000000000use std::iter; use std::mem; use std::slice; /// A single URL parameter, consisting of a key and a value. #[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Default, Copy, Clone)] struct Param<'k, 'v> { key: &'k [u8], value: &'v [u8], } impl<'k, 'v> Param<'k, 'v> { // this could be from_utf8_unchecked, but we'll keep this safe for now fn key_str(&self) -> &'k str { std::str::from_utf8(self.key).unwrap() } 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> { /// # let mut router = matchit::Router::new(); /// # router.insert("/users/:id", true).unwrap(); /// let matched = router.at("/users/1")?; /// /// // you can iterate through the keys and values /// for (key, value) in matched.params.iter() { /// println!("key: {}, value: {}", key, value); /// } /// /// // or get a specific value by key /// let id = matched.params.get("id"); /// assert_eq!(id, Some("1")); /// # Ok(()) /// # } /// ``` #[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone)] pub struct Params<'k, 'v> { kind: ParamsKind<'k, 'v>, } // most routes have 1-3 dynamic parameters, so we can avoid a heap allocation in common cases. const SMALL: usize = 3; #[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone)] enum ParamsKind<'k, 'v> { None, Small([Param<'k, 'v>; SMALL], usize), Large(Vec>), } impl<'k, 'v> Params<'k, 'v> { pub(crate) fn new() -> Self { let kind = ParamsKind::None; Self { kind } } /// Returns the number of parameters. pub fn len(&self) -> usize { match &self.kind { ParamsKind::None => 0, ParamsKind::Small(_, len) => *len, ParamsKind::Large(vec) => vec.len(), } } pub(crate) fn truncate(&mut self, n: usize) { match &mut self.kind { ParamsKind::None => {} 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) -> Option<&'v str> { let key = key.as_ref().as_bytes(); match &self.kind { ParamsKind::None => None, 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::None => true, ParamsKind::Small(_, len) => *len == 0, ParamsKind::Large(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(len: usize, elem: T, arr: &mut [T; SMALL]) -> Vec { 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::None => { self.kind = ParamsKind::Small([param, Param::default(), Param::default()], 1); } 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), } } // Transform each key. pub(crate) fn for_each_key_mut(&mut self, f: impl Fn((usize, &mut &'k [u8]))) { match &mut self.kind { ParamsKind::None => {} 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), } } } /// 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::None => ParamsIterKind::None, ParamsKind::Small(arr, len) => ParamsIterKind::Small(arr.iter().take(*len)), ParamsKind::Large(vec) => ParamsIterKind::Large(vec.iter()), }; Self { kind } } } enum ParamsIterKind<'ps, 'k, 'v> { None, Small(iter::Take>>), 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 { match self.kind { ParamsIterKind::None => None, 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())) } } } } #[cfg(test)] mod tests { use super::*; #[test] fn no_alloc() { assert_eq!(Params::new().kind, ParamsKind::None); } #[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()); } } matchit-0.7.3/src/router.rs000064400000000000000000000061611046102023000137450ustar 00000000000000use crate::tree::Node; use crate::{InsertError, MatchError, Params}; /// A URL router. /// /// See [the crate documentation](crate) for details. #[derive(Clone)] #[cfg_attr(test, derive(Debug))] pub struct Router { root: Node, } impl Default for Router { fn default() -> Self { Self { root: Node::default(), } } } impl Router { /// Construct a new router. pub fn new() -> Self { Self::default() } /// Insert a route. /// /// # Examples /// /// ```rust /// # use matchit::Router; /// # fn main() -> Result<(), Box> { /// let mut router = Router::new(); /// router.insert("/home", "Welcome!")?; /// router.insert("/users/:id", "A User")?; /// # Ok(()) /// # } /// ``` pub fn insert(&mut self, route: impl Into, value: T) -> Result<(), InsertError> { self.root.insert(route, value) } /// Tries to find a value in the router matching the given path. /// /// # Examples /// /// ```rust /// # use matchit::Router; /// # fn main() -> Result<(), Box> { /// let mut router = Router::new(); /// router.insert("/home", "Welcome!")?; /// /// let matched = router.at("/home").unwrap(); /// assert_eq!(*matched.value, "Welcome!"); /// # Ok(()) /// # } /// ``` pub fn at<'m, 'p>(&'m self, path: &'p str) -> Result, 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> { /// 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<'m, 'p>( &'m mut self, path: &'p str, ) -> Result, 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), } } #[cfg(feature = "__test_helpers")] pub fn check_priorities(&self) -> Result { 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>, } matchit-0.7.3/src/tree.rs000064400000000000000000000655421046102023000133740ustar 00000000000000use crate::{InsertError, MatchError, Params}; use std::cell::UnsafeCell; use std::cmp::min; use std::mem; /// The types of nodes the tree can hold #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] pub(crate) enum NodeType { /// The root path Root, /// A route parameter, ex: `/:id`. Param, /// A catchall parameter, ex: `/*file` CatchAll, /// Anything else Static, } /// A radix tree used for URL path matching. /// /// See [the crate documentation](crate) for details. pub struct Node { priority: u32, wild_child: bool, indices: Vec, // see `at` for why an unsafe cell is needed value: Option>, pub(crate) param_remapping: ParamRemapping, pub(crate) node_type: NodeType, pub(crate) prefix: Vec, pub(crate) children: Vec, } // SAFETY: we expose `value` per rust's usual borrowing rules, so we can just delegate these traits unsafe impl Send for Node {} unsafe impl Sync for Node {} impl Node { pub fn insert(&mut self, route: impl Into, val: T) -> Result<(), InsertError> { let route = route.into().into_bytes(); let (route, param_remapping) = normalize_params(route)?; let mut prefix = route.as_ref(); self.priority += 1; // the tree is empty if self.prefix.is_empty() && self.children.is_empty() { let last = self.insert_child(prefix, &route, val)?; last.param_remapping = param_remapping; self.node_type = NodeType::Root; return Ok(()); } let mut current = self; 'walk: loop { // find the longest common prefix let len = min(prefix.len(), current.prefix.len()); let common_prefix = (0..len) .find(|&i| prefix[i] != current.prefix[i]) .unwrap_or(len); // the common prefix is a substring of the current node's prefix, split the node if common_prefix < current.prefix.len() { let child = Node { prefix: current.prefix[common_prefix..].to_owned(), children: mem::take(&mut current.children), wild_child: current.wild_child, indices: current.indices.clone(), value: current.value.take(), param_remapping: mem::take(&mut current.param_remapping), priority: current.priority - 1, ..Node::default() }; // the current node now holds only the common prefix current.children = vec![child]; current.indices = vec![current.prefix[common_prefix]]; current.prefix = prefix[..common_prefix].to_owned(); current.wild_child = false; } // the route has a common prefix, search deeper if prefix.len() > common_prefix { prefix = &prefix[common_prefix..]; let next = prefix[0]; // `/` after param if current.node_type == NodeType::Param && next == b'/' && current.children.len() == 1 { current = &mut current.children[0]; current.priority += 1; continue 'walk; } // find a child that matches the next path byte for mut i in 0..current.indices.len() { // found a match if next == current.indices[i] { i = current.update_child_priority(i); current = &mut current.children[i]; continue 'walk; } } // not a wildcard and there is no matching child node, create a new one if !matches!(next, b':' | b'*') && 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 new node let last = current.children[child].insert_child(prefix, &route, val)?; last.param_remapping = param_remapping; return Ok(()); } // inserting a wildcard, and this node already has a wildcard child if current.wild_child { // wildcards are always at the end current = current.children.last_mut().unwrap(); current.priority += 1; // make sure the wildcard matches if prefix.len() < current.prefix.len() || current.prefix != prefix[..current.prefix.len()] // catch-alls cannot have children || current.node_type == NodeType::CatchAll // check for longer wildcard, e.g. :name and :names || (current.prefix.len() < prefix.len() && prefix[current.prefix.len()] != b'/') { return Err(InsertError::conflict(&route, prefix, current)); } continue 'walk; } // otherwise, create the wildcard node let last = current.insert_child(prefix, &route, val)?; last.param_remapping = param_remapping; return Ok(()); } // exact match, this node should be empty if current.value.is_some() { return Err(InsertError::conflict(&route, prefix, current)); } // add the value to current node current.value = Some(UnsafeCell::new(val)); current.param_remapping = param_remapping; return Ok(()); } } // add a child node, keeping wildcards at the end fn add_child(&mut self, child: Node) -> 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 and reorders if necessary. // // returns the new index of the child fn update_child_priority(&mut self, i: usize) -> usize { self.children[i].priority += 1; let priority = self.children[i].priority; // adjust position (move to front) let mut updated = i; while updated > 0 && self.children[updated - 1].priority < priority { // swap node positions self.children.swap(updated - 1, updated); updated -= 1; } // build new index list if updated != i { self.indices = [ &self.indices[..updated], // unchanged prefix, might be empty &self.indices[i..=i], // the index char we move &self.indices[updated..i], // rest without char at 'pos' &self.indices[i + 1..], ] .concat(); } updated } // insert a child node at this node fn insert_child( &mut self, mut prefix: &[u8], route: &[u8], val: T, ) -> Result<&mut Node, InsertError> { let mut current = self; loop { // search for a wildcard segment let (wildcard, wildcard_index) = match find_wildcard(prefix)? { Some((w, i)) => (w, i), // no wildcard, simply use the current node None => { current.value = Some(UnsafeCell::new(val)); current.prefix = prefix.to_owned(); return Ok(current); } }; // regular route parameter if wildcard[0] == b':' { // insert prefix before the current wildcard if wildcard_index > 0 { current.prefix = prefix[..wildcard_index].to_owned(); prefix = &prefix[wildcard_index..]; } let child = Self { node_type: NodeType::Param, prefix: wildcard.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 with the wildcard, then there // will be another non-wildcard subroute starting with '/' if wildcard.len() < prefix.len() { prefix = &prefix[wildcard.len()..]; let child = Self { priority: 1, ..Self::default() }; let child = current.add_child(child); current = &mut current.children[child]; continue; } // otherwise we're done. Insert the value in the new leaf current.value = Some(UnsafeCell::new(val)); return Ok(current); // catch-all route } else if wildcard[0] == b'*' { // "/foo/*x/bar" if wildcard_index + wildcard.len() != prefix.len() { return Err(InsertError::InvalidCatchAll); } if let Some(i) = wildcard_index.checked_sub(1) { // "/foo/bar*x" if prefix[i] != b'/' { return Err(InsertError::InvalidCatchAll); } } // "*x" without leading `/` if prefix == route && route[0] != b'/' { return Err(InsertError::InvalidCatchAll); } // insert prefix before the current wildcard if wildcard_index > 0 { current.prefix = prefix[..wildcard_index].to_owned(); prefix = &prefix[wildcard_index..]; } 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]); } } } } struct Skipped<'n, 'p, T> { path: &'p [u8], node: &'n Node, 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 we skipped while traversing // the tree while let Some(skipped) = $skipped_nodes.pop() { if skipped.path.ends_with($path) { $path = skipped.path; $current = &skipped.node; $params.truncate(skipped.params); $backtracking = true; continue $walk; } } }; } }; } impl Node { // it's a bit sad that we have to introduce unsafe here but rust doesn't really have a way // to abstract over mutability, so `UnsafeCell` lets us avoid having to duplicate logic between // `at` and `at_mut` pub fn at<'n, 'p>( &'n self, full_path: &'p [u8], ) -> Result<(&'n UnsafeCell, Params<'n, 'p>), MatchError> { let mut current = self; let mut path = full_path; let mut backtracking = false; let mut params = Params::new(); let mut skipped_nodes = Vec::new(); 'walk: loop { backtracker!(skipped_nodes, path, current, params, backtracking, 'walk); // the path is longer than this node's prefix, we are expecting a child node if path.len() > current.prefix.len() { let (prefix, rest) = path.split_at(current.prefix.len()); // the prefix matches if prefix == current.prefix { let first = rest[0]; let consumed = path; path = rest; // try searching for a matching static child unless we are currently // backtracking, which would mean we already traversed them if !backtracking { if let Some(i) = current.indices.iter().position(|&c| c == first) { // keep track of wildcard routes we skipped to backtrack to later if // we don't find a math if current.wild_child { skipped_nodes.push(Skipped { path: consumed, node: current, params: params.len(), }); } // child won't match because of an extra trailing slash if path == b"/" && current.children[i].prefix != b"/" && current.value.is_some() { return Err(MatchError::ExtraTrailingSlash); } // continue with the child node current = ¤t.children[i]; continue 'walk; } } // we didn't find a match and there are no children with wildcards, there is no match if !current.wild_child { // extra trailing slash if path == b"/" && current.value.is_some() { return Err(MatchError::ExtraTrailingSlash); } // try backtracking if path != b"/" { try_backtrack!(); } // nothing found return Err(MatchError::NotFound); } // handle the wildcard child, which is always at the end of the list current = current.children.last().unwrap(); match current.node_type { NodeType::Param => { // check if there are more segments in the path other than this parameter match path.iter().position(|&c| c == b'/') { Some(i) => { let (param, rest) = path.split_at(i); if let [child] = current.children.as_slice() { // child won't match because of an extra trailing slash if rest == b"/" && child.prefix != b"/" && current.value.is_some() { return Err(MatchError::ExtraTrailingSlash); } // store the parameter value params.push(¤t.prefix[1..], param); // continue with the child node path = rest; current = child; backtracking = false; continue 'walk; } // this node has no children yet the path has more segments... // either the path has an extra trailing slash or there is no match if path.len() == i + 1 { return Err(MatchError::ExtraTrailingSlash); } // try backtracking if path != b"/" { try_backtrack!(); } return Err(MatchError::NotFound); } // this is the last path segment None => { // store the parameter value params.push(¤t.prefix[1..], path); // found the matching value if let Some(ref value) = current.value { // remap parameter keys params.for_each_key_mut(|(i, key)| { *key = ¤t.param_remapping[i][1..] }); return Ok((value, params)); } // check the child node in case the path is missing a trailing slash if let [child] = current.children.as_slice() { current = child; if (current.prefix == b"/" && current.value.is_some()) || (current.prefix.is_empty() && current.indices == b"/") { return Err(MatchError::MissingTrailingSlash); } // no match, try backtracking if path != b"/" { try_backtrack!(); } } // this node doesn't have the value, no match return Err(MatchError::NotFound); } } } NodeType::CatchAll => { // catch all segments are only allowed at the end of the route, // either this node has the value or there is no match return match current.value { Some(ref value) => { // remap parameter keys params.for_each_key_mut(|(i, key)| { *key = ¤t.param_remapping[i][1..] }); // store the final catch-all parameter params.push(¤t.prefix[1..], path); Ok((value, params)) } None => Err(MatchError::NotFound), }; } _ => unreachable!(), } } } // this is it, we should have reached the node containing the value if path == current.prefix { if let Some(ref value) = current.value { // remap parameter keys params.for_each_key_mut(|(i, key)| *key = ¤t.param_remapping[i][1..]); return Ok((value, params)); } // nope, try backtracking if path != b"/" { try_backtrack!(); } // TODO: does this *always* means there is an extra trailing slash? if path == b"/" && current.wild_child && current.node_type != NodeType::Root { return Err(MatchError::unsure(full_path)); } if !backtracking { // check if the path is missing a trailing slash if let Some(i) = current.indices.iter().position(|&c| c == b'/') { current = ¤t.children[i]; if current.prefix.len() == 1 && current.value.is_some() { return Err(MatchError::MissingTrailingSlash); } } } return Err(MatchError::NotFound); } // nothing matches, check for a missing trailing slash if current.prefix.split_last() == Some((&b'/', path)) && current.value.is_some() { return Err(MatchError::MissingTrailingSlash); } // last chance, try backtracking if path != b"/" { try_backtrack!(); } return Err(MatchError::NotFound); } } #[cfg(feature = "__test_helpers")] pub fn check_priorities(&self) -> Result { 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, stored at leaf nodes. type ParamRemapping = Vec>; /// Returns `path` with normalized route parameters, and a parameter remapping /// to store at the leaf node for this route. fn normalize_params(mut path: Vec) -> Result<(Vec, ParamRemapping), InsertError> { let mut start = 0; let mut original = ParamRemapping::new(); // parameter names are normalized alphabetically let mut next = b'a'; loop { let (wildcard, mut wildcard_index) = match find_wildcard(&path[start..])? { Some((w, i)) => (w, i), None => return Ok((path, original)), }; // makes sure the param has a valid name if wildcard.len() < 2 { return Err(InsertError::UnnamedParam); } // don't need to normalize catch-all parameters if wildcard[0] == b'*' { start += wildcard_index + wildcard.len(); continue; } wildcard_index += start; // normalize the parameter let removed = path.splice( (wildcard_index)..(wildcard_index + wildcard.len()), vec![b':', next], ); // remember the original name for remappings original.push(removed.collect()); // get the next key next += 1; if next > b'z' { panic!("too many route parameters"); } start = wildcard_index + 2; } } /// Restores `route` to it's original, denormalized form. pub(crate) fn denormalize_params(route: &mut Vec, params: &ParamRemapping) { let mut start = 0; let mut i = 0; loop { // find the next wildcard let (wildcard, mut wildcard_index) = match find_wildcard(&route[start..]).unwrap() { Some((w, i)) => (w, i), None => return, }; wildcard_index += start; let next = match params.get(i) { Some(param) => param.clone(), None => return, }; // denormalize this parameter route.splice( (wildcard_index)..(wildcard_index + wildcard.len()), next.clone(), ); i += 1; start = wildcard_index + 2; } } // Searches for a wildcard segment and checks the path for invalid characters. fn find_wildcard(path: &[u8]) -> Result, InsertError> { for (start, &c) in path.iter().enumerate() { // a wildcard starts with ':' (param) or '*' (catch-all) if c != b':' && c != b'*' { continue; } for (end, &c) in path[start + 1..].iter().enumerate() { match c { b'/' => return Ok(Some((&path[start..start + 1 + end], start))), b':' | b'*' => return Err(InsertError::TooManyParams), _ => {} } } return Ok(Some((&path[start..], start))); } Ok(None) } impl Clone for Node 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(), param_remapping: self.param_remapping.clone(), priority: self.priority, } } } impl Default for Node { fn default() -> Self { Self { param_remapping: ParamRemapping::new(), prefix: Vec::new(), wild_child: false, node_type: NodeType::Static, indices: Vec::new(), children: Vec::new(), value: None, priority: 0, } } } #[cfg(test)] const _: () = { use std::fmt::{self, Debug, Formatter}; // visualize the tree structure when debugging impl Debug for Node { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { // safety: we only expose &mut T through &mut self let value = unsafe { self.value.as_ref().map(|x| &*x.get()) }; let indices = self .indices .iter() .map(|&x| char::from_u32(x as _)) .collect::>(); let param_names = self .param_remapping .iter() .map(|x| std::str::from_utf8(x).unwrap()) .collect::>(); let mut fmt = f.debug_struct("Node"); fmt.field("value", &value); fmt.field("prefix", &std::str::from_utf8(&self.prefix)); fmt.field("node_type", &self.node_type); fmt.field("children", &self.children); fmt.field("param_names", ¶m_names); fmt.field("indices", &indices); fmt.finish() } } }; matchit-0.7.3/tests/tree.rs000064400000000000000000001060041046102023000137340ustar 00000000000000use matchit::{InsertError, MatchError, Router}; #[test] fn issue_31() { 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/bar/baz").map(|m| *m.value), Ok("wildcard") ); } #[test] fn issue_22() { 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::ExtraTrailingSlash); } match_tests! { basic { routes = [ "/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", ], "/a" :: "/a" => {}, "" :: "/" => None, "/hi" :: "/hi" => {}, "/contact" :: "/contact" => {}, "/co" :: "/co" => {}, "" :: "/con" => None, "" :: "/cona" => None, "" :: "/no" => None, "/ab" :: "/ab" => {}, "/ʯ" :: "/ʯ" => {}, "/β" :: "/β" => {}, "/sd!here" :: "/sd!here" => {}, "/sd$here" :: "/sd$here" => {}, "/sd&here" :: "/sd&here" => {}, "/sd'here" :: "/sd'here" => {}, "/sd(here" :: "/sd(here" => {}, "/sd)here" :: "/sd)here" => {}, "/sd+here" :: "/sd+here" => {}, "/sd,here" :: "/sd,here" => {}, "/sd;here" :: "/sd;here" => {}, "/sd=here" :: "/sd=here" => {}, }, wildcard { routes = [ "/", "/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", "/: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", ], "/" :: "/" => {}, "/cmd/test" :: "/cmd/:tool/" => None, "/cmd/test/" :: "/cmd/:tool/" => { "tool" => "test" }, "/cmd/test/3" :: "/cmd/:tool2/:sub" => { "tool2" => "test", "sub" => "3" }, "/cmd/who" :: "/cmd/:tool/" => None, "/cmd/who/" :: "/cmd/:tool/" => { "tool" => "who" }, "/cmd/whoami" :: "/cmd/whoami" => {}, "/cmd/whoami/" :: "/cmd/whoami" => None, "/cmd/whoami/r" :: "/cmd/:tool2/:sub" => { "tool2" => "whoami", "sub" => "r" }, "/cmd/whoami/r/" :: "/cmd/:tool/:sub" => None, "/cmd/whoami/root" :: "/cmd/whoami/root" => {}, "/cmd/whoami/root/" :: "/cmd/whoami/root/" => {}, "/src" :: "/src" => {}, "/src/" :: "/src/" => {}, "/src/some/file.png" :: "/src/*filepath" => { "filepath" => "some/file.png" }, "/search/" :: "/search/" => {}, "/search/actix" :: "/search/:query" => { "query" => "actix" }, "/search/actix-web" :: "/search/actix-web" => {}, "/search/someth!ng+in+ünìcodé" :: "/search/:query" => { "query" => "someth!ng+in+ünìcodé" }, "/search/someth!ng+in+ünìcodé/" :: "" => None, "/user_rustacean" :: "/user_:name" => { "name" => "rustacean" }, "/user_rustacean/about" :: "/user_:name/about" => { "name" => "rustacean" }, "/files/js/inc/framework.js" :: "/files/:dir/*filepath" => { "dir" => "js", "filepath" => "inc/framework.js" }, "/info/gordon/public" :: "/info/:user/public" => { "user" => "gordon" }, "/info/gordon/project/rust" :: "/info/:user/project/:project" => { "user" => "gordon", "project" => "rust" } , "/info/gordon/project/rustlang" :: "/info/:user/project/rustlang" => { "user" => "gordon" }, "/aa/" :: "/" => None, "/aa/aa" :: "/aa/*xx" => { "xx" => "aa" }, "/ab/ab" :: "/ab/*xx" => { "xx" => "ab" }, "/a" :: "/:cc" => { "cc" => "a" }, "/all" :: "/:cc" => { "cc" => "all" }, "/d" :: "/:cc" => { "cc" => "d" }, "/ad" :: "/:cc" => { "cc" => "ad" }, "/dd" :: "/:cc" => { "cc" => "dd" }, "/dddaa" :: "/:cc" => { "cc" => "dddaa" }, "/aa" :: "/:cc" => { "cc" => "aa" }, "/aaa" :: "/:cc" => { "cc" => "aaa" }, "/aaa/cc" :: "/:cc/cc" => { "cc" => "aaa" }, "/ab" :: "/:cc" => { "cc" => "ab" }, "/abb" :: "/:cc" => { "cc" => "abb" }, "/abb/cc" :: "/:cc/cc" => { "cc" => "abb" }, "/allxxxx" :: "/:cc" => { "cc" => "allxxxx" }, "/alldd" :: "/:cc" => { "cc" => "alldd" }, "/all/cc" :: "/:cc/cc" => { "cc" => "all" }, "/a/cc" :: "/:cc/cc" => { "cc" => "a" }, "/c1/d/e" :: "/c1/:dd/e" => { "dd" => "d" }, "/c1/d/e1" :: "/c1/:dd/e1" => { "dd" => "d" }, "/c1/d/ee" :: "/:cc/:dd/ee" => { "cc" => "c1", "dd" => "d" }, "/cc/cc" :: "/:cc/cc" => { "cc" => "cc" }, "/ccc/cc" :: "/:cc/cc" => { "cc" => "ccc" }, "/deedwjfs/cc" :: "/:cc/cc" => { "cc" => "deedwjfs" }, "/acllcc/cc" :: "/:cc/cc" => { "cc" => "acllcc" }, "/get/test/abc/" :: "/get/test/abc/" => {}, "/get/te/abc/" :: "/get/:param/abc/" => { "param" => "te" }, "/get/testaa/abc/" :: "/get/:param/abc/" => { "param" => "testaa" }, "/get/xx/abc/" :: "/get/:param/abc/" => { "param" => "xx" }, "/get/tt/abc/" :: "/get/:param/abc/" => { "param" => "tt" }, "/get/a/abc/" :: "/get/:param/abc/" => { "param" => "a" }, "/get/t/abc/" :: "/get/:param/abc/" => { "param" => "t" }, "/get/aa/abc/" :: "/get/:param/abc/" => { "param" => "aa" }, "/get/abas/abc/" :: "/get/:param/abc/" => { "param" => "abas" }, "/something/secondthing/test" :: "/something/secondthing/test" => {}, "/something/abcdad/thirdthing" :: "/something/:paramname/thirdthing" => { "paramname" => "abcdad" }, "/something/secondthingaaaa/thirdthing" :: "/something/:paramname/thirdthing" => { "paramname" => "secondthingaaaa" }, "/something/se/thirdthing" :: "/something/:paramname/thirdthing" => { "paramname" => "se" }, "/something/s/thirdthing" :: "/something/:paramname/thirdthing" => { "paramname" => "s" }, "/c/d/ee" :: "/:cc/:dd/ee" => { "cc" => "c", "dd" => "d" }, "/c/d/e/ff" :: "/:cc/:dd/:ee/ff" => { "cc" => "c", "dd" => "d", "ee" => "e" }, "/c/d/e/f/gg" :: "/:cc/:dd/:ee/:ff/gg" => { "cc" => "c", "dd" => "d", "ee" => "e", "ff" => "f" }, "/c/d/e/f/g/hh" :: "/:cc/:dd/:ee/:ff/:gg/hh" => { "cc" => "c", "dd" => "d", "ee" => "e", "ff" => "f", "gg" => "g" }, "/cc/dd/ee/ff/gg/hh" :: "/:cc/:dd/:ee/:ff/:gg/hh" => { "cc" => "cc", "dd" => "dd", "ee" => "ee", "ff" => "ff", "gg" => "gg" }, "/get/abc" :: "/get/abc" => {}, "/get/a" :: "/get/:param" => { "param" => "a" }, "/get/abz" :: "/get/:param" => { "param" => "abz" }, "/get/12a" :: "/get/:param" => { "param" => "12a" }, "/get/abcd" :: "/get/:param" => { "param" => "abcd" }, "/get/abc/123abc" :: "/get/abc/123abc" => {}, "/get/abc/12" :: "/get/abc/:param" => { "param" => "12" }, "/get/abc/123ab" :: "/get/abc/:param" => { "param" => "123ab" }, "/get/abc/xyz" :: "/get/abc/:param" => { "param" => "xyz" }, "/get/abc/123abcddxx" :: "/get/abc/:param" => { "param" => "123abcddxx" }, "/get/abc/123abc/xxx8" :: "/get/abc/123abc/xxx8" => {}, "/get/abc/123abc/x" :: "/get/abc/123abc/:param" => { "param" => "x" }, "/get/abc/123abc/xxx" :: "/get/abc/123abc/:param" => { "param" => "xxx" }, "/get/abc/123abc/abc" :: "/get/abc/123abc/:param" => { "param" => "abc" }, "/get/abc/123abc/xxx8xxas" :: "/get/abc/123abc/:param" => { "param" => "xxx8xxas" }, "/get/abc/123abc/xxx8/1234" :: "/get/abc/123abc/xxx8/1234" => {}, "/get/abc/123abc/xxx8/1" :: "/get/abc/123abc/xxx8/:param" => { "param" => "1" }, "/get/abc/123abc/xxx8/123" :: "/get/abc/123abc/xxx8/:param" => { "param" => "123" }, "/get/abc/123abc/xxx8/78k" :: "/get/abc/123abc/xxx8/:param" => { "param" => "78k" }, "/get/abc/123abc/xxx8/1234xxxd" :: "/get/abc/123abc/xxx8/:param" => { "param" => "1234xxxd" }, "/get/abc/123abc/xxx8/1234/ffas" :: "/get/abc/123abc/xxx8/1234/ffas" => {}, "/get/abc/123abc/xxx8/1234/f" :: "/get/abc/123abc/xxx8/1234/:param" => { "param" => "f" }, "/get/abc/123abc/xxx8/1234/ffa" :: "/get/abc/123abc/xxx8/1234/:param" => { "param" => "ffa" }, "/get/abc/123abc/xxx8/1234/kka" :: "/get/abc/123abc/xxx8/1234/:param" => { "param" => "kka" }, "/get/abc/123abc/xxx8/1234/ffas321" :: "/get/abc/123abc/xxx8/1234/:param" => { "param" => "ffas321" }, "/get/abc/123abc/xxx8/1234/kkdd/12c" :: "/get/abc/123abc/xxx8/1234/kkdd/12c" => {}, "/get/abc/123abc/xxx8/1234/kkdd/1" :: "/get/abc/123abc/xxx8/1234/kkdd/:param" => { "param" => "1" }, "/get/abc/123abc/xxx8/1234/kkdd/12" :: "/get/abc/123abc/xxx8/1234/kkdd/:param" => { "param" => "12" }, "/get/abc/123abc/xxx8/1234/kkdd/12b" :: "/get/abc/123abc/xxx8/1234/kkdd/:param" => { "param" => "12b" }, "/get/abc/123abc/xxx8/1234/kkdd/34" :: "/get/abc/123abc/xxx8/1234/kkdd/:param" => { "param" => "34" }, "/get/abc/123abc/xxx8/1234/kkdd/12c2e3" :: "/get/abc/123abc/xxx8/1234/kkdd/:param" => { "param" => "12c2e3" }, "/get/abc/12/test" :: "/get/abc/:param/test" => { "param" => "12" }, "/get/abc/123abdd/test" :: "/get/abc/:param/test" => { "param" => "123abdd" }, "/get/abc/123abdddf/test" :: "/get/abc/:param/test" => { "param" => "123abdddf" }, "/get/abc/123ab/test" :: "/get/abc/:param/test" => { "param" => "123ab" }, "/get/abc/123abgg/test" :: "/get/abc/:param/test" => { "param" => "123abgg" }, "/get/abc/123abff/test" :: "/get/abc/:param/test" => { "param" => "123abff" }, "/get/abc/123abffff/test" :: "/get/abc/:param/test" => { "param" => "123abffff" }, "/get/abc/123abd/test" :: "/get/abc/123abd/:param" => { "param" => "test" }, "/get/abc/123abddd/test" :: "/get/abc/123abddd/:param" => { "param" => "test" }, "/get/abc/123/test22" :: "/get/abc/123/:param" => { "param" => "test22" }, "/get/abc/123abg/test" :: "/get/abc/123abg/:param" => { "param" => "test" }, "/get/abc/123abf/testss" :: "/get/abc/123abf/:param" => { "param" => "testss" }, "/get/abc/123abfff/te" :: "/get/abc/123abfff/:param" => { "param" => "te" }, }, normalized { routes = [ "/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", ], "/x/foo/bar" :: "/x/:foo/bar" => { "foo" => "foo" }, "/x/foo/baz" :: "/x/:bar/baz" => { "bar" => "foo" }, "/y/foo/baz" :: "/:foo/:bar/baz" => { "foo" => "y", "bar" => "foo" }, "/y/foo/bax" :: "/:foo/:baz/bax" => { "foo" => "y", "baz" => "foo" }, "/y/baz/baz" :: "/:foo/:bar/baz" => { "foo" => "y", "bar" => "baz" }, "/y/baz/bax/foo" :: "/:fod/baz/bax/foo" => { "fod" => "y" }, "/y/baz/b/foo" :: "/:fod/:baz/:bax/foo" => { "fod" => "y", "baz" => "baz", "bax" => "b" }, "/y/baz/bax" :: "/:foo/baz/bax" => { "foo" => "y" }, "/z/bar/bay" :: "/:bar/:bay/bay" => { "bar" => "z", "bay" => "bar" }, "/s" :: "/s" => { }, "/s/s" :: "/s/s" => { }, "/s/s/s" :: "/s/s/s" => { }, "/s/s/s/s" :: "/s/s/s/s" => { }, "/s/s/s/x" :: "/s/s/:s/x" => { "s" => "s" }, "/s/s/s/d" :: "/s/s/:y/d" => { "y" => "s" }, }, blog { routes = [ "/:page", "/posts/:year/:month/:post", "/posts/:year/:month/index", "/posts/:year/top", "/static/*path", "/favicon.ico", ], "/about" :: "/:page" => { "page" => "about" }, "/posts/2021/01/rust" :: "/posts/:year/:month/:post" => { "year" => "2021", "month" => "01", "post" => "rust" }, "/posts/2021/01/index" :: "/posts/:year/:month/index" => { "year" => "2021", "month" => "01" }, "/posts/2021/top" :: "/posts/:year/top" => { "year" => "2021" }, "/static/foo.png" :: "/static/*path" => { "path" => "foo.png" }, "/favicon.ico" :: "/favicon.ico" => {}, }, double_overlap { routes = [ "/:object/:id", "/secret/:id/path", "/secret/978", "/other/:object/:id/", "/other/an_object/:id", "/other/static/path", "/other/long/static/path/" ], "/secret/978/path" :: "/secret/:id/path" => { "id" => "978" }, "/some_object/978" :: "/:object/:id" => { "object" => "some_object", "id" => "978" }, "/secret/978" :: "/secret/978" => {}, "/super_secret/978/" :: "/:object/:id" => None, "/other/object/1/" :: "/other/:object/:id/" => { "object" => "object", "id" => "1" }, "/other/object/1/2" :: "/other/:object/:id" => None, "/other/an_object/1" :: "/other/an_object/:id" => { "id" => "1" }, "/other/static/path" :: "/other/static/path" => {}, "/other/long/static/path/" :: "/other/long/static/path/" => {}, }, catchall_off_by_one { routes = [ "/foo/*catchall", "/bar", "/bar/", "/bar/*catchall", ], "/foo" :: "" => None, "/foo/" :: "" => None, "/foo/x" :: "/foo/*catchall" => { "catchall" => "x" }, "/bar" :: "/bar" => {}, "/bar/" :: "/bar/" => {}, "/bar/x" :: "/bar/*catchall" => { "catchall" => "x" }, }, catchall_static_overlap { routes = [ "/foo", "/bar", "/*bar", "/baz", "/baz/", "/baz/x", "/baz/:xxx", "/", "/xxx/*x", "/xxx/", ], "/foo" :: "/foo" => {}, "/bar" :: "/bar" => {}, "/baz" :: "/baz" => {}, "/baz/" :: "/baz/" => {}, "/baz/x" :: "/baz/x" => {}, "/???" :: "/*bar" => { "bar" => "???" }, "/" :: "/" => {}, "" :: "" => None, "/xxx/y" :: "/xxx/*x" => { "x" => "y" }, "/xxx/" :: "/xxx/" => {}, "/xxx" :: "" => None } } // https://github.com/ibraheemdev/matchit/issues/12 #[test] fn issue_12() { 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")); } insert_tests! { wildcard_conflict { "/cmd/:tool/:sub" => Ok(()), "/cmd/vet" => Ok(()), "/foo/bar" => Ok(()), "/foo/:name" => Ok(()), "/foo/:names" => Err(InsertError::Conflict { with: "/foo/:name".into() }), "/cmd/*path" => Err(InsertError::Conflict { with: "/cmd/:tool/:sub".into() }), "/cmd/:xxx/names" => Ok(()), "/cmd/:tool/:xxx/foo" => Ok(()), "/src/*filepath" => Ok(()), "/src/:file" => Err(InsertError::Conflict { with: "/src/*filepath".into() }), "/src/static.json" => Ok(()), "/src/$filepathx" => Ok(()), "/src/" => Ok(()), "/src/foo/bar" => Ok(()), "/src1/" => Ok(()), "/src1/*filepath" => Ok(()), "/src2*filepath" => Err(InsertError::InvalidCatchAll), "/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(InsertError::Conflict { with: "/user_:name".into() }), "/id:id" => Ok(()), "/id/:id" => Ok(()), }, invalid_catchall { "/non-leading-*catchall" => Err(InsertError::InvalidCatchAll), "/foo/bar*catchall" => Err(InsertError::InvalidCatchAll), "/src/*filepath/x" => Err(InsertError::InvalidCatchAll), "/src2/" => Ok(()), "/src2/*filepath/x" => Err(InsertError::InvalidCatchAll), }, invalid_catchall2 { "*x" => Err(InsertError::InvalidCatchAll) }, catchall_root_conflict { "/" => Ok(()), "/*filepath" => Ok(()), }, child_conflict { "/cmd/vet" => Ok(()), "/cmd/:tool" => Ok(()), "/cmd/:tool/:sub" => Ok(()), "/cmd/:tool/misc" => Ok(()), "/cmd/:tool/:bad" => Err(InsertError::Conflict { with: "/cmd/:tool/:sub".into() }), "/src/AUTHORS" => Ok(()), "/src/*filepath" => Ok(()), "/user_x" => Ok(()), "/user_:name" => Ok(()), "/id/:id" => Ok(()), "/id:id" => Ok(()), "/:id" => Ok(()), "/*filepath" => Err(InsertError::Conflict { with: "/:id".into() }), }, duplicates { "/" => Ok(()), "/" => Err(InsertError::Conflict { with: "/".into() }), "/doc/" => Ok(()), "/doc/" => Err(InsertError::Conflict { with: "/doc/".into() }), "/src/*filepath" => Ok(()), "/src/*filepath" => Err(InsertError::Conflict { with: "/src/*filepath".into() }), "/search/:query" => Ok(()), "/search/:query" => Err(InsertError::Conflict { with: "/search/:query".into() }), "/user_:name" => Ok(()), "/user_:name" => Err(InsertError::Conflict { with: "/user_:name".into() }), }, unnamed_param { "/user:" => Err(InsertError::UnnamedParam), "/user:/" => Err(InsertError::UnnamedParam), "/cmd/:/" => Err(InsertError::UnnamedParam), "/src/*" => Err(InsertError::UnnamedParam), }, double_params { "/:foo:bar" => Err(InsertError::TooManyParams), "/:foo:bar/" => Err(InsertError::TooManyParams), "/:foo*bar/" => Err(InsertError::TooManyParams), }, normalized_conflict { "/x/:foo/bar" => Ok(()), "/x/:bar/bar" => Err(InsertError::Conflict { with: "/x/:foo/bar".into() }), "/:y/bar/baz" => Ok(()), "/:y/baz/baz" => Ok(()), "/:z/bar/bat" => Ok(()), "/:z/bar/baz" => Err(InsertError::Conflict { with: "/:y/bar/baz".into() }), }, more_conflicts { "/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(InsertError::Conflict { with: "/con:tact".into() }), "/whose/:users/:user" => Err(InsertError::Conflict { with: "/whose/:users/:name".into() }), }, catchall_static_overlap1 { "/bar" => Ok(()), "/bar/" => Ok(()), "/bar/*foo" => Ok(()), }, catchall_static_overlap2 { "/foo" => Ok(()), "/*bar" => Ok(()), "/bar" => Ok(()), "/baz" => Ok(()), "/baz/:split" => Ok(()), "/" => Ok(()), "/*bar" => Err(InsertError::Conflict { with: "/*bar".into() }), "/*zzz" => Err(InsertError::Conflict { with: "/*bar".into() }), "/:xxx" => Err(InsertError::Conflict { with: "/*bar".into() }), }, catchall_static_overlap3 { "/*bar" => Ok(()), "/bar" => Ok(()), "/bar/x" => Ok(()), "/bar_:x" => Ok(()), "/bar_:x" => Err(InsertError::Conflict { with: "/bar_:x".into() }), "/bar_:x/y" => Ok(()), "/bar/:x" => Ok(()), }, } tsr_tests! { tsr { routes = [ "/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", ], "/hi/" => ExtraTrailingSlash, "/b" => MissingTrailingSlash, "/search/rustacean/" => ExtraTrailingSlash, "/cmd/vet" => MissingTrailingSlash, "/src" => NotFound, "/src/" => NotFound, "/x/" => ExtraTrailingSlash, "/y" => MissingTrailingSlash, "/0/rust/" => ExtraTrailingSlash, "/1/rust" => MissingTrailingSlash, "/a" => MissingTrailingSlash, "/admin/" => ExtraTrailingSlash, "/doc/" => ExtraTrailingSlash, "/admin/static/" => ExtraTrailingSlash, "/admin/cfg/" => ExtraTrailingSlash, "/admin/cfg/users/" => ExtraTrailingSlash, "/api/hello/x/bar" => MissingTrailingSlash, "/api/baz/foo/" => ExtraTrailingSlash, "/api/baz/bax/" => ExtraTrailingSlash, "/api/bar/huh/" => ExtraTrailingSlash, "/api/baz/foo/bar/" => ExtraTrailingSlash, "/api/world/abc/" => ExtraTrailingSlash, "/foo/pp/" => ExtraTrailingSlash, "/" => NotFound, "/no" => NotFound, "/no/" => NotFound, "/no/a/b" => NotFound, "/no/a/b/" => NotFound, "/_" => NotFound, "/_/" => NotFound, "/api" => NotFound, "/api/" => NotFound, "/api/hello/x/foo" => NotFound, "/api/baz/foo/bad" => NotFound, "/foo/p/p" => NotFound, }, backtracking_tsr { routes = [ "/a/:b/:c", "/a/b/:c/d/", ], "/a/b/c/d" => MissingTrailingSlash, }, same_len { routes = ["/foo", "/bar/"], "/baz" => NotFound, }, root_tsr_wildcard { routes = ["/:foo"], "/" => NotFound, }, root_tsr_static { routes = ["/foo"], "/" => NotFound, }, root_tsr { routes = [ "/foo", "/bar", "/:baz" ], "/" => NotFound, }, double_overlap_tsr { routes = [ "/:object/:id", "/secret/:id/path", "/secret/978/", "/other/:object/:id/", "/other/an_object/:id", "/other/static/path", "/other/long/static/path/" ], "/secret/978/path/" => ExtraTrailingSlash, "/object/id/" => ExtraTrailingSlash, "/object/id/path" => NotFound, "/secret/978" => MissingTrailingSlash, "/other/object/1" => MissingTrailingSlash, "/other/object/1/2" => NotFound, "/other/an_object/1/" => ExtraTrailingSlash, "/other/static/path/" => ExtraTrailingSlash, "/other/long/static/path" => MissingTrailingSlash, "/other/object/static/path" => NotFound, }, } macro_rules! match_tests { ($($name:ident { routes = $routes:expr, $( $path:literal :: $route:literal => $( $(@$none:tt)? None )? $( $(@$some:tt)? { $( $key:literal => $val:literal ),* $(,)? } )? ),* $(,)? }),* $(,)?) => { $( #[test] fn $name() { let mut router = Router::new(); for route in $routes { router.insert(route, route.to_owned()) .unwrap_or_else(|e| panic!("error when inserting route '{}': {:?}", route, e)); } $(match router.at($path) { Err(_) => { $($( @$some )? panic!("Expected value for route '{}'", $path) )? } Ok(result) => { $($( @$some )? if result.value != $route { panic!( "Wrong value for route '{}'. Expected '{}', found '{}')", $path, result.value, $route ); } let expected_params = vec![$(($key, $val)),*]; let got_params = result.params.iter().collect::>(); assert_eq!( got_params, expected_params, "Wrong params for route '{}'", $path ); router.at_mut($path).unwrap().value.push_str("CHECKED"); assert!(router.at($path).unwrap().value.contains("CHECKED")); let val = router.at_mut($path).unwrap().value; *val = val.replace("CHECKED", ""); )? $($( @$none )? panic!( "Unexpected value for route '{}', got: {:?}", $path, result.params.iter().collect::>() ); )? } })* if let Err((got, expected)) = router.check_priorities() { panic!( "priority mismatch for node: got '{}', expected '{}'", got, expected ) } } )* }; } macro_rules! insert_tests { ($($name:ident { $($route:literal => $res:expr),* $(,)? }),* $(,)?) => { $( #[test] fn $name() { let mut router = Router::new(); $( let res = router.insert($route, $route.to_owned()); assert_eq!(res, $res, "unexpected result for path '{}'", $route); )* } )* }; } macro_rules! tsr_tests { ($($name:ident { routes = $routes:expr, $($path:literal => $tsr:ident),* $(,)? }),* $(,)?) => { $( #[test] fn $name() { let mut router = Router::new(); for route in $routes { router.insert(route, route.to_owned()) .unwrap_or_else(|e| panic!("error when inserting route '{}': {:?}", route, e)); } $( match router.at($path) { Err(MatchError::$tsr) => {}, Err(e) => panic!("wrong tsr value for '{}', expected {}, found {}", $path, MatchError::$tsr, e), res => panic!("unexpected result for '{}': {:?}", $path, res) } )* } )* }; } pub(self) use {insert_tests, match_tests, tsr_tests};