matchit-0.8.6/.cargo_vcs_info.json0000644000000001360000000000100125000ustar { "git": { "sha1": "d00110bf53e5d6a2a7a4bf1b45cdb4057cd1e407" }, "path_in_vcs": "" }matchit-0.8.6/.github/workflows/rust.yml000064400000000000000000000035661046102023000164170ustar 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.66 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.8.6/.gitignore000064400000000000000000000000231046102023000132530ustar 00000000000000/target Cargo.lock matchit-0.8.6/.rustfmt.toml000064400000000000000000000000441046102023000137450ustar 00000000000000edition="2018" reorder_imports=true matchit-0.8.6/Cargo.lock0000644000001007170000000000100104610ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "actix-router" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" dependencies = [ "bytestring", "cfg-if", "http 0.2.8", "regex", "regex-lite", "serde", "tracing", ] [[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 = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[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 = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytes" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "bytestring" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7f83e57d9154148e355404702e2694463241880b939570d7c97c014da7a69a1" dependencies = [ "bytes", ] [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "libc", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_lex" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "criterion" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", "is-terminal", "itertools", "num-traits", "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" 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 = "crunchy" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[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-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 = "half" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "cfg-if", "crunchy", ] [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "hermit-abi" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "http" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http 1.1.0", ] [[package]] name = "http-body-util" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", "http 1.1.0", "http-body", "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 = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "smallvec", "tokio", ] [[package]] name = "hyper-util" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" dependencies = [ "bytes", "futures-util", "http 1.1.0", "http-body", "hyper", "pin-project-lite", "tokio", ] [[package]] name = "is-terminal" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ "hermit-abi 0.4.0", "libc", "windows-sys 0.52.0", ] [[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 = "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 = "libc" version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "lock_api" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] [[package]] name = "matchit" version = "0.8.6" dependencies = [ "actix-router", "criterion", "gonzales", "http-body-util", "hyper", "hyper-util", "path-tree", "regex", "route-recognizer", "routefinder", "tokio", "tower", "wayfind", ] [[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 0.48.0", ] [[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 0.1.19", "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 0.48.5", ] [[package]] name = "path-tree" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c7fabb0b56aba5d2eb3fa9b1547c187f21f8c051295a7b97a50be6a9332f4cb" dependencies = [ "smallvec", ] [[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-lite" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" [[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 = "rustc-hash" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[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" dependencies = [ "serde_derive", ] [[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", "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 = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[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.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" dependencies = [ "libc", "windows-sys 0.48.0", ] [[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 = "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", "tokio-macros", "windows-sys 0.48.0", ] [[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 = "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 = "unicode-ident" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[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 = "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 = "wayfind" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2e79886aadef343ddda164a70522a271d7d42dbeb55dc45f91bb01fdb6e44" dependencies = [ "rustc-hash", "smallvec", ] [[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 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[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_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[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_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[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_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[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_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" matchit-0.8.6/Cargo.toml0000644000000042320000000000100104770ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "matchit" version = "0.8.6" authors = ["Ibraheem Ahmed "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false 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 [lib] name = "matchit" path = "src/lib.rs" [[example]] name = "hyper" path = "examples/hyper.rs" [[test]] name = "insert" path = "tests/insert.rs" [[test]] name = "match" path = "tests/match.rs" [[test]] name = "merge" path = "tests/merge.rs" [[test]] name = "remove" path = "tests/remove.rs" [[bench]] name = "bench" path = "benches/bench.rs" harness = false [dependencies] [dev-dependencies.actix-router] version = "0.5" [dev-dependencies.criterion] version = "0.5" [dev-dependencies.gonzales] version = "0.0.3-beta" [dev-dependencies.http-body-util] version = "0.1" [dev-dependencies.hyper] version = "1" features = [ "http1", "server", ] [dev-dependencies.hyper-util] version = "0.1" features = ["tokio"] [dev-dependencies.path-tree] version = "0.8" [dev-dependencies.regex] version = "1" [dev-dependencies.route-recognizer] version = "0.3" [dev-dependencies.routefinder] version = "0.5" [dev-dependencies.tokio] version = "1" features = ["full"] [dev-dependencies.tower] version = "0.4" features = [ "make", "util", ] [dev-dependencies.wayfind] version = "0.7" [features] __test_helpers = [] default = [] matchit-0.8.6/Cargo.toml.orig0000644000000017350000000000100114430ustar [package] name = "matchit" version = "0.8.6" 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.5" actix-router = "0.5" regex = "1" route-recognizer = "0.3" gonzales = "0.0.3-beta" path-tree = "0.8" routefinder = "0.5" wayfind = "0.7" # Examples tower = { version = "0.4", features = ["make", "util"] } tokio = { version = "1", features = ["full"] } http-body-util = "0.1" hyper = { version = "1", features = ["http1", "server"] } hyper-util = { version = "0.1", features = ["tokio"] } [features] default = [] __test_helpers = [] [[bench]] name = "bench" harness = false [profile.release] lto = true opt-level = 3 codegen-units = 1 matchit-0.8.6/Cargo.toml.orig000064400000000000000000000017351046102023000141650ustar 00000000000000[package] name = "matchit" version = "0.8.6" 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.5" actix-router = "0.5" regex = "1" route-recognizer = "0.3" gonzales = "0.0.3-beta" path-tree = "0.8" routefinder = "0.5" wayfind = "0.7" # Examples tower = { version = "0.4", features = ["make", "util"] } tokio = { version = "1", features = ["full"] } http-body-util = "0.1" hyper = { version = "1", features = ["http1", "server"] } hyper-util = { version = "0.1", features = ["tokio"] } [features] default = [] __test_helpers = [] [[bench]] name = "bench" harness = false [profile.release] lto = true opt-level = 3 codegen-units = 1 matchit-0.8.6/LICENSE000064400000000000000000000020571046102023000123010ustar 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.8.6/LICENSE.httprouter000064400000000000000000000027621046102023000145230ustar 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.8.6/README.md000064400000000000000000000110451046102023000125500ustar 00000000000000# `matchit` [crates.io](https://crates.io/crates/matchit) [github](https://github.com/ibraheemdev/matchit) [docs.rs](https://docs.rs/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 The router supports dynamic route segments. These can either be named or catch-all parameters. Named parameters like `/{id}` match anything until the next static segment 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()); ``` Prefixes and suffixes within a segment are also supported. However, there may only be a single named parameter per route segment. ```rust,ignore let mut m = Router::new(); m.insert("/images/img{id}.png", true)?; assert_eq!(m.at("/images/img1.png")?.params.get("id"), Some("1")); assert!(m.at("/images/img1.jpg").is_err()); ``` Catch-all parameters start with `*` and match anything until the end of the path. They must always be at the **end** of the route. ```rust,ignore let mut m = Router::new(); m.insert("/{*p}", true)?; assert_eq!(m.at("/foo.js")?.params.get("p"), Some("foo.js")); assert_eq!(m.at("/c/bar.css")?.params.get("p"), Some("c/bar.css")); // Note that this would lead to an empty parameter. assert!(m.at("/").is_err()); ``` The literal characters `{` and `}` may be included in a static route by escaping them with the same character. For example, the `{` character is escaped with `{{` and the `}` character is escaped with `}}`. ```rust,ignore let mut m = Router::new(); m.insert("/{{hello}}", true)?; m.insert("/{hello}", true)?; // Match the static route. assert!(m.at("/{hello}")?.value); // Match the dynamic route. assert_eq!(m.at("/hello")?.params.get("hello"), Some("hello")); ``` ## Routing Priority Static and dynamic route segments are allowed to overlap. If they do, static segments will be given higher priority: ```rust,ignore let mut m = Router::new(); m.insert("/", "Welcome!").unwrap(); // Priority: 1 m.insert("/about", "About Me").unwrap(); // Priority: 1 m.insert("/{*filepath}", "...").unwrap(); // Priority: 2 ``` ## How does it work? The router takes advantage of the fact that URL routes generally follow a hierarchical structure. Routes are stored them in a radix trie that makes heavy use of common prefixes. ```text Priority Path Value 9 \ 1 3 ├s None 2 |├earch\ 2 1 |└upport\ 3 2 ├blog\ 4 1 | └{post} None 1 | └\ 5 2 ├about-us\ 6 1 | └team\ 7 1 └contact\ 8 ``` This allows us to reduce the route search to a small number of branches. Child nodes on the same level of the tree are also prioritized by the number of children with registered values, increasing the chance of choosing the correct branch of the first try. ## Benchmarks As it turns out, this method of routing is extremely fast. Below are the benchmark results matching against 130 registered routes. You can view the benchmark code [here](https://github.com/ibraheemdev/matchit/blob/master/benches/bench.rs). ```text Compare Routers/matchit time: [2.4451 µs 2.4456 µs 2.4462 µs] Compare Routers/gonzales time: [4.2618 µs 4.2632 µs 4.2646 µs] Compare Routers/path-tree time: [4.8666 µs 4.8696 µs 4.8728 µs] Compare Routers/wayfind time: [4.9440 µs 4.9539 µs 4.9668 µs] Compare Routers/route-recognizer time: [49.203 µs 49.214 µs 49.226 µs] Compare Routers/routefinder time: [70.598 µs 70.636 µs 70.670 µs] Compare Routers/actix time: [453.91 µs 454.01 µs 454.11 µs] Compare Routers/regex time: [421.76 µs 421.82 µs 421.89 µs] ``` ## Credits A lot of the code in this package was inspired by Julien Schmidt's [`httprouter`](https://github.com/julienschmidt/httprouter). matchit-0.8.6/examples/hyper.rs000064400000000000000000000064751046102023000146170ustar 00000000000000use std::collections::HashMap; use std::sync::{Arc, Mutex}; use http_body_util::Full; use hyper::body::{Bytes, Incoming}; use hyper::server::conn::http1::Builder as ConnectionBuilder; use hyper::{Method, Request, Response, StatusCode}; use hyper_util::rt::TokioIo; use tokio::net::TcpListener; use tower::service_fn; use tower::util::BoxCloneService; use tower::Service as _; type Body = Full; // 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(StatusCode::NOT_FOUND) .body(Body::default()) .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 Some(router) = router.get(req.method()) else { // if there are no routes for this method, respond with 405 Method Not Allowed return Ok(Response::builder() .status(StatusCode::METHOD_NOT_ALLOWED) .body(Body::default()) .unwrap()); }; // find the service for this request path let Ok(found) = router.at(req.uri().path()) else { // if we there is no matching service, call the 404 handler return not_found(req).await; }; // 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 } #[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(); let listener = TcpListener::bind(("127.0.0.1", 3000)).await.unwrap(); // boilerplate for the hyper service let router = Arc::new(router); loop { let router = router.clone(); let (tcp, _) = listener.accept().await.unwrap(); tokio::task::spawn(async move { if let Err(err) = ConnectionBuilder::new() .serve_connection( TokioIo::new(tcp), hyper::service::service_fn(|request| async { route(router.clone(), request).await }), ) .await { println!("Error serving connection: {:?}", err); } }); } } matchit-0.8.6/src/error.rs000064400000000000000000000112171046102023000135600ustar 00000000000000use crate::escape::{UnescapedRef, UnescapedRoute}; use crate::tree::{denormalize_params, Node}; use std::fmt; use std::ops::Deref; /// 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. /// /// For example, `/foo-{bar}` and `/{bar}-foo` are valid routes, but `/{foo}-{bar}` /// is not. InvalidParamSegment, /// Parameters must be registered with a valid name and matching braces. /// /// Note you can use `{{` or `}}` to escape literal brackets. InvalidParam, /// Catch-all parameters are only allowed at the end of a path. InvalidCatchAll, } impl fmt::Display for InsertError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Conflict { with } => { write!( f, "Insertion failed due to conflict with previously registered route: {with}" ) } Self::InvalidParamSegment => { write!(f, "Only one parameter is allowed per path segment") } Self::InvalidParam => write!(f, "Parameters must be registered with a valid name"), Self::InvalidCatchAll => write!( f, "Catch-all parameters are only allowed at the end of a route" ), } } } impl std::error::Error for InsertError {} impl InsertError { /// Returns an error for a route conflict with the given node. /// /// This method attempts to find the full conflicting route. pub(crate) fn conflict( route: &UnescapedRoute, prefix: UnescapedRef<'_>, current: &Node, ) -> Self { let mut route = route.clone(); // The route is conflicting with the current node. if prefix.unescaped() == current.prefix.unescaped() { denormalize_params(&mut route, ¤t.remapping); return InsertError::Conflict { with: String::from_utf8(route.into_unescaped()).unwrap(), }; } // Remove the non-matching suffix from the route. route.truncate(route.len() - prefix.len()); // Add the conflicting prefix. if !route.ends_with(¤t.prefix) { route.append(¤t.prefix); } // Add the prefixes of any conflicting children. let mut child = current.children.first(); while let Some(node) = child { route.append(&node.prefix); child = node.children.first(); } // Denormalize any route parameters. let mut last = current; while let Some(node) = last.children.first() { last = node; } denormalize_params(&mut route, &last.remapping); // Return the conflicting route. InsertError::Conflict { with: String::from_utf8(route.into_unescaped()).unwrap(), } } } /// A failed merge attempt. /// /// See [`Router::merge`](crate::Router::merge) for details. #[derive(Clone, Debug, Eq, PartialEq)] pub struct MergeError(pub(crate) Vec); impl MergeError { /// Returns a list of [`InsertError`] for every insertion that failed /// during the merge. pub fn into_errors(self) -> Vec { self.0 } } impl fmt::Display for MergeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for error in self.0.iter() { writeln!(f, "{}", error)?; } Ok(()) } } impl std::error::Error for MergeError {} impl Deref for MergeError { type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 } } /// 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.")?; /// /// // no routes match /// if let Err(err) = router.at("/blo") { /// assert_eq!(err, MatchError::NotFound); /// } /// # Ok(()) /// # } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum MatchError { /// No matching route was found. NotFound, } impl fmt::Display for MatchError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Matching route not found") } } impl std::error::Error for MatchError {} matchit-0.8.6/src/escape.rs000064400000000000000000000115031046102023000136650ustar 00000000000000use std::{fmt, ops::Range}; /// An unescaped route that keeps track of the position of /// escaped characters, i.e. '{{' or '}}'. /// /// Note that this type dereferences to `&[u8]`. #[derive(Clone, Default)] pub struct UnescapedRoute { // The raw unescaped route. inner: Vec, escaped: Vec, } impl UnescapedRoute { /// Unescapes escaped brackets ('{{' or '}}') in a route. pub fn new(mut inner: Vec) -> UnescapedRoute { let mut escaped = Vec::new(); let mut i = 0; while let Some(&c) = inner.get(i) { if (c == b'{' && inner.get(i + 1) == Some(&b'{')) || (c == b'}' && inner.get(i + 1) == Some(&b'}')) { inner.remove(i); escaped.push(i); } i += 1; } UnescapedRoute { inner, escaped } } /// Returns true if the character at the given index was escaped. pub fn is_escaped(&self, i: usize) -> bool { self.escaped.contains(&i) } /// Replaces the characters in the given range. pub fn splice( &mut self, range: Range, replace: Vec, ) -> impl Iterator + '_ { // Ignore any escaped characters in the range being replaced. self.escaped.retain(|x| !range.contains(x)); // Update the escaped indices. let offset = (replace.len() as isize) - (range.len() as isize); for i in &mut self.escaped { if *i > range.end { *i = i.checked_add_signed(offset).unwrap(); } } self.inner.splice(range, replace) } /// Appends another route to the end of this one. pub fn append(&mut self, other: &UnescapedRoute) { for i in &other.escaped { self.escaped.push(self.inner.len() + i); } self.inner.extend_from_slice(&other.inner); } /// Truncates the route to the given length. pub fn truncate(&mut self, to: usize) { self.escaped.retain(|&x| x < to); self.inner.truncate(to); } /// Returns a reference to this route. pub fn as_ref(&self) -> UnescapedRef<'_> { UnescapedRef { inner: &self.inner, escaped: &self.escaped, offset: 0, } } /// Returns a reference to the unescaped slice. pub fn unescaped(&self) -> &[u8] { &self.inner } /// Returns the unescaped route. pub fn into_unescaped(self) -> Vec { self.inner } } impl std::ops::Deref for UnescapedRoute { type Target = [u8]; fn deref(&self) -> &Self::Target { &self.inner } } impl fmt::Debug for UnescapedRoute { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(std::str::from_utf8(&self.inner).unwrap(), f) } } /// A reference to an `UnescapedRoute`. #[derive(Copy, Clone)] pub struct UnescapedRef<'a> { inner: &'a [u8], escaped: &'a [usize], // An offset applied to each escaped index. offset: isize, } impl<'a> UnescapedRef<'a> { /// Converts this reference into an owned route. pub fn to_owned(self) -> UnescapedRoute { let mut escaped = Vec::new(); for &i in self.escaped { let i = i.checked_add_signed(self.offset); match i { Some(i) if i < self.inner.len() => escaped.push(i), _ => {} } } UnescapedRoute { escaped, inner: self.inner.to_owned(), } } /// Returns `true` if the character at the given index was escaped. pub fn is_escaped(&self, i: usize) -> bool { if let Some(i) = i.checked_add_signed(-self.offset) { return self.escaped.contains(&i); } false } /// Slices the route with `start..`. pub fn slice_off(&self, start: usize) -> UnescapedRef<'a> { UnescapedRef { inner: &self.inner[start..], escaped: self.escaped, offset: self.offset - (start as isize), } } /// Slices the route with `..end`. pub fn slice_until(&self, end: usize) -> UnescapedRef<'a> { UnescapedRef { inner: &self.inner[..end], escaped: self.escaped, offset: self.offset, } } /// Returns a reference to the unescaped slice. pub fn unescaped(&self) -> &[u8] { self.inner } } impl<'a> std::ops::Deref for UnescapedRef<'a> { type Target = &'a [u8]; fn deref(&self) -> &Self::Target { &self.inner } } impl fmt::Debug for UnescapedRef<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("UnescapedRef") .field("inner", &std::str::from_utf8(self.inner)) .field("escaped", &self.escaped) .field("offset", &self.offset) .finish() } } matchit-0.8.6/src/lib.rs000064400000000000000000000101471046102023000131760ustar 00000000000000/*! 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 The router supports dynamic route segments. These can either be named or catch-all parameters. Named parameters like `/{id}` match anything until the next static segment 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(()) # } ``` Prefixes and suffixes within a segment are also supported. However, there may only be a single named parameter per route segment. ```rust # use matchit::Router; # fn main() -> Result<(), Box> { let mut m = Router::new(); m.insert("/images/img{id}.png", true)?; assert_eq!(m.at("/images/img1.png")?.params.get("id"), Some("1")); assert!(m.at("/images/img1.jpg").is_err()); # Ok(()) # } ``` Catch-all parameters start with `*` and match anything until the end of the path. They must always be at the **end** of the route. ```rust # use matchit::Router; # fn main() -> Result<(), Box> { 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 lead to an empty parameter. assert!(m.at("/").is_err()); # Ok(()) # } ``` The literal characters `{` and `}` may be included in a static route by escaping them with the same character. For example, the `{` character is escaped with `{{` and the `}` character is escaped with `}}`. ```rust # use matchit::Router; # fn main() -> Result<(), Box> { let mut m = Router::new(); m.insert("/{{hello}}", true)?; m.insert("/{hello}", true)?; // Match the static route. assert!(m.at("/{hello}")?.value); // Match the dynamic route. assert_eq!(m.at("/hello")?.params.get("hello"), Some("hello")); # Ok(()) # } ``` # Routing Priority Static and dynamic route segments are allowed to overlap. If they do, static segments will be given higher priority: ```rust # use matchit::Router; # fn main() -> Result<(), Box> { let mut m = Router::new(); m.insert("/", "Welcome!").unwrap(); // Priority: 1 m.insert("/about", "About Me").unwrap(); // Priority: 1 m.insert("/{*filepath}", "...").unwrap(); // Priority: 2 # Ok(()) # } ``` # How does it work? The router takes advantage of the fact that URL routes generally follow a hierarchical structure. Routes are stored them in a radix trie that makes heavy use of common prefixes. ```text Priority Path Value 9 \ 1 3 ├s None 2 |├earch\ 2 1 |└upport\ 3 2 ├blog\ 4 1 | └{post} None 1 | └\ 5 2 ├about-us\ 6 1 | └team\ 7 1 └contact\ 8 ``` This allows us to reduce the route search to a small number of branches. Child nodes on the same level of the tree are also prioritized by the number of children with registered values, increasing the chance of choosing the correct branch of the first try. As it turns out, this method of routing is extremely fast. See the [benchmark results](https://github.com/ibraheemdev/matchit?tab=readme-ov-file#benchmarks) for details. */ #![deny(rust_2018_idioms, clippy::all)] mod error; mod escape; mod params; mod router; mod tree; pub use error::{InsertError, MatchError, MergeError}; pub use params::{Params, ParamsIter}; pub use router::{Match, Router}; #[cfg(doctest)] mod readme { #[allow(dead_code)] #[doc = include_str!("../README.md")] struct Readme; } matchit-0.8.6/src/params.rs000064400000000000000000000171701046102023000137160ustar 00000000000000use std::{fmt, iter, mem, slice}; /// A single URL parameter, consisting of a key and a value. #[derive(PartialEq, Eq, Ord, PartialOrd, Default, Copy, Clone)] pub(crate) struct Param<'k, 'v> { // Keys and values are stored as byte slices internally by the router // to avoid utf8 checks when slicing. This allows us to perform utf8 // validation lazily without resorting to unsafe code. pub(crate) key: &'k [u8], pub(crate) value: &'v [u8], } impl<'k, 'v> Param<'k, 'v> { const EMPTY: Param<'static, 'static> = Param { key: b"", value: b"", }; // Returns the parameter key as a string. fn key_str(&self) -> &'k str { std::str::from_utf8(self.key).unwrap() } // Returns the parameter value as a string. fn value_str(&self) -> &'v str { std::str::from_utf8(self.value).unwrap() } } /// A list of parameters returned by a route match. /// /// ```rust /// # fn main() -> Result<(), Box> { /// # let mut router = matchit::Router::new(); /// # router.insert("/users/{id}", true).unwrap(); /// let matched = router.at("/users/1")?; /// /// // Iterate through the keys and values. /// for (key, value) in matched.params.iter() { /// println!("key: {}, value: {}", key, value); /// } /// /// // Get a specific value by name. /// let id = matched.params.get("id"); /// assert_eq!(id, Some("1")); /// # Ok(()) /// # } /// ``` #[derive(PartialEq, Eq, Ord, PartialOrd, Clone)] pub struct Params<'k, 'v> { kind: ParamsKind<'k, 'v>, } impl Default for Params<'_, '_> { fn default() -> Self { Self::new() } } // Most routes have a small number of dynamic parameters, so we can avoid // heap allocations in the common case. const SMALL: usize = 3; // A list of parameters, optimized to avoid allocations when possible. #[derive(PartialEq, Eq, Ord, PartialOrd, Clone)] enum ParamsKind<'k, 'v> { Small([Param<'k, 'v>; SMALL], usize), Large(Vec>), } impl<'k, 'v> Params<'k, 'v> { /// Create an empty list of parameters. #[inline] pub fn new() -> Self { Self { kind: ParamsKind::Small([Param::EMPTY; SMALL], 0), } } /// Returns the number of parameters. pub fn len(&self) -> usize { match self.kind { ParamsKind::Small(_, len) => len, ParamsKind::Large(ref vec) => vec.len(), } } // Truncates the parameter list to the given length. pub(crate) fn truncate(&mut self, n: usize) { match &mut self.kind { ParamsKind::Small(_, len) => *len = n, ParamsKind::Large(vec) => vec.truncate(n), } } /// Returns the value of the first parameter registered under the given key. pub fn get(&self, key: impl AsRef) -> Option<&'v str> { let key = key.as_ref().as_bytes(); match &self.kind { ParamsKind::Small(arr, len) => arr .iter() .take(*len) .find(|param| param.key == key) .map(Param::value_str), ParamsKind::Large(vec) => vec .iter() .find(|param| param.key == key) .map(Param::value_str), } } /// Returns an iterator over the parameters in the list. pub fn iter(&self) -> ParamsIter<'_, 'k, 'v> { ParamsIter::new(self) } /// Returns `true` if there are no parameters in the list. pub fn is_empty(&self) -> bool { match self.kind { ParamsKind::Small(_, len) => len == 0, ParamsKind::Large(ref vec) => vec.is_empty(), } } /// Appends a key-value parameter to the list. #[inline] pub(crate) fn push(&mut self, key: &'k [u8], value: &'v [u8]) { #[cold] #[inline(never)] 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 } #[cold] #[inline(never)] fn push_slow<'k, 'v>(vec: &mut Vec>, param: Param<'k, 'v>) { vec.push(param); } let param = Param { key, value }; match &mut self.kind { ParamsKind::Small(arr, len) => { if *len >= SMALL { self.kind = ParamsKind::Large(drain_to_vec(*len, param, arr)); return; } arr[*len] = param; *len += 1; } ParamsKind::Large(vec) => push_slow(vec, param), } } // Applies a transformation function to each key. #[inline] pub(crate) fn for_each_key_mut(&mut self, f: impl Fn((usize, &mut Param<'k, 'v>))) { match &mut self.kind { ParamsKind::Small(arr, len) => arr.iter_mut().take(*len).enumerate().for_each(f), ParamsKind::Large(vec) => vec.iter_mut().enumerate().for_each(f), } } } impl fmt::Debug for Params<'_, '_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_list().entries(self.iter()).finish() } } /// An iterator over the keys and values of a route's [parameters](crate::Params). pub struct ParamsIter<'ps, 'k, 'v> { kind: ParamsIterKind<'ps, 'k, 'v>, } impl<'ps, 'k, 'v> ParamsIter<'ps, 'k, 'v> { fn new(params: &'ps Params<'k, 'v>) -> Self { let kind = match ¶ms.kind { ParamsKind::Small(arr, len) => ParamsIterKind::Small(arr.iter().take(*len)), ParamsKind::Large(vec) => ParamsIterKind::Large(vec.iter()), }; Self { kind } } } enum ParamsIterKind<'ps, 'k, 'v> { Small(iter::Take>>), Large(slice::Iter<'ps, Param<'k, 'v>>), } impl<'k, 'v> Iterator for ParamsIter<'_, 'k, 'v> { type Item = (&'k str, &'v str); fn next(&mut self) -> Option { match self.kind { ParamsIterKind::Small(ref mut iter) => { iter.next().map(|p| (p.key_str(), p.value_str())) } ParamsIterKind::Large(ref mut iter) => { iter.next().map(|p| (p.key_str(), p.value_str())) } } } } impl ExactSizeIterator for ParamsIter<'_, '_, '_> { fn len(&self) -> usize { match self.kind { ParamsIterKind::Small(ref iter) => iter.len(), ParamsIterKind::Large(ref iter) => iter.len(), } } } #[cfg(test)] mod tests { use super::*; #[test] fn heap_alloc() { let vec = vec![ ("hello", "hello"), ("world", "world"), ("foo", "foo"), ("bar", "bar"), ("baz", "baz"), ]; let mut params = Params::new(); for (key, value) in vec.clone() { params.push(key.as_bytes(), value.as_bytes()); assert_eq!(params.get(key), Some(value)); } match params.kind { ParamsKind::Large(..) => {} _ => panic!(), } assert!(params.iter().eq(vec.clone())); } #[test] fn stack_alloc() { let vec = vec![("hello", "hello"), ("world", "world"), ("baz", "baz")]; let mut params = Params::new(); for (key, value) in vec.clone() { params.push(key.as_bytes(), value.as_bytes()); assert_eq!(params.get(key), Some(value)); } match params.kind { ParamsKind::Small(..) => {} _ => panic!(), } assert!(params.iter().eq(vec.clone())); } #[test] fn ignore_array_default() { let params = Params::new(); assert!(params.get("").is_none()); } } matchit-0.8.6/src/router.rs000064400000000000000000000126131046102023000137500ustar 00000000000000use crate::error::MergeError; use crate::tree::Node; use crate::{InsertError, MatchError, Params}; /// A zero-copy URL router. /// /// See [the crate documentation](crate) for details. #[derive(Clone, Debug)] pub struct Router { 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 into the router. /// /// # 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.into(), 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(()) /// # } /// ``` #[inline] pub fn at<'path>(&self, path: &'path 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(()) /// # } /// ``` #[inline] pub fn at_mut<'path>( &mut self, path: &'path 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), } } /// Remove a given route from the router. /// /// Returns the value stored under the route if it was found. /// If the route was not found or invalid, `None` is returned. /// /// # Examples /// /// ```rust /// # use matchit::Router; /// let mut router = Router::new(); /// /// router.insert("/home", "Welcome!"); /// assert_eq!(router.remove("/home"), Some("Welcome!")); /// assert_eq!(router.remove("/home"), None); /// /// router.insert("/home/{id}/", "Hello!"); /// assert_eq!(router.remove("/home/{id}/"), Some("Hello!")); /// assert_eq!(router.remove("/home/{id}/"), None); /// /// router.insert("/home/{id}/", "Hello!"); /// // The route does not match. /// assert_eq!(router.remove("/home/{user}"), None); /// assert_eq!(router.remove("/home/{id}/"), Some("Hello!")); /// /// router.insert("/home/{id}/", "Hello!"); /// // Invalid route. /// assert_eq!(router.remove("/home/{id"), None); /// assert_eq!(router.remove("/home/{id}/"), Some("Hello!")); /// ``` pub fn remove(&mut self, path: impl Into) -> Option { self.root.remove(path.into()) } #[cfg(feature = "__test_helpers")] pub fn check_priorities(&self) -> Result { self.root.check_priorities() } /// Merge a given router into current one. /// /// Returns a list of [`InsertError`] for every failed insertion. /// Note that this can result in a partially successful merge if /// a subset of routes conflict. /// /// # Examples /// /// ```rust /// # use matchit::Router; /// # fn main() -> Result<(), Box> { /// let mut root = Router::new(); /// root.insert("/home", "Welcome!")?; /// /// let mut child = Router::new(); /// child.insert("/users/{id}", "A User")?; /// /// root.merge(child)?; /// assert!(root.at("/users/1").is_ok()); /// # Ok(()) /// # } /// ``` pub fn merge(&mut self, other: Self) -> Result<(), MergeError> { let mut errors = Vec::new(); other.root.for_each(|path, value| { if let Err(err) = self.insert(path, value) { errors.push(err); } }); if errors.is_empty() { Ok(()) } else { Err(MergeError(errors)) } } } /// 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.8.6/src/tree.rs000064400000000000000000001140141046102023000133650ustar 00000000000000use crate::escape::{UnescapedRef, UnescapedRoute}; use crate::{InsertError, MatchError, Params}; use std::cell::UnsafeCell; use std::cmp::min; use std::collections::VecDeque; use std::ops::Range; use std::{fmt, mem}; /// A radix tree used for URL path matching. /// /// See [the crate documentation](crate) for details. pub struct Node { // This node's prefix. pub(crate) prefix: UnescapedRoute, // The priority of this node. // // Nodes with more children are higher priority and searched first. pub(crate) priority: u32, // Whether this node contains a wildcard child. pub(crate) wild_child: bool, // The first character of any static children, for fast linear search. pub(crate) indices: Vec, // The type of this node. pub(crate) node_type: NodeType, // The children of this node. pub(crate) children: Vec>, // The value stored at this node. // // See `Node::at` for why an `UnsafeCell` is necessary. value: Option>, // A parameter name remapping, stored at nodes that hold values. pub(crate) remapping: ParamRemapping, } /// The types of nodes a tree can hold. #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] pub(crate) enum NodeType { /// The root path. Root, /// A route parameter, e.g. '/{id}'. /// /// If `suffix` is `false`, the only child of this node is /// a static '/', allowing for a fast path when searching. /// Otherwise, the route may have static suffixes, e.g. '/{id}.png'. /// /// The leaves of a parameter node are the static suffixes /// sorted by length. This allows for a reverse linear search /// to determine the correct leaf. It would also be possible to /// use a reverse prefix-tree here, but is likely not worth the /// complexity. Param { suffix: bool }, /// A catch-all parameter, e.g. '/{*file}'. CatchAll, /// A static prefix, e.g. '/foo'. Static, } /// Safety: We expose `value` per Rust's usual borrowing rules, so we can just /// delegate these traits. unsafe impl Send for Node {} unsafe impl Sync for Node {} impl Node { // Insert a route into the tree. pub fn insert(&mut self, route: String, val: T) -> Result<(), InsertError> { let route = UnescapedRoute::new(route.into_bytes()); let (route, remapping) = normalize_params(route)?; let mut remaining = route.as_ref(); self.priority += 1; // If the tree is empty, insert the root node. if self.prefix.is_empty() && self.children.is_empty() { let last = self.insert_route(remaining, val)?; last.remapping = remapping; self.node_type = NodeType::Root; return Ok(()); } let mut node = self; 'walk: loop { // Find the common prefix between the route and the current node. let len = min(remaining.len(), node.prefix.len()); let common_prefix = (0..len) .find(|&i| { remaining[i] != node.prefix[i] // Make sure not confuse the start of a wildcard with an escaped `{`. || remaining.is_escaped(i) != node.prefix.is_escaped(i) }) .unwrap_or(len); // If this node has a longer prefix than we need, we have to fork and extract the // common prefix into a shared parent. if node.prefix.len() > common_prefix { // Move the non-matching suffix into a child node. let suffix = node.prefix.as_ref().slice_off(common_prefix).to_owned(); let child = Node { prefix: suffix, value: node.value.take(), indices: node.indices.clone(), wild_child: node.wild_child, children: mem::take(&mut node.children), remapping: mem::take(&mut node.remapping), priority: node.priority - 1, node_type: NodeType::Static, }; // The current node now only holds the common prefix. node.children = vec![child]; node.indices = vec![node.prefix[common_prefix]]; node.prefix = node.prefix.as_ref().slice_until(common_prefix).to_owned(); node.wild_child = false; continue; } if remaining.len() == common_prefix { // This node must not already contain a value. if node.value.is_some() { return Err(InsertError::conflict(&route, remaining, node)); } // Insert the value. node.value = Some(UnsafeCell::new(val)); node.remapping = remapping; return Ok(()); } // Otherwise, the route has a remaining non-matching suffix. // // We have to search deeper. remaining = remaining.slice_off(common_prefix); let next = remaining[0]; // For parameters with a suffix, we have to find the matching suffix or // create a new child node. if matches!(node.node_type, NodeType::Param { .. }) { let terminator = remaining .iter() .position(|&b| b == b'/') .map(|b| b + 1) .unwrap_or(remaining.len()); let suffix = remaining.slice_until(terminator); for (i, child) in node.children.iter().enumerate() { // Find a matching suffix. if *child.prefix == **suffix { node = &mut node.children[i]; node.priority += 1; continue 'walk; } } // Multiple parameters within the same segment, e.g. `/{foo}{bar}`. if matches!(find_wildcard(suffix), Ok(Some(_))) { return Err(InsertError::InvalidParamSegment); } // If there is no matching suffix, create a new suffix node. let child = node.add_suffix_child(Node { prefix: suffix.to_owned(), node_type: NodeType::Static, priority: 1, ..Node::default() }); node.node_type = NodeType::Param { suffix: true }; node = &mut node.children[child]; // If this is the final route segment, insert the value. if terminator == remaining.len() { node.value = Some(UnsafeCell::new(val)); node.remapping = remapping; return Ok(()); } // Otherwise, the previous node will hold only the suffix and we // need to create a new child for the remaining route. remaining = remaining.slice_off(terminator); // Create a static node unless we are inserting a parameter. if remaining[0] != b'{' || remaining.is_escaped(0) { let child = node.add_child(Node { node_type: NodeType::Static, priority: 1, ..Node::default() }); node.indices.push(remaining[0]); node = &mut node.children[child]; } // Insert the remaining route. let last = node.insert_route(remaining, val)?; last.remapping = remapping; return Ok(()); } // Find a child node that matches the next character in the route. for mut i in 0..node.indices.len() { if next == node.indices[i] { // Make sure not confuse the start of a wildcard with an escaped `{` or `}`. if matches!(next, b'{' | b'}') && !remaining.is_escaped(0) { continue; } // Continue searching in the child. i = node.update_child_priority(i); node = &mut node.children[i]; continue 'walk; } } // We couldn't find a matching child. // // If we're not inserting a wildcard we have to create a static child. if (next != b'{' || remaining.is_escaped(0)) && node.node_type != NodeType::CatchAll { node.indices.push(next); let child = node.add_child(Node::default()); let child = node.update_child_priority(child); // Insert into the newly created node. let last = node.children[child].insert_route(remaining, val)?; last.remapping = remapping; return Ok(()); } // We're trying to insert a wildcard. // // If this node already has a wildcard child, we have to make sure it matches. if node.wild_child { // Wildcards are always the last child. node = node.children.last_mut().unwrap(); node.priority += 1; // Make sure the route parameter matches. if let Some(wildcard) = remaining.get(..node.prefix.len()) { if *wildcard != *node.prefix { return Err(InsertError::conflict(&route, remaining, node)); } } // Catch-all routes cannot have children. if node.node_type == NodeType::CatchAll { return Err(InsertError::conflict(&route, remaining, node)); } // Continue with the wildcard node. continue 'walk; } // Otherwise, create a new node for the wildcard and insert the route. let last = node.insert_route(remaining, val)?; last.remapping = remapping; return Ok(()); } } // Insert a route at this node. // // If the route starts with a wildcard, a child node will be created for the parameter // and `wild_child` will be set on the parent. fn insert_route( &mut self, mut prefix: UnescapedRef<'_>, val: T, ) -> Result<&mut Node, InsertError> { let mut node = self; loop { // Search for a wildcard segment. let Some(wildcard) = find_wildcard(prefix)? else { // There is no wildcard, simply insert into the current node. node.value = Some(UnsafeCell::new(val)); node.prefix = prefix.to_owned(); return Ok(node); }; // Insering a catch-all route. if prefix[wildcard.clone()][1] == b'*' { // Ensure there is no suffix after the parameter, e.g. `/foo/{*x}/bar`. if wildcard.end != prefix.len() { return Err(InsertError::InvalidCatchAll); } // Add the prefix before the wildcard into the current node. if wildcard.start > 0 { node.prefix = prefix.slice_until(wildcard.start).to_owned(); prefix = prefix.slice_off(wildcard.start); } // Add the catch-all as a child node. let child = node.add_child(Node { prefix: prefix.to_owned(), node_type: NodeType::CatchAll, value: Some(UnsafeCell::new(val)), priority: 1, ..Node::default() }); node.wild_child = true; return Ok(&mut node.children[child]); } // Otherwise, we're inserting a regular route parameter. // // Add the prefix before the wildcard into the current node. if wildcard.start > 0 { node.prefix = prefix.slice_until(wildcard.start).to_owned(); prefix = prefix.slice_off(wildcard.start); } // Find the end of this route segment. let terminator = prefix .iter() .position(|&b| b == b'/') // Include the '/' in the suffix. .map(|b| b + 1) .unwrap_or(prefix.len()); let wildcard = prefix.slice_until(wildcard.len()); let suffix = prefix.slice_until(terminator).slice_off(wildcard.len()); prefix = prefix.slice_off(terminator); // Multiple parameters within the same segment, e.g. `/{foo}{bar}`. if matches!(find_wildcard(suffix), Ok(Some(_))) { return Err(InsertError::InvalidParamSegment); } // Add the parameter as a child node. let has_suffix = !matches!(*suffix, b"" | b"/"); let child = node.add_child(Node { priority: 1, node_type: NodeType::Param { suffix: has_suffix }, prefix: wildcard.to_owned(), ..Node::default() }); node.wild_child = true; node = &mut node.children[child]; // Add the static suffix until the '/', if there is one. // // Note that for '/' suffixes where `suffix: false`, this // unconditionally introduces an extra node for the '/' // without attempting to merge with the remaining route. // This makes converting a non-suffix parameter node into // a suffix one easier during insertion, but slightly hurts // performance. if !suffix.is_empty() { let child = node.add_suffix_child(Node { priority: 1, node_type: NodeType::Static, prefix: suffix.to_owned(), ..Node::default() }); node = &mut node.children[child]; } // If the route ends here, insert the value. if prefix.is_empty() { node.value = Some(UnsafeCell::new(val)); return Ok(node); } // If there is a static segment after the '/', setup the node // for the rest of the route. if prefix[0] != b'{' || prefix.is_escaped(0) { node.indices.push(prefix[0]); let child = node.add_child(Node { priority: 1, ..Node::default() }); node = &mut node.children[child]; } } } // Adds a child to this 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 } } // Adds a suffix child to this node, keeping suffixes sorted by ascending length. fn add_suffix_child(&mut self, child: Node) -> usize { let i = self .children .partition_point(|node| node.prefix.len() >= child.prefix.len()); self.children.insert(i, child); i } // Increments priority of the given child node, reordering the children if necessary. // // Returns the new index of the node. fn update_child_priority(&mut self, i: usize) -> usize { self.children[i].priority += 1; let priority = self.children[i].priority; // Move the node to the front as necessary. let mut updated = i; while updated > 0 && self.children[updated - 1].priority < priority { self.children.swap(updated - 1, updated); updated -= 1; } // Update the position of the indices to match. if updated != i { self.indices[updated..=i].rotate_right(1); } updated } /// Removes a route from the tree, returning the value if the route already existed. /// /// The provided path should be the same as the one used to insert the route, including /// wildcards. pub fn remove(&mut self, route: String) -> Option { let route = UnescapedRoute::new(route.into_bytes()); let (route, remapping) = normalize_params(route).ok()?; let mut remaining = route.unescaped(); // Check if we are removing the root node. if remaining == self.prefix.unescaped() { let value = self.value.take().map(UnsafeCell::into_inner); // If the root node has no children, we can reset it. if self.children.is_empty() { *self = Node::default(); } return value; } let mut node = self; 'walk: loop { // Could not find a match. if remaining.len() <= node.prefix.len() { return None; } // Otherwise, the path is longer than this node's prefix, search deeper. let (prefix, rest) = remaining.split_at(node.prefix.len()); // The prefix does not match. if prefix != node.prefix.unescaped() { return None; } let next = rest[0]; remaining = rest; // If this is a parameter node, we have to find the matching suffix. if matches!(node.node_type, NodeType::Param { .. }) { let terminator = remaining .iter() .position(|&b| b == b'/') .map(|b| b + 1) .unwrap_or(remaining.len()); let suffix = &remaining[..terminator]; for (i, child) in node.children.iter().enumerate() { // Find the matching suffix. if *child.prefix == *suffix { // If this is the end of the path, remove the suffix node. if terminator == remaining.len() { return node.remove_child(i, &remapping); } // Otherwise, continue searching. remaining = &remaining[terminator - child.prefix.len()..]; node = &mut node.children[i]; continue 'walk; } } } // Find a child node that matches the next character in the route. if let Some(i) = node.indices.iter().position(|&c| c == next) { // The route matches, remove the node. if node.children[i].prefix.unescaped() == remaining { return node.remove_child(i, &remapping); } // Otherwise, continue searching. node = &mut node.children[i]; continue 'walk; } // If there is no matching wildcard child, there is no matching route. if !node.wild_child { return None; } // If the route does match, remove the node. if node.children.last_mut().unwrap().prefix.unescaped() == remaining { return node.remove_child(node.children.len() - 1, &remapping); } // Otherwise, keep searching deeper. node = node.children.last_mut().unwrap(); } } /// Remove the child node at the given index, if the route parameters match. fn remove_child(&mut self, i: usize, remapping: &ParamRemapping) -> Option { // Require an exact match to remove a route. // // For example, `/{a}` cannot be used to remove `/{b}`. if self.children[i].remapping != *remapping { return None; } // If the node does not have any children, we can remove it completely. let value = if self.children[i].children.is_empty() { // Remove the child node. let child = self.children.remove(i); match child.node_type { // Remove the index if we removed a static prefix that is // not a suffix node. NodeType::Static if !matches!(self.node_type, NodeType::Param { .. }) => { self.indices.remove(i); } // Otherwise, we removed a wildcard. _ => self.wild_child = false, } child.value } // Otherwise, remove the value but preserve the node. else { self.children[i].value.take() }; value.map(UnsafeCell::into_inner) } /// Iterates over the tree and calls the given visitor function /// with fully resolved path and its value. pub fn for_each(self, mut visitor: V) { let mut queue = VecDeque::from([(self.prefix.clone(), self)]); // Perform a BFS on the routing tree. while let Some((mut prefix, mut node)) = queue.pop_front() { denormalize_params(&mut prefix, &node.remapping); if let Some(value) = node.value.take() { let path = String::from_utf8(prefix.unescaped().to_vec()).unwrap(); visitor(path, value.into_inner()); } // Traverse the child nodes. for child in node.children { let mut prefix = prefix.clone(); prefix.append(&child.prefix); queue.push_back((prefix, child)); } } } } /// A wildcard node that was skipped during a tree search. /// /// Contains the state necessary to backtrack to the given node. struct Skipped<'node, 'path, T> { // The node that was skipped. node: &'node Node, /// The path at the time we skipped this node. path: &'path [u8], // The number of parameters that were present. params: usize, } impl Node { // Returns the node matching the given path. // // Returning an `UnsafeCell` allows us to avoid duplicating the logic between `Node::at` and // `Node::at_mut`, as Rust doesn't have a great way of abstracting over mutability. #[inline] pub fn at<'node, 'path>( &'node self, mut path: &'path [u8], ) -> Result<(&'node UnsafeCell, Params<'node, 'path>), MatchError> { let mut node = self; let mut backtracking = false; let mut params = Params::new(); let mut skipped: Vec> = Vec::new(); 'backtrack: loop { 'walk: loop { // Reached the end of the if path.len() <= node.prefix.len() { // Check for an exact match. if *path == *node.prefix { // Found the matching value. if let Some(ref value) = node.value { // Remap the keys of any route parameters we accumulated during the search. params.for_each_key_mut(|(i, param)| param.key = &node.remapping[i]); return Ok((value, params)); } } break 'walk; } // Otherwise, the path is longer than this node's prefix, search deeper. let (prefix, rest) = path.split_at(node.prefix.len()); // The prefix does not match. if *prefix != *node.prefix { break 'walk; } let previous = path; path = rest; // If we are currently backtracking, avoid searching static children // that we already searched. if !backtracking { let next = path[0]; // Find a child node that matches the next character in the path. if let Some(i) = node.indices.iter().position(|&c| c == next) { // Keep track of wildcard routes that we skip. // // We may end up needing to backtrack later in case we do not find a // match. if node.wild_child { skipped.push(Skipped { node, path: previous, params: params.len(), }); } // Continue searching. node = &node.children[i]; continue 'walk; } } // We didn't find a matching static child. // // If there are no wildcards, then there are no matching routes in the tree. if !node.wild_child { break 'walk; } // Continue searching in the wildcard child, which is kept at the end of the list. node = node.children.last().unwrap(); match node.node_type { NodeType::Param { suffix: false } => { // Check for more path segments. let terminator = match path.iter().position(|&c| c == b'/') { // Double `//` implying an empty parameter, no match. Some(0) => break 'walk, // Found another segment. Some(i) => i, // This is the last path segment. None => { // If this is the last path segment and there is a matching // value without a suffix, we have a match. let Some(ref value) = node.value else { break 'walk; }; // Store the parameter value. params.push(b"", path); // Remap the keys of any route parameters we accumulated during the search. params .for_each_key_mut(|(i, param)| param.key = &node.remapping[i]); return Ok((value, params)); } }; // Found another path segment. let (param, rest) = path.split_at(terminator); // If there is a static child, continue the search. let [child] = node.children.as_slice() else { break 'walk; }; // Store the parameter value. // Parameters are normalized so this key is irrelevant for now. params.push(b"", param); // Continue searching. path = rest; node = child; backtracking = false; continue 'walk; } NodeType::Param { suffix: true } => { // Check for more path segments. let slash = path.iter().position(|&c| c == b'/'); let terminator = match slash { // Double `//` implying an empty parameter, no match. Some(0) => break 'walk, // Found another segment. Some(i) => i + 1, // This is the last path segment. None => path.len(), }; for child in node.children.iter() { // Ensure there is a possible match with a non-zero suffix. if child.prefix.len() >= terminator { continue; } let suffix_start = terminator - child.prefix.len(); let (param, suffix) = path[..terminator].split_at(suffix_start); // Continue searching if the suffix matches. if *suffix == *child.prefix { node = child; path = &path[suffix_start..]; backtracking = false; // Parameters are normalized so this key is irrelevant for now. params.push(b"", param); continue 'walk; } } // If this is the last path segment and there is a matching // value without a suffix, we have a match. let value = match node.value { // Found the matching value. Some(ref value) if slash.is_none() => value, _ => break 'walk, }; // Store the parameter value. params.push(b"", path); // Remap the keys of any route parameters we accumulated during the search. params.for_each_key_mut(|(i, param)| param.key = &node.remapping[i]); return Ok((value, params)); } NodeType::CatchAll => { // Catch-all segments are only allowed at the end of the route, meaning // this node must contain the value. let value = match node.value { // Found the matching value. Some(ref value) => value, // Otherwise, there are no matching routes in the tree. None => return Err(MatchError::NotFound), }; // Remap the keys of any route parameters we accumulated during the search. params.for_each_key_mut(|(i, param)| param.key = &node.remapping[i]); // Store the final catch-all parameter (`{*...}`). let key = &node.prefix[2..node.prefix.len() - 1]; params.push(key, path); return Ok((value, params)); } _ => unreachable!(), } } // Try backtracking to any matching wildcard nodes that we skipped while // traversing the tree. while let Some(skipped) = skipped.pop() { if skipped.path.ends_with(path) { // Found a matching node, restore the search state. path = skipped.path; node = skipped.node; backtracking = true; params.truncate(skipped.params); continue 'backtrack; } } return Err(MatchError::NotFound); } } /// Test helper that ensures route priorities are consistent. #[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. /// /// To support conflicting routes like `/{a}/foo` and `/{b}/bar`, route parameters /// are normalized before being inserted into the tree. Parameter remapping are /// stored at nodes containing values, containing the "true" names of all route parameters /// for the given route. type ParamRemapping = Vec>; /// Returns `path` with normalized route parameters, and a parameter remapping /// to store at the node for this route. /// /// Note that the parameter remapping may contain unescaped characters. fn normalize_params( mut path: UnescapedRoute, ) -> Result<(UnescapedRoute, ParamRemapping), InsertError> { let mut start = 0; let mut original = ParamRemapping::new(); // Parameter names are normalized alphabetically. let mut next = b'a'; loop { // Find a wildcard to normalize. let mut wildcard = match find_wildcard(path.as_ref().slice_off(start))? { Some(wildcard) => wildcard, // No wildcard, we are done. None => return Ok((path, original)), }; wildcard.start += start; wildcard.end += start; // Ensure the parameter has a valid name. if wildcard.len() < 2 { return Err(InsertError::InvalidParam); } // We don't need to normalize catch-all parameters, as they are always // at the end of a route. if path[wildcard.clone()][1] == b'*' { start = wildcard.end; continue; } // Normalize the parameter. let removed = path.splice(wildcard.clone(), vec![b'{', next, b'}']); // Preserve the original name for remapping. let mut removed = removed.skip(1).collect::>(); removed.pop(); original.push(removed); next += 1; if next > b'z' { panic!("Too many route parameters."); } // Continue the search after the parameter we just normalized. start = wildcard.start + 3; } } /// Restores `route` to it's original, denormalized form. pub(crate) fn denormalize_params(route: &mut UnescapedRoute, params: &ParamRemapping) { let mut start = 0; let mut i = 0; loop { // Find a wildcard to denormalize. let mut wildcard = match find_wildcard(route.as_ref().slice_off(start)).unwrap() { Some(w) => w, None => return, }; wildcard.start += start; wildcard.end += start; // Get the corresponding parameter remapping. let mut next = match params.get(i) { Some(param) => param.clone(), None => return, }; // Denormalize this parameter. next.insert(0, b'{'); next.push(b'}'); let _ = route.splice(wildcard.clone(), next.clone()); i += 1; start = wildcard.start + next.len(); } } // Searches for a wildcard segment and checks the path for invalid characters. fn find_wildcard(path: UnescapedRef<'_>) -> Result>, InsertError> { for (start, &c) in path.iter().enumerate() { // Found an unescaped closing brace without a corresponding opening brace. if c == b'}' && !path.is_escaped(start) { return Err(InsertError::InvalidParam); } // Keep going until we find an unescaped opening brace. if c != b'{' || path.is_escaped(start) { continue; } // Ensure there is a non-empty parameter name. if path.get(start + 1) == Some(&b'}') { return Err(InsertError::InvalidParam); } // Find the corresponding closing brace. for (i, &c) in path.iter().enumerate().skip(start + 2) { match c { b'}' => { // This closing brace was escaped, keep searching. if path.is_escaped(i) { continue; } // Ensure catch-all parameters have a non-empty name. if path.get(i - 1) == Some(&b'*') { return Err(InsertError::InvalidParam); } return Ok(Some(start..i + 1)); } // `*` and `/` are invalid in parameter names. b'*' | b'/' => return Err(InsertError::InvalidParam), _ => {} } } // Missing closing brace. return Err(InsertError::InvalidParam); } Ok(None) } impl Clone for Node where T: Clone, { fn clone(&self) -> Node { 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()) }); Node { value, prefix: self.prefix.clone(), wild_child: self.wild_child, node_type: self.node_type.clone(), indices: self.indices.clone(), children: self.children.clone(), remapping: self.remapping.clone(), priority: self.priority, } } } impl Default for Node { fn default() -> Node { Node { remapping: ParamRemapping::new(), prefix: UnescapedRoute::default(), wild_child: false, node_type: NodeType::Static, indices: Vec::new(), children: Vec::new(), value: None, priority: 0, } } } impl fmt::Debug for Node where T: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Safety: We only expose `&mut T` through `&mut self`. let value = unsafe { self.value.as_ref().map(|x| &*x.get()) }; let mut f = f.debug_struct("Node"); f.field("value", &value) .field("prefix", &self.prefix) .field("node_type", &self.node_type) .field("children", &self.children); // Extra information for debugging purposes. #[cfg(test)] { let indices = self .indices .iter() .map(|&x| char::from_u32(x as _)) .collect::>(); let params = self .remapping .iter() .map(|x| std::str::from_utf8(x).unwrap()) .collect::>(); f.field("indices", &indices).field("params", ¶ms); } f.finish() } } matchit-0.8.6/tests/insert.rs000064400000000000000000000154521046102023000143130ustar 00000000000000use matchit::{InsertError, Router}; struct InsertTest(Vec<(&'static str, Result<(), InsertError>)>); impl InsertTest { fn run(self) { let mut router = Router::new(); for (route, expected) in self.0 { let got = router.insert(route, route.to_owned()); assert_eq!(got, expected, "{route}"); } } } fn conflict(with: &'static str) -> InsertError { InsertError::Conflict { with: with.into() } } #[test] fn wildcard_conflict() { InsertTest(vec![ ("/cmd/{tool}/{sub}", Ok(())), ("/cmd/vet", Ok(())), ("/foo/bar", Ok(())), ("/foo/{name}", Ok(())), ("/foo/{names}", Err(conflict("/foo/{name}"))), ("/cmd/{*path}", Err(conflict("/cmd/{tool}/{sub}"))), ("/cmd/{xxx}/names", Ok(())), ("/cmd/{tool}/{xxx}/foo", Ok(())), ("/src/{*filepath}", Ok(())), ("/src/{file}", Err(conflict("/src/{*filepath}"))), ("/src/static.json", Ok(())), ("/src/$filepathx", Ok(())), ("/src/", Ok(())), ("/src/foo/bar", Ok(())), ("/src1/", Ok(())), ("/src1/{*filepath}", Ok(())), ("/src2{*filepath}", Ok(())), ("/src2/{*filepath}", Ok(())), ("/src2/", Ok(())), ("/src2", Ok(())), ("/src3", Ok(())), ("/src3/{*filepath}", Ok(())), ("/search/{query}", Ok(())), ("/search/valid", Ok(())), ("/user_{name}", Ok(())), ("/user_x", Ok(())), ("/user_{bar}", Err(conflict("/user_{name}"))), ("/id{id}", Ok(())), ("/id/{id}", Ok(())), ("/x/{id}", Ok(())), ("/x/{id}/", Ok(())), ("/x/{id}y", Ok(())), ("/x/{id}y/", Ok(())), ("/x/x{id}", Ok(())), ("/x/x{id}y", Ok(())), ("/qux/id", Ok(())), ("/qux/{id}y", Ok(())), ("/qux/{id}", Ok(())), ("/qux/{id}/", Ok(())), ("/qux/{id}x", Ok(())), ("/qux/x{id}y", Ok(())), ("/qux/x{id}", Ok(())), ("/qux/x{id}", Err(conflict("/qux/x{id}"))), ("/qux/x{id}y", Err(conflict("/qux/x{id}y"))), ("/bar/{id}", Ok(())), ("/bar/x{id}y", Ok(())), ]) .run() } #[test] fn invalid_catchall() { InsertTest(vec![ ("/non-leading-{*catchall}", Ok(())), ("/foo/bar{*catchall}", Ok(())), ("/src/{*filepath}x", Err(InsertError::InvalidCatchAll)), ("/src/{*filepath}/x", Err(InsertError::InvalidCatchAll)), ("/src2/", Ok(())), ("/src2/{*filepath}/x", Err(InsertError::InvalidCatchAll)), ]) .run() } #[test] fn catchall_root_conflict() { InsertTest(vec![("/", Ok(())), ("/{*filepath}", Ok(()))]).run() } #[test] fn child_conflict() { InsertTest(vec![ ("/cmd/vet", Ok(())), ("/cmd/{tool}", Ok(())), ("/cmd/{tool}/{sub}", Ok(())), ("/cmd/{tool}/misc", Ok(())), ("/cmd/{tool}/{bad}", Err(conflict("/cmd/{tool}/{sub}"))), ("/src/AUTHORS", Ok(())), ("/src/{*filepath}", Ok(())), ("/user_x", Ok(())), ("/user_{name}", Ok(())), ("/id/{id}", Ok(())), ("/id{id}", Ok(())), ("/{id}", Ok(())), ("/{*filepath}", Err(conflict("/{id}"))), ]) .run() } #[test] fn duplicates() { InsertTest(vec![ ("/", Ok(())), ("/", Err(conflict("/"))), ("/doc/", Ok(())), ("/doc/", Err(conflict("/doc/"))), ("/src/{*filepath}", Ok(())), ("/src/{*filepath}", Err(conflict("/src/{*filepath}"))), ("/search/{query}", Ok(())), ("/search/{query}", Err(conflict("/search/{query}"))), ("/user_{name}", Ok(())), ("/user_{name}", Err(conflict("/user_{name}"))), ]) .run() } #[test] fn unnamed_param() { InsertTest(vec![ ("/{}", Err(InsertError::InvalidParam)), ("/user{}/", Err(InsertError::InvalidParam)), ("/cmd/{}/", Err(InsertError::InvalidParam)), ("/src/{*}", Err(InsertError::InvalidParam)), ]) .run() } #[test] fn double_params() { InsertTest(vec![ ("/{foo}{bar}", Err(InsertError::InvalidParamSegment)), ("/{foo}{bar}/", Err(InsertError::InvalidParamSegment)), ("/{foo}{{*bar}/", Err(InsertError::InvalidParamSegment)), ]) .run() } #[test] fn normalized_conflict() { InsertTest(vec![ ("/x/{foo}/bar", Ok(())), ("/x/{bar}/bar", Err(conflict("/x/{foo}/bar"))), ("/{y}/bar/baz", Ok(())), ("/{y}/baz/baz", Ok(())), ("/{z}/bar/bat", Ok(())), ("/{z}/bar/baz", Err(conflict("/{y}/bar/baz"))), ]) .run() } #[test] fn more_conflicts() { InsertTest(vec![ ("/con{tact}", Ok(())), ("/who/are/{*you}", Ok(())), ("/who/foo/hello", Ok(())), ("/whose/{users}/{name}", Ok(())), ("/who/are/foo", Ok(())), ("/who/are/foo/bar", Ok(())), ("/con{nection}", Err(conflict("/con{tact}"))), ( "/whose/{users}/{user}", Err(conflict("/whose/{users}/{name}")), ), ]) .run() } #[test] fn catchall_static_overlap() { InsertTest(vec![ ("/bar", Ok(())), ("/bar/", Ok(())), ("/bar/{*foo}", Ok(())), ]) .run(); InsertTest(vec![ ("/foo", Ok(())), ("/{*bar}", Ok(())), ("/bar", Ok(())), ("/baz", Ok(())), ("/baz/{split}", Ok(())), ("/", Ok(())), ("/{*bar}", Err(conflict("/{*bar}"))), ("/{*zzz}", Err(conflict("/{*bar}"))), ("/{xxx}", Err(conflict("/{*bar}"))), ]) .run(); InsertTest(vec![ ("/{*bar}", Ok(())), ("/bar", Ok(())), ("/bar/x", Ok(())), ("/bar_{x}", Ok(())), ("/bar_{x}", Err(conflict("/bar_{x}"))), ("/bar_{x}/y", Ok(())), ("/bar/{x}", Ok(())), ]) .run(); } #[test] fn duplicate_conflict() { InsertTest(vec![ ("/hey", Ok(())), ("/hey/users", Ok(())), ("/hey/user", Ok(())), ("/hey/user", Err(conflict("/hey/user"))), ]) .run() } #[test] fn invalid_param() { InsertTest(vec![ ("{", Err(InsertError::InvalidParam)), ("}", Err(InsertError::InvalidParam)), ("x{y", Err(InsertError::InvalidParam)), ("x}", Err(InsertError::InvalidParam)), ]) .run(); } #[test] fn escaped_param() { InsertTest(vec![ ("{{", Ok(())), ("}}", Ok(())), ("xx}}", Ok(())), ("}}yy", Ok(())), ("}}yy{{}}", Ok(())), ("}}yy{{}}{{}}y{{", Ok(())), ("}}yy{{}}{{}}y{{", Err(conflict("}yy{}{}y{"))), ("/{{yy", Ok(())), ("/{yy}", Ok(())), ("/foo", Ok(())), ("/foo/{{", Ok(())), ("/foo/{{/{x}", Ok(())), ("/foo/{ba{{r}", Ok(())), ("/bar/{ba}}r}", Ok(())), ("/xxx/{x{{}}y}", Ok(())), ]) .run() } #[test] fn bare_catchall() { InsertTest(vec![("{*foo}", Ok(())), ("foo/{*bar}", Ok(()))]).run() } matchit-0.8.6/tests/match.rs000064400000000000000000001071621046102023000141030ustar 00000000000000use matchit::{MatchError, Router}; // https://github.com/ibraheemdev/matchit/issues/22 #[test] fn partial_overlap() { let mut x = Router::new(); x.insert("/foo_bar", "Welcome!").unwrap(); x.insert("/foo/bar", "Welcome!").unwrap(); assert_eq!(x.at("/foo/").unwrap_err(), MatchError::NotFound); let mut x = Router::new(); x.insert("/foo", "Welcome!").unwrap(); x.insert("/foo/bar", "Welcome!").unwrap(); assert_eq!(x.at("/foo/").unwrap_err(), MatchError::NotFound); } // https://github.com/ibraheemdev/matchit/issues/31 #[test] fn wildcard_overlap() { let mut router = Router::new(); router.insert("/path/foo", "foo").unwrap(); router.insert("/path/{*rest}", "wildcard").unwrap(); assert_eq!(router.at("/path/foo").map(|m| *m.value), Ok("foo")); assert_eq!(router.at("/path/bar").map(|m| *m.value), Ok("wildcard")); assert_eq!(router.at("/path/foo/").map(|m| *m.value), Ok("wildcard")); let mut router = Router::new(); router.insert("/path/foo/{arg}", "foo").unwrap(); router.insert("/path/{*rest}", "wildcard").unwrap(); assert_eq!(router.at("/path/foo/myarg").map(|m| *m.value), Ok("foo")); assert_eq!( router.at("/path/foo/myarg/").map(|m| *m.value), Ok("wildcard") ); assert_eq!( router.at("/path/foo/myarg/bar/baz").map(|m| *m.value), Ok("wildcard") ); } // https://github.com/ibraheemdev/matchit/issues/12 #[test] fn overlapping_param_backtracking() { let mut matcher = Router::new(); matcher.insert("/{object}/{id}", "object with id").unwrap(); matcher .insert("/secret/{id}/path", "secret with id and path") .unwrap(); let matched = matcher.at("/secret/978/path").unwrap(); assert_eq!(matched.params.get("id"), Some("978")); let matched = matcher.at("/something/978").unwrap(); assert_eq!(matched.params.get("id"), Some("978")); assert_eq!(matched.params.get("object"), Some("something")); let matched = matcher.at("/secret/978").unwrap(); assert_eq!(matched.params.get("id"), Some("978")); } struct MatchTest { routes: Vec<&'static str>, matches: Vec<( &'static str, &'static str, Result, ()>, )>, } impl MatchTest { fn run(self) { let mut router = Router::new(); for route in self.routes { assert_eq!(router.insert(route, route.to_owned()), Ok(()), "{route}"); } router.check_priorities().unwrap(); for (path, route, params) in self.matches { match router.at(path) { Ok(x) => { assert_eq!(x.value, route); let got = x.params.iter().collect::>(); assert_eq!(params.unwrap(), got); router.at_mut(path).unwrap().value.push_str("Z"); assert!(router.at(path).unwrap().value.contains("Z")); router.at_mut(path).unwrap().value.pop(); } Err(err) => { if let Ok(params) = params { panic!("{err} for {path} ({params:?})"); } } } } } } macro_rules! p { ($($k:expr => $v:expr),* $(,)?) => { Ok(vec![$(($k, $v)),*]) }; } // https://github.com/ibraheemdev/matchit/issues/42 #[test] fn bare_catchall() { MatchTest { routes: vec!["{*foo}", "foo/{*bar}"], matches: vec![ ("x/y", "{*foo}", p! { "foo" => "x/y" }), ("/x/y", "{*foo}", p! { "foo" => "/x/y" }), ("/foo/x/y", "{*foo}", p! { "foo" => "/foo/x/y" }), ("foo/x/y", "foo/{*bar}", p! { "bar" => "x/y" }), ], } .run() } #[test] fn normalized() { MatchTest { routes: vec![ "/x/{foo}/bar", "/x/{bar}/baz", "/{foo}/{baz}/bax", "/{foo}/{bar}/baz", "/{fod}/{baz}/{bax}/foo", "/{fod}/baz/bax/foo", "/{foo}/baz/bax", "/{bar}/{bay}/bay", "/s", "/s/s", "/s/s/s", "/s/s/s/s", "/s/s/{s}/x", "/s/s/{y}/d", ], matches: vec![ ("/x/foo/bar", "/x/{foo}/bar", p! { "foo" => "foo" }), ("/x/foo/baz", "/x/{bar}/baz", p! { "bar" => "foo" }), ( "/y/foo/baz", "/{foo}/{bar}/baz", p! { "foo" => "y", "bar" => "foo" }, ), ( "/y/foo/bax", "/{foo}/{baz}/bax", p! { "foo" => "y", "baz" => "foo" }, ), ( "/y/baz/baz", "/{foo}/{bar}/baz", p! { "foo" => "y", "bar" => "baz" }, ), ("/y/baz/bax/foo", "/{fod}/baz/bax/foo", p! { "fod" => "y" }), ( "/y/baz/b/foo", "/{fod}/{baz}/{bax}/foo", p! { "fod" => "y", "baz" => "baz", "bax" => "b" }, ), ("/y/baz/bax", "/{foo}/baz/bax", p! { "foo" => "y" }), ( "/z/bar/bay", "/{bar}/{bay}/bay", p! { "bar" => "z", "bay" => "bar" }, ), ("/s", "/s", p! {}), ("/s/s", "/s/s", p! {}), ("/s/s/s", "/s/s/s", p! {}), ("/s/s/s/s", "/s/s/s/s", p! {}), ("/s/s/s/x", "/s/s/{s}/x", p! { "s" => "s" }), ("/s/s/s/d", "/s/s/{y}/d", p! { "y" => "s" }), ], } .run() } #[test] fn blog() { MatchTest { routes: vec![ "/{page}", "/posts/{year}/{month}/{post}", "/posts/{year}/{month}/index", "/posts/{year}/top", "/static/{*path}", "/favicon.ico", ], matches: vec![ ("/about", "/{page}", p! { "page" => "about" }), ( "/posts/2021/01/rust", "/posts/{year}/{month}/{post}", p! { "year" => "2021", "month" => "01", "post" => "rust" }, ), ( "/posts/2021/01/index", "/posts/{year}/{month}/index", p! { "year" => "2021", "month" => "01" }, ), ( "/posts/2021/top", "/posts/{year}/top", p! { "year" => "2021" }, ), ( "/static/foo.png", "/static/{*path}", p! { "path" => "foo.png" }, ), ("/favicon.ico", "/favicon.ico", p! {}), ], } .run() } #[test] fn double_overlap() { MatchTest { routes: vec![ "/{object}/{id}", "/secret/{id}/path", "/secret/978", "/other/{object}/{id}/", "/other/an_object/{id}", "/other/static/path", "/other/long/static/path/", ], matches: vec![ ( "/secret/978/path", "/secret/{id}/path", p! { "id" => "978" }, ), ( "/some_object/978", "/{object}/{id}", p! { "object" => "some_object", "id" => "978" }, ), ("/secret/978", "/secret/978", p! {}), ("/super_secret/978/", "/{object}/{id}", Err(())), ( "/other/object/1/", "/other/{object}/{id}/", p! { "object" => "object", "id" => "1" }, ), ("/other/object/1/2", "/other/{object}/{id}", Err(())), ( "/other/an_object/1", "/other/an_object/{id}", p! { "id" => "1" }, ), ("/other/static/path", "/other/static/path", p! {}), ( "/other/long/static/path/", "/other/long/static/path/", p! {}, ), ], } .run() } #[test] fn catchall_off_by_one() { MatchTest { routes: vec!["/foo/{*catchall}", "/bar", "/bar/", "/bar/{*catchall}"], matches: vec![ ("/foo", "", Err(())), ("/foo/", "", Err(())), ("/foo/x", "/foo/{*catchall}", p! { "catchall" => "x" }), ("/bar", "/bar", p! {}), ("/bar/", "/bar/", p! {}), ("/bar/x", "/bar/{*catchall}", p! { "catchall" => "x" }), ], } .run() } #[test] fn overlap() { MatchTest { routes: vec![ "/foo", "/bar", "/{*bar}", "/baz", "/baz/", "/baz/x", "/baz/{xxx}", "/", "/xxx/{*x}", "/xxx/", ], matches: vec![ ("/foo", "/foo", p! {}), ("/bar", "/bar", p! {}), ("/baz", "/baz", p! {}), ("/baz/", "/baz/", p! {}), ("/baz/x", "/baz/x", p! {}), ("/???", "/{*bar}", p! { "bar" => "???" }), ("/", "/", p! {}), ("", "", Err(())), ("/xxx/y", "/xxx/{*x}", p! { "x" => "y" }), ("/xxx/", "/xxx/", p! {}), ("/xxx", "/{*bar}", p! { "bar" => "xxx" }), ], } .run() } #[test] fn missing_trailing_slash_param() { MatchTest { routes: vec!["/foo/{object}/{id}", "/foo/bar/baz", "/foo/secret/978/"], matches: vec![ ("/foo/secret/978/", "/foo/secret/978/", p! {}), ( "/foo/secret/978", "/foo/{object}/{id}", p! { "object" => "secret", "id" => "978" }, ), ], } .run() } #[test] fn extra_trailing_slash_param() { MatchTest { routes: vec!["/foo/{object}/{id}", "/foo/bar/baz", "/foo/secret/978"], matches: vec![ ("/foo/secret/978/", "", Err(())), ("/foo/secret/978", "/foo/secret/978", p! {}), ], } .run() } #[test] fn missing_trailing_slash_catch_all() { MatchTest { routes: vec!["/foo/{*bar}", "/foo/bar/baz", "/foo/secret/978/"], matches: vec![ ( "/foo/secret/978", "/foo/{*bar}", p! { "bar" => "secret/978" }, ), ("/foo/secret/978/", "/foo/secret/978/", p! {}), ], } .run() } #[test] fn extra_trailing_slash_catch_all() { MatchTest { routes: vec!["/foo/{*bar}", "/foo/bar/baz", "/foo/secret/978"], matches: vec![ ( "/foo/secret/978/", "/foo/{*bar}", p! { "bar" => "secret/978/" }, ), ("/foo/secret/978", "/foo/secret/978", p! {}), ], } .run() } #[test] fn double_overlap_trailing_slash() { MatchTest { routes: vec![ "/{object}/{id}", "/secret/{id}/path", "/secret/978/", "/other/{object}/{id}/", "/other/an_object/{id}", "/other/static/path", "/other/long/static/path/", ], matches: vec![ ("/secret/978/path/", "", Err(())), ("/object/id/", "", Err(())), ("/object/id/path", "", Err(())), ("/other/object/1", "", Err(())), ("/other/object/1/2", "", Err(())), ( "/other/an_object/1/", "/other/{object}/{id}/", p! { "object" => "an_object", "id" => "1" }, ), ( "/other/static/path/", "/other/{object}/{id}/", p! { "object" => "static", "id" => "path" }, ), ("/other/long/static/path", "", Err(())), ("/other/object/static/path", "", Err(())), ], } .run() } #[test] fn trailing_slash_overlap() { MatchTest { routes: vec!["/foo/{x}/baz/", "/foo/{x}/baz", "/foo/bar/bar"], matches: vec![ ("/foo/x/baz/", "/foo/{x}/baz/", p! { "x" => "x" }), ("/foo/x/baz", "/foo/{x}/baz", p! { "x" => "x" }), ("/foo/bar/bar", "/foo/bar/bar", p! {}), ], } .run() } #[test] fn trailing_slash() { MatchTest { routes: vec![ "/hi", "/b/", "/search/{query}", "/cmd/{tool}/", "/src/{*filepath}", "/x", "/x/y", "/y/", "/y/z", "/0/{id}", "/0/{id}/1", "/1/{id}/", "/1/{id}/2", "/aa", "/a/", "/admin", "/admin/static", "/admin/{category}", "/admin/{category}/{page}", "/doc", "/doc/rust_faq.html", "/doc/rust1.26.html", "/no/a", "/no/b", "/no/a/b/{*other}", "/api/{page}/{name}", "/api/hello/{name}/bar/", "/api/bar/{name}", "/api/baz/foo", "/api/baz/foo/bar", "/foo/{p}", ], matches: vec![ ("/hi/", "", Err(())), ("/b", "", Err(())), ("/search/rustacean/", "", Err(())), ("/cmd/vet", "", Err(())), ("/src", "", Err(())), ("/src/", "", Err(())), ("/x/", "", Err(())), ("/y", "", Err(())), ("/0/rust/", "", Err(())), ("/1/rust", "", Err(())), ("/a", "", Err(())), ("/admin/", "", Err(())), ("/doc/", "", Err(())), ("/admin/static/", "", Err(())), ("/admin/cfg/", "", Err(())), ("/admin/cfg/users/", "", Err(())), ("/api/hello/x/bar", "", Err(())), ("/api/baz/foo/", "", Err(())), ("/api/baz/bax/", "", Err(())), ("/api/bar/huh/", "", Err(())), ("/api/baz/foo/bar/", "", Err(())), ("/api/world/abc/", "", Err(())), ("/foo/pp/", "", Err(())), ("/", "", Err(())), ("/no", "", Err(())), ("/no/", "", Err(())), ("/no/a/b", "", Err(())), ("/no/a/b/", "", Err(())), ("/_", "", Err(())), ("/_/", "", Err(())), ("/api", "", Err(())), ("/api/", "", Err(())), ("/api/hello/x/foo", "", Err(())), ("/api/baz/foo/bad", "", Err(())), ("/foo/p/p", "", Err(())), ], } .run() } #[test] fn backtracking_trailing_slash() { MatchTest { routes: vec!["/a/{b}/{c}", "/a/b/{c}/d/"], matches: vec![("/a/b/c/d", "", Err(()))], } .run() } #[test] fn root_trailing_slash() { MatchTest { routes: vec!["/foo", "/bar", "/{baz}"], matches: vec![("/", "", Err(()))], } .run() } #[test] fn catchall_overlap() { MatchTest { routes: vec!["/yyy/{*x}", "/yyy{*x}"], matches: vec![ ("/yyy/y", "/yyy/{*x}", p! { "x" => "y" }), ("/yyy/", "/yyy{*x}", p! { "x" => "/" }), ], } .run(); } #[test] fn escaped() { MatchTest { routes: vec![ "/", "/{{", "/}}", "/{{x", "/}}y{{", "/xy{{", "/{{/xyz", "/{ba{{r}", "/{ba{{r}/", "/{ba{{r}/x", "/baz/{xxx}", "/baz/{xxx}/xy{{", "/baz/{xxx}/}}xy{{{{", "/{{/{x}", "/xxx/", "/xxx/{x}}{{}}}}{{}}{{{{}}y}", ], matches: vec![ ("/", "/", p! {}), ("/{", "/{{", p! {}), ("/}", "/}}", p! {}), ("/{x", "/{{x", p! {}), ("/}y{", "/}}y{{", p! {}), ("/xy{", "/xy{{", p! {}), ("/{/xyz", "/{{/xyz", p! {}), ("/foo", "/{ba{{r}", p! { "ba{r" => "foo" }), ("/{{", "/{ba{{r}", p! { "ba{r" => "{{" }), ("/{{}}/", "/{ba{{r}/", p! { "ba{r" => "{{}}" }), ("/{{}}{{/x", "/{ba{{r}/x", p! { "ba{r" => "{{}}{{" }), ("/baz/x", "/baz/{xxx}", p! { "xxx" => "x" }), ("/baz/x/xy{", "/baz/{xxx}/xy{{", p! { "xxx" => "x" }), ("/baz/x/xy{{", "", Err(())), ("/baz/x/}xy{{", "/baz/{xxx}/}}xy{{{{", p! { "xxx" => "x" }), ("/{/{{", "/{{/{x}", p! { "x" => "{{" }), ("/xxx", "/{ba{{r}", p! { "ba{r" => "xxx" }), ("/xxx/", "/xxx/", p!()), ( "/xxx/foo", "/xxx/{x}}{{}}}}{{}}{{{{}}y}", p! { "x}{}}{}{{}y" => "foo" }, ), ], } .run() } #[test] fn empty_param() { MatchTest { routes: vec![ "/y/{foo}", "/x/{foo}/z", "/z/{*foo}", "/a/x{foo}", "/b/{foo}x", ], matches: vec![ ("/y/", "", Err(())), ("/x//z", "", Err(())), ("/z/", "", Err(())), ("/a/x", "", Err(())), ("/b/x", "", Err(())), ], } .run(); } #[test] fn wildcard_suffix() { MatchTest { routes: vec![ "/", "/{foo}x", "/foox", "/{foo}x/bar", "/{foo}x/bar/baz", "/x{foo}", "/x{foo}/bar", ], matches: vec![ ("/", "/", p! {}), ("/foox", "/foox", p! {}), ("/barx", "/{foo}x", p! { "foo" => "bar" }), ("/mx", "/{foo}x", p! { "foo" => "m" }), ("/mx/", "", Err(())), ("/mxm", "", Err(())), ("/mx/bar", "/{foo}x/bar", p! { "foo" => "m" }), ("/mxm/bar", "", Err(())), ("/x", "", Err(())), ("/xfoo", "/x{foo}", p! { "foo" => "foo" }), ("/xfoox", "/x{foo}", p! { "foo" => "foox" }), ("/xfoox/bar", "/x{foo}/bar", p! { "foo" => "foox" }), ("/xfoox/bar/baz", "/{foo}x/bar/baz", p! { "foo" => "xfoo" }), ], } .run(); } #[test] fn mixed_wildcard_suffix() { MatchTest { routes: vec![ "/", "/{f}o/b", "/{f}oo/b", "/{f}ooo/b", "/{f}oooo/b", "/foo/b", "/foo/{b}", "/foo/{b}one", "/foo/{b}one/", "/foo/{b}two", "/foo/{b}/one", "/foo/{b}one/one", "/foo/{b}two/one", "/foo/{b}one/one/", "/bar/{b}one", "/bar/{b}", "/bar/{b}/baz", "/bar/{b}one/baz", "/baz/{b}/bar", "/baz/{b}one/bar", ], matches: vec![ ("/", "/", p! {}), ("/o/b", "", Err(())), ("/fo/b", "/{f}o/b", p! { "f" => "f" }), ("/foo/b", "/foo/b", p! {}), ("/fooo/b", "/{f}ooo/b", p! { "f" => "f" }), ("/foooo/b", "/{f}oooo/b", p! { "f" => "f" }), ("/foo/b/", "", Err(())), ("/foooo/b/", "", Err(())), ("/foo/bb", "/foo/{b}", p! { "b" => "bb" }), ("/foo/bone", "/foo/{b}one", p! { "b" => "b" }), ("/foo/bone/", "/foo/{b}one/", p! { "b" => "b" }), ("/foo/btwo", "/foo/{b}two", p! { "b" => "b" }), ("/foo/btwo/", "", Err(())), ("/foo/b/one", "/foo/{b}/one", p! { "b" => "b" }), ("/foo/bone/one", "/foo/{b}one/one", p! { "b" => "b" }), ("/foo/bone/one/", "/foo/{b}one/one/", p! { "b" => "b" }), ("/foo/btwo/one", "/foo/{b}two/one", p! { "b" => "b" }), ("/bar/b", "/bar/{b}", p! { "b" => "b" }), ("/bar/b/baz", "/bar/{b}/baz", p! { "b" => "b" }), ("/bar/bone", "/bar/{b}one", p! { "b" => "b" }), ("/bar/bone/baz", "/bar/{b}one/baz", p! { "b" => "b" }), ("/baz/b/bar", "/baz/{b}/bar", p! { "b" => "b" }), ("/baz/bone/bar", "/baz/{b}one/bar", p! { "b" => "b" }), ], } .run(); } #[test] fn basic() { MatchTest { routes: vec![ "/hi", "/contact", "/co", "/c", "/a", "/ab", "/doc/", "/doc/rust_faq.html", "/doc/rust1.26.html", "/ʯ", "/β", "/sd!here", "/sd$here", "/sd&here", "/sd'here", "/sd(here", "/sd)here", "/sd+here", "/sd,here", "/sd;here", "/sd=here", ], matches: vec![ ("/a", "/a", p! {}), ("", "/", Err(())), ("/hi", "/hi", p! {}), ("/contact", "/contact", p! {}), ("/co", "/co", p! {}), ("", "/con", Err(())), ("", "/cona", Err(())), ("", "/no", Err(())), ("/ab", "/ab", p! {}), ("/ʯ", "/ʯ", p! {}), ("/β", "/β", p! {}), ("/sd!here", "/sd!here", p! {}), ("/sd$here", "/sd$here", p! {}), ("/sd&here", "/sd&here", p! {}), ("/sd'here", "/sd'here", p! {}), ("/sd(here", "/sd(here", p! {}), ("/sd)here", "/sd)here", p! {}), ("/sd+here", "/sd+here", p! {}), ("/sd,here", "/sd,here", p! {}), ("/sd;here", "/sd;here", p! {}), ("/sd=here", "/sd=here", p! {}), ], } .run() } #[test] fn wildcard() { MatchTest { routes: vec![ "/", "/cmd/{tool}/", "/cmd/{tool2}/{sub}", "/cmd/whoami", "/cmd/whoami/root", "/cmd/whoami/root/", "/src", "/src/", "/src/{*filepath}", "/search/", "/search/{query}", "/search/actix-web", "/search/google", "/user_{name}", "/user_{name}/about", "/files/{dir}/{*filepath}", "/doc/", "/doc/rust_faq.html", "/doc/rust1.26.html", "/info/{user}/public", "/info/{user}/project/{project}", "/info/{user}/project/rustlang", "/aa/{*xx}", "/ab/{*xx}", "/ab/hello{*xx}", "/{cc}", "/c1/{dd}/e", "/c1/{dd}/e1", "/{cc}/cc", "/{cc}/{dd}/ee", "/{cc}/{dd}/{ee}/ff", "/{cc}/{dd}/{ee}/{ff}/gg", "/{cc}/{dd}/{ee}/{ff}/{gg}/hh", "/get/test/abc/", "/get/{param}/abc/", "/something/{paramname}/thirdthing", "/something/secondthing/test", "/get/abc", "/get/{param}", "/get/abc/123abc", "/get/abc/{param}", "/get/abc/123abc/xxx8", "/get/abc/123abc/{param}", "/get/abc/123abc/xxx8/1234", "/get/abc/123abc/xxx8/{param}", "/get/abc/123abc/xxx8/1234/ffas", "/get/abc/123abc/xxx8/1234/{param}", "/get/abc/123abc/xxx8/1234/kkdd/12c", "/get/abc/123abc/xxx8/1234/kkdd/{param}", "/get/abc/{param}/test", "/get/abc/123abd/{param}", "/get/abc/123abddd/{param}", "/get/abc/123/{param}", "/get/abc/123abg/{param}", "/get/abc/123abf/{param}", "/get/abc/123abfff/{param}", ], matches: vec![ ("/", "/", p! {}), ("/cmd/test", "/cmd/{tool}/", Err(())), ("/cmd/test/", "/cmd/{tool}/", p! { "tool" => "test" }), ( "/cmd/test/3", "/cmd/{tool2}/{sub}", p! { "tool2" => "test", "sub" => "3" }, ), ("/cmd/who", "/cmd/{tool}/", Err(())), ("/cmd/who/", "/cmd/{tool}/", p! { "tool" => "who" }), ("/cmd/whoami", "/cmd/whoami", p! {}), ("/cmd/whoami/", "/cmd/{tool}/", p! { "tool" => "whoami" }), ( "/cmd/whoami/r", "/cmd/{tool2}/{sub}", p! { "tool2" => "whoami", "sub" => "r" }, ), ("/cmd/whoami/r/", "/cmd/{tool}/{sub}", Err(())), ("/cmd/whoami/root", "/cmd/whoami/root", p! {}), ("/cmd/whoami/root/", "/cmd/whoami/root/", p! {}), ("/src", "/src", p! {}), ("/src/", "/src/", p! {}), ( "/src/some/file.png", "/src/{*filepath}", p! { "filepath" => "some/file.png" }, ), ("/search/", "/search/", p! {}), ( "/search/actix", "/search/{query}", p! { "query" => "actix" }, ), ("/search/actix-web", "/search/actix-web", p! {}), ( "/search/someth!ng+in+ünìcodé", "/search/{query}", p! { "query" => "someth!ng+in+ünìcodé" }, ), ("/search/someth!ng+in+ünìcodé/", "", Err(())), ( "/user_rustacean", "/user_{name}", p! { "name" => "rustacean" }, ), ( "/user_rustacean/about", "/user_{name}/about", p! { "name" => "rustacean" }, ), ( "/files/js/inc/framework.js", "/files/{dir}/{*filepath}", p! { "dir" => "js", "filepath" => "inc/framework.js" }, ), ( "/info/gordon/public", "/info/{user}/public", p! { "user" => "gordon" }, ), ( "/info/gordon/project/rust", "/info/{user}/project/{project}", p! { "user" => "gordon", "project" => "rust" }, ), ( "/info/gordon/project/rustlang", "/info/{user}/project/rustlang", p! { "user" => "gordon" }, ), ("/aa/", "/", Err(())), ("/aa/aa", "/aa/{*xx}", p! { "xx" => "aa" }), ("/ab/ab", "/ab/{*xx}", p! { "xx" => "ab" }), ("/ab/hello-world", "/ab/hello{*xx}", p! { "xx" => "-world" }), ("/a", "/{cc}", p! { "cc" => "a" }), ("/all", "/{cc}", p! { "cc" => "all" }), ("/d", "/{cc}", p! { "cc" => "d" }), ("/ad", "/{cc}", p! { "cc" => "ad" }), ("/dd", "/{cc}", p! { "cc" => "dd" }), ("/dddaa", "/{cc}", p! { "cc" => "dddaa" }), ("/aa", "/{cc}", p! { "cc" => "aa" }), ("/aaa", "/{cc}", p! { "cc" => "aaa" }), ("/aaa/cc", "/{cc}/cc", p! { "cc" => "aaa" }), ("/ab", "/{cc}", p! { "cc" => "ab" }), ("/abb", "/{cc}", p! { "cc" => "abb" }), ("/abb/cc", "/{cc}/cc", p! { "cc" => "abb" }), ("/allxxxx", "/{cc}", p! { "cc" => "allxxxx" }), ("/alldd", "/{cc}", p! { "cc" => "alldd" }), ("/all/cc", "/{cc}/cc", p! { "cc" => "all" }), ("/a/cc", "/{cc}/cc", p! { "cc" => "a" }), ("/c1/d/e", "/c1/{dd}/e", p! { "dd" => "d" }), ("/c1/d/e1", "/c1/{dd}/e1", p! { "dd" => "d" }), ( "/c1/d/ee", "/{cc}/{dd}/ee", p! { "cc" => "c1", "dd" => "d" }, ), ("/cc/cc", "/{cc}/cc", p! { "cc" => "cc" }), ("/ccc/cc", "/{cc}/cc", p! { "cc" => "ccc" }), ("/deedwjfs/cc", "/{cc}/cc", p! { "cc" => "deedwjfs" }), ("/acllcc/cc", "/{cc}/cc", p! { "cc" => "acllcc" }), ("/get/test/abc/", "/get/test/abc/", p! {}), ("/get/te/abc/", "/get/{param}/abc/", p! { "param" => "te" }), ( "/get/testaa/abc/", "/get/{param}/abc/", p! { "param" => "testaa" }, ), ("/get/xx/abc/", "/get/{param}/abc/", p! { "param" => "xx" }), ("/get/tt/abc/", "/get/{param}/abc/", p! { "param" => "tt" }), ("/get/a/abc/", "/get/{param}/abc/", p! { "param" => "a" }), ("/get/t/abc/", "/get/{param}/abc/", p! { "param" => "t" }), ("/get/aa/abc/", "/get/{param}/abc/", p! { "param" => "aa" }), ( "/get/abas/abc/", "/get/{param}/abc/", p! { "param" => "abas" }, ), ( "/something/secondthing/test", "/something/secondthing/test", p! {}, ), ( "/something/abcdad/thirdthing", "/something/{paramname}/thirdthing", p! { "paramname" => "abcdad" }, ), ( "/something/secondthingaaaa/thirdthing", "/something/{paramname}/thirdthing", p! { "paramname" => "secondthingaaaa" }, ), ( "/something/se/thirdthing", "/something/{paramname}/thirdthing", p! { "paramname" => "se" }, ), ( "/something/s/thirdthing", "/something/{paramname}/thirdthing", p! { "paramname" => "s" }, ), ("/c/d/ee", "/{cc}/{dd}/ee", p! { "cc" => "c", "dd" => "d" }), ( "/c/d/e/ff", "/{cc}/{dd}/{ee}/ff", p! { "cc" => "c", "dd" => "d", "ee" => "e" }, ), ( "/c/d/e/f/gg", "/{cc}/{dd}/{ee}/{ff}/gg", p! { "cc" => "c", "dd" => "d", "ee" => "e", "ff" => "f" }, ), ( "/c/d/e/f/g/hh", "/{cc}/{dd}/{ee}/{ff}/{gg}/hh", p! { "cc" => "c", "dd" => "d", "ee" => "e", "ff" => "f", "gg" => "g" }, ), ( "/cc/dd/ee/ff/gg/hh", "/{cc}/{dd}/{ee}/{ff}/{gg}/hh", p! { "cc" => "cc", "dd" => "dd", "ee" => "ee", "ff" => "ff", "gg" => "gg" }, ), ("/get/abc", "/get/abc", p! {}), ("/get/a", "/get/{param}", p! { "param" => "a" }), ("/get/abz", "/get/{param}", p! { "param" => "abz" }), ("/get/12a", "/get/{param}", p! { "param" => "12a" }), ("/get/abcd", "/get/{param}", p! { "param" => "abcd" }), ("/get/abc/123abc", "/get/abc/123abc", p! {}), ("/get/abc/12", "/get/abc/{param}", p! { "param" => "12" }), ( "/get/abc/123ab", "/get/abc/{param}", p! { "param" => "123ab" }, ), ("/get/abc/xyz", "/get/abc/{param}", p! { "param" => "xyz" }), ( "/get/abc/123abcddxx", "/get/abc/{param}", p! { "param" => "123abcddxx" }, ), ("/get/abc/123abc/xxx8", "/get/abc/123abc/xxx8", p! {}), ( "/get/abc/123abc/x", "/get/abc/123abc/{param}", p! { "param" => "x" }, ), ( "/get/abc/123abc/xxx", "/get/abc/123abc/{param}", p! { "param" => "xxx" }, ), ( "/get/abc/123abc/abc", "/get/abc/123abc/{param}", p! { "param" => "abc" }, ), ( "/get/abc/123abc/xxx8xxas", "/get/abc/123abc/{param}", p! { "param" => "xxx8xxas" }, ), ( "/get/abc/123abc/xxx8/1234", "/get/abc/123abc/xxx8/1234", p! {}, ), ( "/get/abc/123abc/xxx8/1", "/get/abc/123abc/xxx8/{param}", p! { "param" => "1" }, ), ( "/get/abc/123abc/xxx8/123", "/get/abc/123abc/xxx8/{param}", p! { "param" => "123" }, ), ( "/get/abc/123abc/xxx8/78k", "/get/abc/123abc/xxx8/{param}", p! { "param" => "78k" }, ), ( "/get/abc/123abc/xxx8/1234xxxd", "/get/abc/123abc/xxx8/{param}", p! { "param" => "1234xxxd" }, ), ( "/get/abc/123abc/xxx8/1234/ffas", "/get/abc/123abc/xxx8/1234/ffas", p! {}, ), ( "/get/abc/123abc/xxx8/1234/f", "/get/abc/123abc/xxx8/1234/{param}", p! { "param" => "f" }, ), ( "/get/abc/123abc/xxx8/1234/ffa", "/get/abc/123abc/xxx8/1234/{param}", p! { "param" => "ffa" }, ), ( "/get/abc/123abc/xxx8/1234/kka", "/get/abc/123abc/xxx8/1234/{param}", p! { "param" => "kka" }, ), ( "/get/abc/123abc/xxx8/1234/ffas321", "/get/abc/123abc/xxx8/1234/{param}", p! { "param" => "ffas321" }, ), ( "/get/abc/123abc/xxx8/1234/kkdd/12c", "/get/abc/123abc/xxx8/1234/kkdd/12c", p! {}, ), ( "/get/abc/123abc/xxx8/1234/kkdd/1", "/get/abc/123abc/xxx8/1234/kkdd/{param}", p! { "param" => "1" }, ), ( "/get/abc/123abc/xxx8/1234/kkdd/12", "/get/abc/123abc/xxx8/1234/kkdd/{param}", p! { "param" => "12" }, ), ( "/get/abc/123abc/xxx8/1234/kkdd/12b", "/get/abc/123abc/xxx8/1234/kkdd/{param}", p! { "param" => "12b" }, ), ( "/get/abc/123abc/xxx8/1234/kkdd/34", "/get/abc/123abc/xxx8/1234/kkdd/{param}", p! { "param" => "34" }, ), ( "/get/abc/123abc/xxx8/1234/kkdd/12c2e3", "/get/abc/123abc/xxx8/1234/kkdd/{param}", p! { "param" => "12c2e3" }, ), ( "/get/abc/12/test", "/get/abc/{param}/test", p! { "param" => "12" }, ), ( "/get/abc/123abdd/test", "/get/abc/{param}/test", p! { "param" => "123abdd" }, ), ( "/get/abc/123abdddf/test", "/get/abc/{param}/test", p! { "param" => "123abdddf" }, ), ( "/get/abc/123ab/test", "/get/abc/{param}/test", p! { "param" => "123ab" }, ), ( "/get/abc/123abgg/test", "/get/abc/{param}/test", p! { "param" => "123abgg" }, ), ( "/get/abc/123abff/test", "/get/abc/{param}/test", p! { "param" => "123abff" }, ), ( "/get/abc/123abffff/test", "/get/abc/{param}/test", p! { "param" => "123abffff" }, ), ( "/get/abc/123abd/test", "/get/abc/123abd/{param}", p! { "param" => "test" }, ), ( "/get/abc/123abddd/test", "/get/abc/123abddd/{param}", p! { "param" => "test" }, ), ( "/get/abc/123/test22", "/get/abc/123/{param}", p! { "param" => "test22" }, ), ( "/get/abc/123abg/test", "/get/abc/123abg/{param}", p! { "param" => "test" }, ), ( "/get/abc/123abf/testss", "/get/abc/123abf/{param}", p! { "param" => "testss" }, ), ( "/get/abc/123abfff/te", "/get/abc/123abfff/{param}", p! { "param" => "te" }, ), ], } .run() } matchit-0.8.6/tests/merge.rs000064400000000000000000000035051046102023000141020ustar 00000000000000use matchit::{InsertError, Router}; #[test] fn merge_ok() { let mut root = Router::new(); assert!(root.insert("/foo", "foo").is_ok()); assert!(root.insert("/bar/{id}", "bar").is_ok()); let mut child = Router::new(); assert!(child.insert("/baz", "baz").is_ok()); assert!(child.insert("/xyz/{id}", "xyz").is_ok()); assert!(root.merge(child).is_ok()); assert_eq!(root.at("/foo").map(|m| *m.value), Ok("foo")); assert_eq!(root.at("/bar/1").map(|m| *m.value), Ok("bar")); assert_eq!(root.at("/baz").map(|m| *m.value), Ok("baz")); assert_eq!(root.at("/xyz/2").map(|m| *m.value), Ok("xyz")); } #[test] fn merge_conflict() { let mut root = Router::new(); assert!(root.insert("/foo", "foo").is_ok()); assert!(root.insert("/bar", "bar").is_ok()); let mut child = Router::new(); assert!(child.insert("/foo", "changed").is_ok()); assert!(child.insert("/bar", "changed").is_ok()); assert!(child.insert("/baz", "baz").is_ok()); let errors = root.merge(child).unwrap_err(); assert_eq!( errors.get(0), Some(&InsertError::Conflict { with: "/foo".into() }) ); assert_eq!( errors.get(1), Some(&InsertError::Conflict { with: "/bar".into() }) ); assert_eq!(root.at("/foo").map(|m| *m.value), Ok("foo")); assert_eq!(root.at("/bar").map(|m| *m.value), Ok("bar")); assert_eq!(root.at("/baz").map(|m| *m.value), Ok("baz")); } #[test] fn merge_nested() { let mut root = Router::new(); assert!(root.insert("/foo", "foo").is_ok()); let mut child = Router::new(); assert!(child.insert("/foo/bar", "bar").is_ok()); assert!(root.merge(child).is_ok()); assert_eq!(root.at("/foo").map(|m| *m.value), Ok("foo")); assert_eq!(root.at("/foo/bar").map(|m| *m.value), Ok("bar")); } matchit-0.8.6/tests/remove.rs000064400000000000000000000207221046102023000143000ustar 00000000000000use matchit::Router; struct RemoveTest { routes: Vec<&'static str>, ops: Vec<(Operation, &'static str, Option<&'static str>)>, remaining: Vec<&'static str>, } enum Operation { Insert, Remove, } use Operation::*; impl RemoveTest { fn run(self) { let mut router = Router::new(); for route in self.routes.iter() { assert_eq!(router.insert(*route, route.to_owned()), Ok(()), "{route}"); } for (op, route, expected) in self.ops.iter() { match op { Insert => { assert_eq!(router.insert(*route, route), Ok(()), "{route}") } Remove => { assert_eq!(router.remove(*route), *expected, "removing {route}",) } } } for route in self.remaining { assert!(matches!(router.at(route), Ok(_)), "remaining {route}"); } } } #[test] fn normalized() { RemoveTest { routes: vec![ "/x/{foo}/bar", "/x/{bar}/baz", "/{foo}/{baz}/bax", "/{foo}/{bar}/baz", "/{fod}/{baz}/{bax}/foo", "/{fod}/baz/bax/foo", "/{foo}/baz/bax", "/{bar}/{bay}/bay", "/s", "/s/s", "/s/s/s", "/s/s/s/s", "/s/s/{s}/x", "/s/s/{y}/d", ], ops: vec![ (Remove, "/x/{foo}/bar", Some("/x/{foo}/bar")), (Remove, "/x/{bar}/baz", Some("/x/{bar}/baz")), (Remove, "/{foo}/{baz}/bax", Some("/{foo}/{baz}/bax")), (Remove, "/{foo}/{bar}/baz", Some("/{foo}/{bar}/baz")), ( Remove, "/{fod}/{baz}/{bax}/foo", Some("/{fod}/{baz}/{bax}/foo"), ), (Remove, "/{fod}/baz/bax/foo", Some("/{fod}/baz/bax/foo")), (Remove, "/{foo}/baz/bax", Some("/{foo}/baz/bax")), (Remove, "/{bar}/{bay}/bay", Some("/{bar}/{bay}/bay")), (Remove, "/s", Some("/s")), (Remove, "/s/s", Some("/s/s")), (Remove, "/s/s/s", Some("/s/s/s")), (Remove, "/s/s/s/s", Some("/s/s/s/s")), (Remove, "/s/s/{s}/x", Some("/s/s/{s}/x")), (Remove, "/s/s/{y}/d", Some("/s/s/{y}/d")), ], remaining: vec![], } .run(); } #[test] fn test() { RemoveTest { routes: vec!["/home", "/home/{id}"], ops: vec![ (Remove, "/home", Some("/home")), (Remove, "/home", None), (Remove, "/home/{id}", Some("/home/{id}")), (Remove, "/home/{id}", None), ], remaining: vec![], } .run(); } #[test] fn blog() { RemoveTest { routes: vec![ "/{page}", "/posts/{year}/{month}/{post}", "/posts/{year}/{month}/index", "/posts/{year}/top", "/static/{*path}", "/favicon.ico", ], ops: vec![ (Remove, "/{page}", Some("/{page}")), ( Remove, "/posts/{year}/{month}/{post}", Some("/posts/{year}/{month}/{post}"), ), ( Remove, "/posts/{year}/{month}/index", Some("/posts/{year}/{month}/index"), ), (Remove, "/posts/{year}/top", Some("/posts/{year}/top")), (Remove, "/static/{*path}", Some("/static/{*path}")), (Remove, "/favicon.ico", Some("/favicon.ico")), ], remaining: vec![], } .run() } #[test] fn catchall() { RemoveTest { routes: vec!["/foo/{*catchall}", "/bar", "/bar/", "/bar/{*catchall}"], ops: vec![ (Remove, "/foo/{catchall}", None), (Remove, "/foo/{*catchall}", Some("/foo/{*catchall}")), (Remove, "/bar/", Some("/bar/")), (Insert, "/foo/*catchall", Some("/foo/*catchall")), (Remove, "/bar/{*catchall}", Some("/bar/{*catchall}")), ], remaining: vec!["/bar", "/foo/*catchall"], } .run(); } #[test] fn overlapping_routes() { RemoveTest { routes: vec![ "/home", "/home/{id}", "/users", "/users/{id}", "/users/{id}/posts", "/users/{id}/posts/{post_id}", "/articles", "/articles/{category}", "/articles/{category}/{id}", ], ops: vec![ (Remove, "/home", Some("/home")), (Insert, "/home", Some("/home")), (Remove, "/home/{id}", Some("/home/{id}")), (Insert, "/home/{id}", Some("/home/{id}")), (Remove, "/users", Some("/users")), (Insert, "/users", Some("/users")), (Remove, "/users/{id}", Some("/users/{id}")), (Insert, "/users/{id}", Some("/users/{id}")), (Remove, "/users/{id}/posts", Some("/users/{id}/posts")), (Insert, "/users/{id}/posts", Some("/users/{id}/posts")), ( Remove, "/users/{id}/posts/{post_id}", Some("/users/{id}/posts/{post_id}"), ), ( Insert, "/users/{id}/posts/{post_id}", Some("/users/{id}/posts/{post_id}"), ), (Remove, "/articles", Some("/articles")), (Insert, "/articles", Some("/articles")), (Remove, "/articles/{category}", Some("/articles/{category}")), (Insert, "/articles/{category}", Some("/articles/{category}")), ( Remove, "/articles/{category}/{id}", Some("/articles/{category}/{id}"), ), ( Insert, "/articles/{category}/{id}", Some("/articles/{category}/{id}"), ), ], remaining: vec![ "/home", "/home/{id}", "/users", "/users/{id}", "/users/{id}/posts", "/users/{id}/posts/{post_id}", "/articles", "/articles/{category}", "/articles/{category}/{id}", ], } .run(); } #[test] fn trailing_slash() { RemoveTest { routes: vec!["/{home}/", "/foo"], ops: vec![ (Remove, "/", None), (Remove, "/{home}", None), (Remove, "/foo/", None), (Remove, "/foo", Some("/foo")), (Remove, "/{home}", None), (Remove, "/{home}/", Some("/{home}/")), ], remaining: vec![], } .run(); } #[test] fn remove_root() { RemoveTest { routes: vec!["/"], ops: vec![(Remove, "/", Some("/"))], remaining: vec![], } .run(); } #[test] fn check_escaped_params() { RemoveTest { routes: vec![ "/foo/{id}", "/foo/{id}/bar", "/bar/{user}/{id}", "/bar/{user}/{id}/baz", "/baz/{product}/{user}/{id}", ], ops: vec![ (Remove, "/foo/{a}", None), (Remove, "/foo/{a}/bar", None), (Remove, "/bar/{a}/{b}", None), (Remove, "/bar/{a}/{b}/baz", None), (Remove, "/baz/{a}/{b}/{c}", None), ], remaining: vec![ "/foo/{id}", "/foo/{id}/bar", "/bar/{user}/{id}", "/bar/{user}/{id}/baz", "/baz/{product}/{user}/{id}", ], } .run(); } #[test] fn wildcard_suffix() { RemoveTest { routes: vec![ "/foo/{id}", "/foo/{id}/bar", "/foo/{id}bar", "/foo/{id}bar/baz", "/foo/{id}bar/baz/bax", "/bar/x{id}y", "/bar/x{id}y/", "/baz/x{id}y", "/baz/x{id}y/", ], ops: vec![ (Remove, "/foo/{id}", Some("/foo/{id}")), (Remove, "/foo/{id}bar", Some("/foo/{id}bar")), (Remove, "/foo/{id}bar/baz", Some("/foo/{id}bar/baz")), (Insert, "/foo/{id}bax", Some("/foo/{id}bax")), (Insert, "/foo/{id}bax/baz", Some("/foo/{id}bax/baz")), (Remove, "/foo/{id}bax/baz", Some("/foo/{id}bax/baz")), (Remove, "/bar/x{id}y", Some("/bar/x{id}y")), (Remove, "/baz/x{id}y/", Some("/baz/x{id}y/")), ], remaining: vec![ "/foo/{id}/bar", "/foo/{id}bar/baz/bax", "/foo/{id}bax", "/bar/x{id}y/", "/baz/x{id}y", ], } .run(); }