diff --git a/Cargo.lock b/Cargo.lock index 96cd56efb..9722accfd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,7 +112,7 @@ dependencies = [ "rand 0.9.1", "rcgen", "regex", - "rustls 0.23.25", + "rustls 0.23.27", "rustls-pemfile", "rustversion", "serde", @@ -251,9 +251,9 @@ dependencies = [ [[package]] name = "actix-server" -version = "2.5.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6398974fd4284f4768af07965701efbbb5fdc0616bff20cade1bb14b77675e24" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" dependencies = [ "actix-rt", "actix-service", @@ -296,7 +296,7 @@ dependencies = [ "rustls 0.20.9", "rustls 0.21.12", "rustls 0.22.4", - "rustls 0.23.25", + "rustls 0.23.27", "serde", "serde_json", "serde_urlencoded", @@ -330,7 +330,7 @@ dependencies = [ "tracing", "webpki-roots 0.22.6", "webpki-roots 0.25.4", - "webpki-roots 0.26.8", + "webpki-roots 0.26.11", ] [[package]] @@ -387,7 +387,7 @@ dependencies = [ "rcgen", "regex", "regex-lite", - "rustls 0.23.25", + "rustls 0.23.27", "rustls-pemfile", "serde", "serde_json", @@ -655,7 +655,7 @@ dependencies = [ "rustls 0.20.9", "rustls 0.21.12", "rustls 0.22.4", - "rustls 0.23.25", + "rustls 0.23.27", "rustls-pemfile", "serde", "serde_json", @@ -668,9 +668,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878" +checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" dependencies = [ "aws-lc-sys", "zeroize", @@ -678,9 +678,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f7720b74ed28ca77f90769a71fd8c637a0137f6fae4ae947e1050229cff57f" +checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" dependencies = [ "bindgen", "cc", @@ -691,9 +691,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -773,9 +773,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.2" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -816,9 +816,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.18" +version = "1.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" +checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" dependencies = [ "jobserver", "libc", @@ -890,18 +890,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.35" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" +checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.35" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" +checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" dependencies = [ "anstyle", "clap_lex", @@ -1153,9 +1153,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "deranged" @@ -1211,9 +1211,9 @@ dependencies = [ [[package]] name = "divan" -version = "0.1.18" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "009c56317fe2bd3b5eebe3aa888828c62ed1b085d26c1ef2079a60369795765e" +checksum = "a405457ec78b8fe08b0e32b4a3570ab5dff6dd16eb9e76a5ee0a9d9cbd898933" dependencies = [ "cfg-if", "clap", @@ -1225,9 +1225,9 @@ dependencies = [ [[package]] name = "divan-macros" -version = "0.1.18" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4de9827ae754db91aedec0277381f5a2d8e2f801564c8d774acfe1ab1b045e" +checksum = "9556bc800956545d6420a640173e5ba7dfa82f38d3ea5a167eb555bc69ac3323" dependencies = [ "proc-macro2", "quote", @@ -1463,9 +1463,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -1474,9 +1474,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", @@ -1537,9 +1537,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" [[package]] name = "heck" @@ -1555,9 +1555,9 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hermit-abi" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" +checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" [[package]] name = "hkdf" @@ -1586,17 +1586,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "hostname" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" -dependencies = [ - "cfg-if", - "libc", - "windows-link", -] - [[package]] name = "http" version = "0.2.12" @@ -1639,21 +1628,22 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -1662,31 +1652,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -1694,67 +1664,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" +checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -1784,9 +1741,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -1851,7 +1808,7 @@ version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi 0.5.0", + "hermit-abi 0.5.1", "libc", "windows-sys 0.59.0", ] @@ -1888,9 +1845,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.6" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f33145a5cbea837164362c7bd596106eb7c5198f97d1ba6f6ebb3223952e488" +checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806" dependencies = [ "jiff-static", "log", @@ -1901,9 +1858,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.6" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43ce13c40ec6956157a3635d97a1ee2df323b263f09ea14165131289cb0f5c19" +checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48" dependencies = [ "proc-macro2", "quote", @@ -1916,7 +1873,7 @@ version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "libc", ] @@ -1950,18 +1907,18 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.53.0", ] [[package]] @@ -1984,9 +1941,9 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "local-channel" @@ -2192,9 +2149,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.107" +version = "0.9.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" +checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" dependencies = [ "cc", "libc", @@ -2334,6 +2291,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2430,7 +2396,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -2439,7 +2405,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", ] [[package]] @@ -2477,9 +2443,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ "bitflags 2.9.0", ] @@ -2521,12 +2487,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "resolv-conf" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48375394603e3dd4b2d64371f7148fd8c7baa2680e28741f2cb8d23b59e3d4c4" -dependencies = [ - "hostname", -] +checksum = "fc7c8f7f733062b66dc1c63f9db168ac0b97a9210e247fa90fdc9ad08f51b302" [[package]] name = "ring" @@ -2551,7 +2514,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.16", "libc", "untrusted 0.9.0", "windows-sys 0.52.0", @@ -2584,9 +2547,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ "bitflags 2.9.0", "errno", @@ -2635,15 +2598,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.25" +version = "0.23.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ "aws-lc-rs", "log", "once_cell", "rustls-pki-types", - "rustls-webpki 0.103.1", + "rustls-webpki 0.103.3", "subtle", "zeroize", ] @@ -2672,9 +2635,12 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-webpki" @@ -2699,9 +2665,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.1" +version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ "aws-lc-rs", "ring 0.17.14", @@ -2859,9 +2825,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -2876,9 +2842,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -2956,9 +2922,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.100" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -2967,9 +2933,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", @@ -2984,14 +2950,14 @@ checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" [[package]] name = "tempfile" -version = "3.19.1" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", - "rustix 1.0.5", + "rustix 1.0.7", "windows-sys 0.59.0", ] @@ -3010,7 +2976,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ - "rustix 1.0.5", + "rustix 1.0.7", "windows-sys 0.59.0", ] @@ -3067,9 +3033,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -3102,9 +3068,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.44.2" +version = "1.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" dependencies = [ "backtrace", "bytes", @@ -3178,7 +3144,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.23.25", + "rustls 0.23.27", "tokio", ] @@ -3210,9 +3176,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", @@ -3223,9 +3189,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.20" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" dependencies = [ "serde", "serde_spanned", @@ -3235,26 +3201,33 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", + "toml_write", "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + [[package]] name = "tracing" version = "0.1.41" @@ -3335,9 +3308,9 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ae08be68c056db96f0e6c6dd820727cca756ced9e1f4cc7fdd20e2a55e23898" +checksum = "1c9bf9513a2f4aeef5fdac8677d7d349c79fdbcc03b9c86da6e9d254f1e43be2" dependencies = [ "glob", "serde", @@ -3420,12 +3393,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -3576,9 +3543,18 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.8" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.0", +] + +[[package]] +name = "webpki-roots" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" dependencies = [ "rustls-pki-types", ] @@ -3632,12 +3608,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-link" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" - [[package]] name = "windows-sys" version = "0.48.0" @@ -3689,13 +3659,29 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "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-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3708,6 +3694,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3720,6 +3712,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3732,12 +3730,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[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_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3750,6 +3760,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -3762,6 +3778,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -3774,6 +3796,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -3787,10 +3815,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winnow" -version = "0.7.6" +name = "windows_x86_64_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] @@ -3814,17 +3848,11 @@ dependencies = [ "bitflags 2.9.0", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "yasna" @@ -3837,9 +3865,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -3849,9 +3877,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", @@ -3861,18 +3889,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", @@ -3907,10 +3935,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] -name = "zerovec" -version = "0.10.4" +name = "zerotrie" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -3919,9 +3958,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 4ae5a09d3..f45604b79 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -2,7 +2,7 @@ ## Unreleased -- Add `resources-introspection` feature for retrieving configured route paths and HTTP methods. +- Add `experimental-introspection` feature for retrieving configured route paths and HTTP methods. ## 4.10.2 diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 76ecffd38..39b3f8e62 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -129,7 +129,7 @@ compat = [ compat-routing-macros-force-pub = ["actix-web-codegen?/compat-routing-macros-force-pub"] # Enabling the retrieval of metadata for initialized resources, including path and HTTP method. -resources-introspection = [] +experimental-introspection = [] [dependencies] actix-codec = "0.5" diff --git a/actix-web/examples/introspection.rs b/actix-web/examples/introspection.rs new file mode 100644 index 000000000..1de9cfec6 --- /dev/null +++ b/actix-web/examples/introspection.rs @@ -0,0 +1,206 @@ +// NOTE: This is a work-in-progress example being used to test the new implementation +// of the experimental introspection feature. +// `cargo run --features experimental-introspection --example introspection` + +use actix_web::{dev::Service, guard, web, App, HttpResponse, HttpServer, Responder}; +use serde::Deserialize; +// Custom guard that checks if the Content-Type header is present. +struct ContentTypeGuard; + +impl guard::Guard for ContentTypeGuard { + fn check(&self, req: &guard::GuardContext<'_>) -> bool { + req.head() + .headers() + .contains_key(actix_web::http::header::CONTENT_TYPE) + } +} + +// Data structure for endpoints that receive JSON. +#[derive(Deserialize)] +struct UserInfo { + username: String, + age: u8, +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + let server = HttpServer::new(|| { + let app = App::new() + .service( + web::scope("/api") + .service( + web::scope("/v1") + // GET /api/v1/item/{id}: returns the item id from the path. + .service(get_item) + // POST /api/v1/info: accepts JSON and returns user info. + .service(post_user_info) + // /api/v1/guarded: only accessible if Content-Type header is present. + .route( + "/guarded", + web::route().guard(ContentTypeGuard).to(guarded_handler), + ), + ) + // API scope /api/v2: additional endpoint. + .service(web::scope("/v2").route("/hello", web::get().to(hello_v2))), + ) + // Scope /v1 outside /api: exposes only GET /v1/item/{id}. + .service(web::scope("/v1").service(get_item)) + // Scope /admin: admin endpoints with different HTTP methods. + .service( + web::scope("/admin") + .route("/dashboard", web::get().to(admin_dashboard)) + // Single route handling multiple methods using separate handlers. + .service( + web::resource("/settings") + .route(web::get().to(get_settings)) + .route(web::post().to(update_settings)), + ), + ) + // Root resource: supports GET and POST on "/". + .service( + web::resource("/") + .route(web::get().to(root_index)) + .route(web::post().to(root_index)), + ) + // Additional endpoints configured in a separate function. + .configure(extra_endpoints) + // Endpoint that rejects GET on /not_guard (allows other methods). + .route( + "/not_guard", + web::route() + .guard(guard::Not(guard::Get())) + .to(HttpResponse::MethodNotAllowed), + ) + // Endpoint that requires GET, content-type: plain/text header, and/or POST on /all_guard. + .route( + "/all_guard", + web::route() + .guard( + guard::All(guard::Get()) + .and(guard::Header("content-type", "plain/text")) + .and(guard::Any(guard::Post())), + ) + .to(HttpResponse::MethodNotAllowed), + ); + + /*#[cfg(feature = "experimental-introspection")] + { + actix_web::introspection::introspect(); + }*/ + // TODO: Enable introspection without the feature flag. + app + }) + .workers(5) + .bind("127.0.0.1:8080")?; + + server.run().await +} + +// GET /api/v1/item/{id} and GET /v1/item/{id} +// Returns a message with the provided id. +#[actix_web::get("/item/{id:\\d+}")] +async fn get_item(path: web::Path) -> impl Responder { + let id = path.into_inner(); + HttpResponse::Ok().body(format!("Requested item with id: {}", id)) +} + +// POST /api/v1/info +// Expects JSON and responds with the received user info. +#[actix_web::post("/info")] +async fn post_user_info(info: web::Json) -> impl Responder { + HttpResponse::Ok().json(format!( + "User {} with age {} received", + info.username, info.age + )) +} + +// /api/v1/guarded +// Uses a custom guard that requires the Content-Type header. +async fn guarded_handler() -> impl Responder { + HttpResponse::Ok().body("Passed the Content-Type guard!") +} + +// GET /api/v2/hello +// Simple greeting endpoint. +async fn hello_v2() -> impl Responder { + HttpResponse::Ok().body("Hello from API v2!") +} + +// GET /admin/dashboard +// Returns a message for the admin dashboard. +async fn admin_dashboard() -> impl Responder { + HttpResponse::Ok().body("Welcome to the Admin Dashboard!") +} + +// GET /admin/settings +// Returns the current admin settings. +async fn get_settings() -> impl Responder { + HttpResponse::Ok().body("Current settings: ...") +} + +// POST /admin/settings +// Updates the admin settings. +async fn update_settings() -> impl Responder { + HttpResponse::Ok().body("Settings have been updated!") +} + +// GET and POST on / +// Generic root endpoint. +async fn root_index() -> impl Responder { + HttpResponse::Ok().body("Welcome to the Root Endpoint!") +} + +// Additional endpoints configured in a separate function. +fn extra_endpoints(cfg: &mut web::ServiceConfig) { + cfg.service( + web::scope("/extra") + // GET /extra/ping: simple ping endpoint. + .route( + "/ping", + web::get().to(|| async { HttpResponse::Ok().body("pong") }), + ) + // /extra/multi: resource that supports GET and POST. + .service( + web::resource("/multi") + .route( + web::get().to(|| async { + HttpResponse::Ok().body("GET response from /extra/multi") + }), + ) + .route(web::post().to(|| async { + HttpResponse::Ok().body("POST response from /extra/multi") + })), + ) + // /extra/{entities_id}/secure: nested scope with GET and POST, prints the received id. + .service( + web::scope("{entities_id:\\d+}") + .service( + web::scope("/secure") + .route( + "", + web::get().to(|| async { + HttpResponse::Ok().body("GET response from /extra/secure") + }), + ) + .route( + "", + web::post().to(|| async { + HttpResponse::Ok().body("POST response from /extra/secure") + }), + ), + ) + // Middleware that prints the id received in the route. + .wrap_fn(|req, srv| { + println!( + "Request to /extra/secure with id: {}", + req.match_info().get("entities_id").unwrap() + ); + let fut = srv.call(req); + async move { + let res = fut.await?; + Ok(res) + } + }), + ), + ); +} diff --git a/actix-web/src/app_service.rs b/actix-web/src/app_service.rs index a935c1354..dd76c3d2d 100644 --- a/actix-web/src/app_service.rs +++ b/actix-web/src/app_service.rs @@ -82,9 +82,6 @@ where let (config, services) = config.into_services(); - #[cfg(feature = "resources-introspection")] - let mut rdef_methods: Vec<(String, Vec)> = Vec::new(); - // complete pipeline creation. *self.factory_ref.borrow_mut() = Some(AppRoutingFactory { default, @@ -92,24 +89,35 @@ where .into_iter() .map(|(mut rdef, srv, guards, nested)| { rmap.add(&mut rdef, nested); - - #[cfg(feature = "resources-introspection")] + #[cfg(feature = "experimental-introspection")] { - let http_methods: Vec = - guards.as_ref().map_or_else(Vec::new, |g| { - g.iter() - .flat_map(|g| { - crate::guard::HttpMethodsExtractor::extract_http_methods( - &**g, - ) - }) - .collect::>() - }); - - rdef_methods - .push((rdef.pattern().unwrap_or_default().to_string(), http_methods)); + use std::borrow::Borrow; + let pat = rdef.pattern().unwrap_or("").to_string(); + let mut methods = Vec::new(); + let mut guard_names = Vec::new(); + if let Some(gs) = guards.borrow().as_ref() { + for g in gs.iter() { + let name = g.name().to_string(); + if !guard_names.contains(&name) { + guard_names.push(name.clone()); + } + if let Some(details) = g.details() { + for d in details { + if let crate::guard::GuardDetail::HttpMethods(v) = d { + for s in v { + if let Ok(m) = s.parse() { + if !methods.contains(&m) { + methods.push(m); + } + } + } + } + } + } + } + } + crate::introspection::register_pattern_detail(pat, methods, guard_names); } - (rdef, srv, RefCell::new(guards)) }) .collect::>() @@ -126,11 +134,6 @@ where let rmap = Rc::new(rmap); ResourceMap::finish(&rmap); - #[cfg(feature = "resources-introspection")] - { - crate::introspection::process_introspection(Rc::clone(&rmap), rdef_methods); - } - // construct all async data factory futures let factory_futs = join_all(self.async_data_factories.iter().map(|f| f())); @@ -156,6 +159,11 @@ where factory.create(&mut app_data); } + #[cfg(feature = "experimental-introspection")] + { + crate::introspection::register_rmap(&rmap); + } + Ok(AppInitService { service, app_data: Rc::new(app_data), diff --git a/actix-web/src/guard/mod.rs b/actix-web/src/guard/mod.rs index acdd86e81..97b0f80b6 100644 --- a/actix-web/src/guard/mod.rs +++ b/actix-web/src/guard/mod.rs @@ -397,35 +397,6 @@ impl Guard for MethodGuard { } } -#[cfg(feature = "resources-introspection")] -pub trait HttpMethodsExtractor { - fn extract_http_methods(&self) -> Vec; -} - -#[cfg(feature = "resources-introspection")] -impl HttpMethodsExtractor for dyn Guard { - fn extract_http_methods(&self) -> Vec { - let methods: Vec = self - .details() - .unwrap_or_default() - .iter() - .flat_map(|detail| { - if let GuardDetail::HttpMethods(methods) = detail { - methods.clone() - } else { - vec!["UNKNOWN".to_string()] - } - }) - .collect(); - - if methods.is_empty() { - vec!["UNKNOWN".to_string()] - } else { - methods - } - } -} - macro_rules! method_guard { ($method_fn:ident, $method_const:ident) => { #[doc = concat!("Creates a guard that matches the `", stringify!($method_const), "` request method.")] diff --git a/actix-web/src/introspection.rs b/actix-web/src/introspection.rs index 0059ed215..71e61eccd 100644 --- a/actix-web/src/introspection.rs +++ b/actix-web/src/introspection.rs @@ -1,494 +1,172 @@ use std::{ - rc::Rc, - sync::{OnceLock, RwLock}, + collections::HashMap, + sync::{ + atomic::{AtomicBool, Ordering}, + Mutex, OnceLock, + }, thread, }; -use crate::rmap::ResourceMap; +use crate::{http::Method, rmap::ResourceMap}; -/// Represents an HTTP resource registered for introspection. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ResourceIntrospection { - /// HTTP method (e.g., "GET"). - pub method: String, - /// Route path (e.g., "/api/v1/test"). - pub path: String, -} - -/// A global registry of listed resources for introspection. -/// Only the designated thread can modify it. -static RESOURCE_REGISTRY: RwLock> = RwLock::new(Vec::new()); - -/// Stores the thread ID of the designated thread (the first to call `process_introspection`). -/// Any other thread will immediately return without updating the global registry. +static REGISTRY: OnceLock> = OnceLock::new(); +static DETAIL_REGISTRY: OnceLock>> = OnceLock::new(); static DESIGNATED_THREAD: OnceLock = OnceLock::new(); +static IS_INITIALIZED: AtomicBool = AtomicBool::new(false); -/// Inserts a resource into the global registry, avoiding duplicates. -pub fn register_resource(resource: ResourceIntrospection) { - let mut global = RESOURCE_REGISTRY.write().unwrap(); - if !global.iter().any(|r| r == &resource) { - global.push(resource); - } +pub fn initialize_registry() { + REGISTRY.get_or_init(|| Mutex::new(IntrospectionNode::new(ResourceType::App, "".into()))); } -/// Completes (updates) partial routes in the global registry whose path contains `marker`, -/// by applying the specified `prefix`. -pub fn complete_partial_routes_with_marker(marker: &str, prefix: &str) { - let mut global = RESOURCE_REGISTRY.write().unwrap(); +pub fn get_registry() -> &'static Mutex { + REGISTRY.get().expect("Registry not initialized") +} - let mut updated = Vec::new(); - let mut remaining = Vec::new(); +pub fn initialize_detail_registry() { + DETAIL_REGISTRY.get_or_init(|| Mutex::new(HashMap::new())); +} - // Move all items out of the current registry. - for resource in global.drain(..) { - if resource.path.contains(marker) { - // Build the full path by applying the prefix if needed. - let full_path = if prefix.is_empty() { - resource.path.clone() - } else if prefix.ends_with('/') || resource.path.starts_with('/') { - format!("{}{}", prefix, resource.path) - } else { - format!("{}/{}", prefix, resource.path) - }; +pub fn get_detail_registry() -> &'static Mutex> { + DETAIL_REGISTRY + .get() + .expect("Detail registry not initialized") +} - let completed = ResourceIntrospection { - method: resource.method, - path: full_path, - }; +#[derive(Clone)] +pub struct RouteDetail { + methods: Vec, + guards: Vec, +} - // Add to `updated` if it's not already in there. - if !updated.iter().any(|r| r == &completed) { - updated.push(completed); - } +#[derive(Debug, Clone, Copy)] +pub enum ResourceType { + App, + Scope, + Resource, +} + +#[derive(Debug, Clone)] +pub struct IntrospectionNode { + pub kind: ResourceType, + pub pattern: String, + pub methods: Vec, + pub guards: Vec, + pub children: Vec, +} + +impl IntrospectionNode { + pub fn new(kind: ResourceType, pattern: String) -> Self { + IntrospectionNode { + kind, + pattern, + methods: Vec::new(), + guards: Vec::new(), + children: Vec::new(), + } + } + + pub fn display(&self, indent: usize, parent_path: &str) { + let full_path = if parent_path.is_empty() { + self.pattern.clone() } else { - // Keep this resource as-is. - remaining.push(resource); - } - } + format!( + "{}/{}", + parent_path.trim_end_matches('/'), + self.pattern.trim_start_matches('/') + ) + }; - // Merge updated items back with the remaining ones. - remaining.extend(updated); - *global = remaining; -} - -/// Returns a **copy** of the global registry (safe to call from any thread). -pub fn get_registered_resources() -> Vec { - RESOURCE_REGISTRY.read().unwrap().clone() -} - -/// Processes introspection data for routes and methods. -/// Only the **first thread** that calls this function (the "designated" one) may update -/// the global resource registry. Any other thread will immediately return without updating it. -/// -/// # Parameters -/// - `rmap`: A resource map convertible to a vector of route strings. -/// - `rdef_methods`: A vector of `(sub_path, [methods])`. -/// - A tuple with an **empty** methods vector is treated as a "marker" (a partial route) -/// for which we try to deduce a prefix by finding `sub_path` in a route, then calling -/// `complete_partial_routes_with_marker`. -/// - A tuple with one or more methods registers a resource with `register_resource`. -pub fn process_introspection(rmap: Rc, rdef_methods: Vec<(String, Vec)>) { - // Determine the designated thread: if none is set yet, assign the current thread's ID. - // This ensures that the first thread to call this function becomes the designated thread. - let current_id = thread::current().id(); - DESIGNATED_THREAD.get_or_init(|| current_id); - - // If the current thread is not the designated one, return immediately. - // This ensures that only the designated thread updates the global registry, - // avoiding any interleaving or inconsistent updates from other threads. - if *DESIGNATED_THREAD.get().unwrap() != current_id { - return; - } - - let rmap_vec = rmap.to_vec(); - - // If there is no data, nothing to process. - // Avoid unnecessary work. - if rmap_vec.is_empty() && rdef_methods.is_empty() { - return; - } - - // Keep track of the deduced prefix for partial routes. - let mut deduced_prefix: Option = None; - - // 1. Handle "marker" entries (where methods is empty). - for (sub_path, http_methods) in &rdef_methods { - if http_methods.is_empty() { - // Find any route that contains sub_path and use it to deduce a prefix. - if let Some(route) = rmap_vec.iter().find(|r| r.contains(sub_path)) { - if let Some(pos) = route.find(sub_path) { - let prefix = &route[..pos]; - deduced_prefix = Some(prefix.to_string()); - // Complete partial routes in the global registry using this prefix. - complete_partial_routes_with_marker(sub_path, prefix); - } - } - } - } - - // 2. Handle endpoint entries (where methods is non-empty). - for (sub_path, http_methods) in &rdef_methods { - if !http_methods.is_empty() { - // Identify candidate routes that end with sub_path (or exactly match "/" if sub_path == "/"). - let candidates: Vec<&String> = if sub_path == "/" { - rmap_vec.iter().filter(|r| r.as_str() == "/").collect() + if !self.methods.is_empty() || !self.guards.is_empty() { + let methods = if self.methods.is_empty() { + "".to_string() } else { - rmap_vec.iter().filter(|r| r.ends_with(sub_path)).collect() + format!(" Methods: {:?}", self.methods) + }; + let guards = if self.guards.is_empty() { + "".to_string() + } else { + format!(" Guards: {:?}", self.guards) }; - // If we found any candidates, pick the best match. - if !candidates.is_empty() { - let chosen = if let Some(prefix) = &deduced_prefix { - if !prefix.is_empty() { - candidates - .iter() - .find(|&&r| r.starts_with(prefix)) - .cloned() - .or_else(|| candidates.iter().min_by_key(|&&r| r.len()).cloned()) - } else { - candidates.iter().min_by_key(|&&r| r.len()).cloned() - } - } else { - candidates.iter().min_by_key(|&&r| r.len()).cloned() - }; + println!("{}{}{}{}", " ".repeat(indent), full_path, methods, guards); + } - if let Some(full_route) = chosen { - // Register the endpoint in the global resource registry. - register_resource(ResourceIntrospection { - method: http_methods.join(","), - path: full_route.clone(), - }); + for child in &self.children { + child.display(indent, &full_path); + } + } +} + +fn build_tree(node: &mut IntrospectionNode, rmap: &ResourceMap) { + initialize_detail_registry(); + let detail_registry = get_detail_registry(); + if let Some(ref children) = rmap.nodes { + for child_rc in children { + let child = child_rc; + let pat = child.pattern.pattern().unwrap_or("").to_string(); + let kind = if child.nodes.is_some() { + ResourceType::Scope + } else { + ResourceType::Resource + }; + let mut new_node = IntrospectionNode::new(kind, pat.clone()); + + if let ResourceType::Resource = new_node.kind { + if let Some(d) = detail_registry.lock().unwrap().get(&pat) { + new_node.methods = d.methods.clone(); + new_node.guards = d.guards.clone(); } } + + build_tree(&mut new_node, child); + node.children.push(new_node); } } } -#[cfg(test)] -mod tests { - use std::{num::NonZeroUsize, rc::Rc}; +fn is_designated_thread() -> bool { + let current_id = thread::current().id(); + DESIGNATED_THREAD.get_or_init(|| { + IS_INITIALIZED.store(true, Ordering::SeqCst); + current_id // Assign the first thread that calls this function + }); - use actix_router::ResourceDef; - use tokio::sync::oneshot; + *DESIGNATED_THREAD.get().unwrap() == current_id +} - use super::*; - use crate::rmap::ResourceMap; - - /// Helper function to create a ResourceMap from a list of route strings. - /// It creates a root ResourceMap with an empty prefix and adds each route as a leaf. - fn create_resource_map(routes: Vec<&str>) -> Rc { - // Create a root node with an empty prefix. - let mut root = ResourceMap::new(ResourceDef::root_prefix("")); - // For each route, create a ResourceDef and add it as a leaf (nested = None). - for route in routes { - let mut def = ResourceDef::new(route); - root.add(&mut def, None); - } - Rc::new(root) +pub fn register_rmap(rmap: &ResourceMap) { + if !is_designated_thread() { + return; } - // Helper function to run the full introspection flow. - // It processes introspection data for multiple blocks, each with a different set of routes and methods. - fn run_full_introspection_flow() { - // Block 1: - // rmap_vec: ["/item/{id}"] - // rdef_methods: [] - process_introspection(create_resource_map(vec!["/item/{id}"]), vec![]); + initialize_registry(); + let mut root = IntrospectionNode::new(ResourceType::App, "".into()); + build_tree(&mut root, rmap); + *get_registry().lock().unwrap() = root; - // Block 2: - // rmap_vec: ["/info"] - // rdef_methods: [] - process_introspection(create_resource_map(vec!["/info"]), vec![]); + // WIP. Display the introspection tree + let reg = get_registry().lock().unwrap(); + reg.display(0, ""); +} - // Block 3: - // rmap_vec: ["/guarded"] - // rdef_methods: [] - process_introspection(create_resource_map(vec!["/guarded"]), vec![]); - - // Block 4: - // rmap_vec: ["/v1/item/{id}", "/v1/info", "/v1/guarded"] - // rdef_methods: [("/item/{id}", ["GET"]), ("/info", ["POST"]), ("/guarded", ["UNKNOWN"])] - process_introspection( - create_resource_map(vec!["/v1/item/{id}", "/v1/info", "/v1/guarded"]), - vec![ - ("/item/{id}".to_string(), vec!["GET".to_string()]), - ("/info".to_string(), vec!["POST".to_string()]), - ("/guarded".to_string(), vec!["UNKNOWN".to_string()]), - ], - ); - - // Block 5: - // rmap_vec: ["/hello"] - // rdef_methods: [] - process_introspection(create_resource_map(vec!["/hello"]), vec![]); - - // Block 6: - // rmap_vec: ["/v2/hello"] - // rdef_methods: [("/hello", ["GET"])] - process_introspection( - create_resource_map(vec!["/v2/hello"]), - vec![("/hello".to_string(), vec!["GET".to_string()])], - ); - - // Block 7: - // rmap_vec: ["/api/v1/item/{id}", "/api/v1/info", "/api/v1/guarded", "/api/v2/hello"] - // rdef_methods: [("/v1", []), ("/v2", [])] - process_introspection( - create_resource_map(vec![ - "/api/v1/item/{id}", - "/api/v1/info", - "/api/v1/guarded", - "/api/v2/hello", - ]), - vec![("/v1".to_string(), vec![]), ("/v2".to_string(), vec![])], - ); - - // Block 8: - // rmap_vec: ["/dashboard"] - // rdef_methods: [] - process_introspection(create_resource_map(vec!["/dashboard"]), vec![]); - - // Block 9: - // rmap_vec: ["/settings"] - // rdef_methods: [("/settings", ["GET"]), ("/settings", ["POST"])] - process_introspection( - create_resource_map(vec!["/settings"]), - vec![ - ("/settings".to_string(), vec!["GET".to_string()]), - ("/settings".to_string(), vec!["POST".to_string()]), - ], - ); - - // Block 10: - // rmap_vec: ["/admin/dashboard", "/admin/settings"] - // rdef_methods: [("/dashboard", ["GET"]), ("/settings", [])] - process_introspection( - create_resource_map(vec!["/admin/dashboard", "/admin/settings"]), - vec![ - ("/dashboard".to_string(), vec!["GET".to_string()]), - ("/settings".to_string(), vec![]), - ], - ); - - // Block 11: - // rmap_vec: ["/"] - // rdef_methods: [("/", ["GET"]), ("/", ["POST"])] - process_introspection( - create_resource_map(vec!["/"]), - vec![ - ("/".to_string(), vec!["GET".to_string()]), - ("/".to_string(), vec!["POST".to_string()]), - ], - ); - - // Block 12: - // rmap_vec: ["/ping"] - // rdef_methods: [] - process_introspection(create_resource_map(vec!["/ping"]), vec![]); - - // Block 13: - // rmap_vec: ["/multi"] - // rdef_methods: [("/multi", ["GET"]), ("/multi", ["POST"])] - process_introspection( - create_resource_map(vec!["/multi"]), - vec![ - ("/multi".to_string(), vec!["GET".to_string()]), - ("/multi".to_string(), vec!["POST".to_string()]), - ], - ); - - // Block 14: - // rmap_vec: ["/extra/ping", "/extra/multi"] - // rdef_methods: [("/ping", ["GET"]), ("/multi", [])] - process_introspection( - create_resource_map(vec!["/extra/ping", "/extra/multi"]), - vec![ - ("/ping".to_string(), vec!["GET".to_string()]), - ("/multi".to_string(), vec![]), - ], - ); - - // Block 15: - // rmap_vec: ["/other_guard"] - // rdef_methods: [] - process_introspection(create_resource_map(vec!["/other_guard"]), vec![]); - - // Block 16: - // rmap_vec: ["/all_guard"] - // rdef_methods: [] - process_introspection(create_resource_map(vec!["/all_guard"]), vec![]); - - // Block 17: - // rmap_vec: ["/api/v1/item/{id}", "/api/v1/info", "/api/v1/guarded", "/api/v2/hello", - // "/admin/dashboard", "/admin/settings", "/", "/extra/ping", "/extra/multi", - // "/other_guard", "/all_guard"] - // rdef_methods: [("/api", []), ("/admin", []), ("/", []), ("/extra", []), - // ("/other_guard", ["UNKNOWN"]), ("/all_guard", ["GET", "UNKNOWN", "POST"])] - process_introspection( - create_resource_map(vec![ - "/api/v1/item/{id}", - "/api/v1/info", - "/api/v1/guarded", - "/api/v2/hello", - "/admin/dashboard", - "/admin/settings", - "/", - "/extra/ping", - "/extra/multi", - "/other_guard", - "/all_guard", - ]), - vec![ - ("/api".to_string(), vec![]), - ("/admin".to_string(), vec![]), - ("/".to_string(), vec![]), - ("/extra".to_string(), vec![]), - ("/other_guard".to_string(), vec!["UNKNOWN".to_string()]), - ( - "/all_guard".to_string(), - vec!["GET".to_string(), "UNKNOWN".to_string(), "POST".to_string()], - ), - ], - ); - } - - /// This test spawns multiple tasks that run the full introspection flow concurrently. - /// Only the designated task (the first one to call process_introspection) updates the global registry, - /// ensuring that the internal order remains consistent. Finally, we verify that get_registered_resources() - /// returns the expected set of listed resources. - /// Using a dedicated arbiter for each task ensures that the global registry is thread-safe. - #[actix_rt::test] - async fn test_introspection() { - // Number of tasks to spawn. - const NUM_TASKS: usize = 4; - let mut completion_receivers = Vec::with_capacity(NUM_TASKS); - - // Check that the registry is initially empty. - let registered_resources = get_registered_resources(); - - assert_eq!( - registered_resources.len(), - 0, - "Expected 0 registered resources, found: {:?}", - registered_resources - ); - - // Determine parallelism and max blocking threads. - let parallelism = std::thread::available_parallelism().map_or(2, NonZeroUsize::get); - let max_blocking_threads = std::cmp::max(512 / parallelism, 1); - - // Spawn tasks on the arbiter. Each task runs the full introspection flow and then signals completion. - for _ in 0..NUM_TASKS { - let (tx, rx) = oneshot::channel(); - - #[cfg(all(target_os = "linux", feature = "experimental-io-uring"))] - let arbiter = { - // TODO: pass max blocking thread config when tokio-uring enable configuration - // on building runtime. - let _ = max_blocking_threads; - actix_rt::Arbiter::new() - }; - - #[cfg(not(all(target_os = "linux", feature = "experimental-io-uring")))] - let arbiter = actix_rt::Arbiter::with_tokio_rt(move || { - // Create an Arbiter with a dedicated Tokio runtime. - tokio::runtime::Builder::new_current_thread() - .enable_all() - .max_blocking_threads(max_blocking_threads) - .build() - .unwrap() - }); - - // Spawn the task on the arbiter. - arbiter.spawn(async move { - run_full_introspection_flow(); - // Signal that this task has finished. - let _ = tx.send(()); - }); - completion_receivers.push(rx); - } - - // Wait for all spawned tasks to complete. - for rx in completion_receivers { - let _ = rx.await; - } - - // After all blocks, we expect the final registry to contain 14 entries. - let registered_resources = get_registered_resources(); - - assert_eq!( - registered_resources.len(), - 14, - "Expected 14 registered resources, found: {:?}", - registered_resources - ); - - // List of expected resources - let expected_resources = vec![ - ResourceIntrospection { - method: "GET".to_string(), - path: "/api/v1/item/{id}".to_string(), - }, - ResourceIntrospection { - method: "POST".to_string(), - path: "/api/v1/info".to_string(), - }, - ResourceIntrospection { - method: "UNKNOWN".to_string(), - path: "/api/v1/guarded".to_string(), - }, - ResourceIntrospection { - method: "GET".to_string(), - path: "/api/v2/hello".to_string(), - }, - ResourceIntrospection { - method: "GET".to_string(), - path: "/admin/settings".to_string(), - }, - ResourceIntrospection { - method: "POST".to_string(), - path: "/admin/settings".to_string(), - }, - ResourceIntrospection { - method: "GET".to_string(), - path: "/admin/dashboard".to_string(), - }, - ResourceIntrospection { - method: "GET".to_string(), - path: "/extra/multi".to_string(), - }, - ResourceIntrospection { - method: "POST".to_string(), - path: "/extra/multi".to_string(), - }, - ResourceIntrospection { - method: "GET".to_string(), - path: "/extra/ping".to_string(), - }, - ResourceIntrospection { - method: "GET".to_string(), - path: "/".to_string(), - }, - ResourceIntrospection { - method: "POST".to_string(), - path: "/".to_string(), - }, - ResourceIntrospection { - method: "UNKNOWN".to_string(), - path: "/other_guard".to_string(), - }, - ResourceIntrospection { - method: "GET,UNKNOWN,POST".to_string(), - path: "/all_guard".to_string(), - }, - ]; - - for exp in expected_resources { - assert!( - registered_resources.contains(&exp), - "Expected resource not found: {:?}", - exp - ); +fn update_unique(existing: &mut Vec, new_items: &[T]) { + for item in new_items { + if !existing.contains(item) { + existing.push(item.clone()); } } } + +pub fn register_pattern_detail(pattern: String, methods: Vec, guards: Vec) { + if !is_designated_thread() { + return; + } + initialize_detail_registry(); + let mut reg = get_detail_registry().lock().unwrap(); + reg.entry(pattern) + .and_modify(|d| { + update_unique(&mut d.methods, &methods); + update_unique(&mut d.guards, &guards); + }) + .or_insert(RouteDetail { methods, guards }); +} diff --git a/actix-web/src/lib.rs b/actix-web/src/lib.rs index e26e76243..4dac84962 100644 --- a/actix-web/src/lib.rs +++ b/actix-web/src/lib.rs @@ -108,7 +108,7 @@ mod thin_data; pub(crate) mod types; pub mod web; -#[cfg(feature = "resources-introspection")] +#[cfg(feature = "experimental-introspection")] pub mod introspection; #[doc(inline)] diff --git a/actix-web/src/resource.rs b/actix-web/src/resource.rs index e5016d240..e6f3c0933 100644 --- a/actix-web/src/resource.rs +++ b/actix-web/src/resource.rs @@ -417,6 +417,8 @@ where B: MessageBody + 'static, { fn register(mut self, config: &mut AppService) { + let routes = std::mem::take(&mut self.routes); + let guards = if self.guards.is_empty() { None } else { @@ -433,27 +435,28 @@ where rdef.set_name(name); } - #[cfg(feature = "resources-introspection")] - let mut rdef_methods: Vec<(String, Vec)> = Vec::new(); - #[cfg(feature = "resources-introspection")] - let mut rmap = crate::rmap::ResourceMap::new(ResourceDef::prefix("")); - - #[cfg(feature = "resources-introspection")] + #[cfg(feature = "experimental-introspection")] { - rmap.add(&mut rdef, None); - - self.routes.iter().for_each(|r| { - r.get_guards().iter().for_each(|g| { - let http_methods: Vec = - crate::guard::HttpMethodsExtractor::extract_http_methods(&**g); - rdef_methods - .push((rdef.pattern().unwrap_or_default().to_string(), http_methods)); - }); - }); + let pat = rdef.pattern().unwrap_or("").to_string(); + let mut methods = Vec::new(); + let mut guard_names = Vec::new(); + for route in &routes { + if let Some(m) = route.get_method() { + if !methods.contains(&m) { + methods.push(m); + } + } + for name in route.guard_names() { + if !guard_names.contains(&name) { + guard_names.push(name.clone()); + } + } + } + crate::introspection::register_pattern_detail(pat, methods, guard_names); } *self.factory_ref.borrow_mut() = Some(ResourceFactory { - routes: self.routes, + routes, default: self.default, }); @@ -470,14 +473,6 @@ where async { Ok(fut.await?.map_into_boxed_body()) } }); - #[cfg(feature = "resources-introspection")] - { - crate::introspection::process_introspection( - Rc::clone(&Rc::new(rmap.clone())), - rdef_methods, - ); - } - config.register_service(rdef, guards, endpoint, None) } } diff --git a/actix-web/src/rmap.rs b/actix-web/src/rmap.rs index 2e4451b38..cf2336f78 100644 --- a/actix-web/src/rmap.rs +++ b/actix-web/src/rmap.rs @@ -15,16 +15,13 @@ const AVG_PATH_LEN: usize = 24; #[derive(Clone, Debug)] pub struct ResourceMap { - pattern: ResourceDef, - + pub(crate) pattern: ResourceDef, /// Named resources within the tree or, for external resources, it points to isolated nodes /// outside the tree. named: FoldHashMap>, - parent: RefCell>, - /// Must be `None` for "edge" nodes. - nodes: Option>>, + pub(crate) nodes: Option>>, } impl ResourceMap { @@ -38,42 +35,6 @@ impl ResourceMap { } } - #[cfg(feature = "resources-introspection")] - /// Returns a list of all paths in the resource map. - pub fn to_vec(&self) -> Vec { - let mut paths = Vec::new(); - self.collect_full_paths(String::new(), &mut paths); - paths - } - - #[cfg(feature = "resources-introspection")] - /// Recursive function that accumulates the full path and adds it only if the node is an endpoint (leaf). - fn collect_full_paths(&self, current_path: String, paths: &mut Vec) { - // Get the current segment of the pattern - if let Some(segment) = self.pattern.pattern() { - let mut new_path = current_path; - // Add the '/' separator if necessary - if !segment.is_empty() { - if !new_path.ends_with('/') && !new_path.is_empty() && !segment.starts_with('/') { - new_path.push('/'); - } - new_path.push_str(segment); - } - - // If this node is an endpoint (has no children), add the full path - if self.nodes.is_none() { - paths.push(new_path.clone()); - } - - // If it has children, iterate over each one - if let Some(children) = &self.nodes { - for child in children { - child.collect_full_paths(new_path.clone(), paths); - } - } - } - } - /// Format resource map as tree structure (unfinished). #[allow(dead_code)] pub(crate) fn tree(&self) -> String { diff --git a/actix-web/src/route.rs b/actix-web/src/route.rs index bcbfb9042..d502534a6 100644 --- a/actix-web/src/route.rs +++ b/actix-web/src/route.rs @@ -65,10 +65,10 @@ impl Route { pub(crate) fn take_guards(&mut self) -> Vec> { mem::take(Rc::get_mut(&mut self.guards).unwrap()) } - - #[cfg(feature = "resources-introspection")] - pub(crate) fn get_guards(&self) -> &Vec> { - &self.guards + /// Get the names of all guards applied to this route. + #[cfg(feature = "experimental-introspection")] + pub fn guard_names(&self) -> Vec { + self.guards.iter().map(|g| g.name()).collect() } } @@ -145,6 +145,23 @@ impl Route { self } + #[cfg(feature = "experimental-introspection")] + /// Get the first HTTP method guard applied to this route (if any). + /// WIP. + pub(crate) fn get_method(&self) -> Option { + self.guards.iter().find_map(|g| { + g.details().and_then(|details| { + details.into_iter().find_map(|d| { + if let crate::guard::GuardDetail::HttpMethods(mut m) = d { + m.pop().and_then(|s| s.parse().ok()) + } else { + None + } + }) + }) + }) + } + /// Add guard to the route. /// /// # Examples @@ -164,6 +181,10 @@ impl Route { self } + pub fn guards(&self) -> &Vec> { + &self.guards + } + /// Set handler function, use request extractors for parameters. /// /// # Examples diff --git a/actix-web/src/scope.rs b/actix-web/src/scope.rs index 0f2b87c86..2c1727779 100644 --- a/actix-web/src/scope.rs +++ b/actix-web/src/scope.rs @@ -395,9 +395,6 @@ where rmap.add(&mut rdef, None); } - #[cfg(feature = "resources-introspection")] - let mut rdef_methods: Vec<(String, Vec)> = Vec::new(); - // complete scope pipeline creation *self.factory_ref.borrow_mut() = Some(ScopeFactory { default, @@ -408,21 +405,38 @@ where .map(|(mut rdef, srv, guards, nested)| { rmap.add(&mut rdef, nested); - #[cfg(feature = "resources-introspection")] + #[cfg(feature = "experimental-introspection")] { - let http_methods: Vec = - guards.as_ref().map_or_else(Vec::new, |g| { - g.iter() - .flat_map(|g| { - crate::guard::HttpMethodsExtractor::extract_http_methods( - &**g, - ) - }) - .collect::>() - }); + use std::borrow::Borrow; - rdef_methods - .push((rdef.pattern().unwrap_or_default().to_string(), http_methods)); + // Get the pattern stored in ResourceMap + let pat = rdef.pattern().unwrap_or("").to_string(); + let guard_list: &[Box] = + guards.borrow().as_ref().map_or(&[], |v| &v[..]); + + // Extract HTTP methods from guards + let methods = guard_list + .iter() + .flat_map(|g| g.details().unwrap_or_default()) + .flat_map(|d| { + if let crate::guard::GuardDetail::HttpMethods(v) = d { + v.into_iter() + .filter_map(|s| s.parse().ok()) + .collect::>() + } else { + Vec::new() + } + }) + .collect::>(); + + // Extract guard names + let guard_names = guard_list + .iter() + .map(|g| g.name().to_string()) + .collect::>(); + + // Register route details for introspection + crate::introspection::register_pattern_detail(pat, methods, guard_names); } (rdef, srv, RefCell::new(guards)) @@ -452,14 +466,6 @@ where async { Ok(fut.await?.map_into_boxed_body()) } }); - #[cfg(feature = "resources-introspection")] - { - crate::introspection::process_introspection( - Rc::clone(&Rc::new(rmap.clone())), - rdef_methods, - ); - } - // register final service config.register_service( ResourceDef::root_prefix(&self.rdef),