From e3c8bfb1aed62fb1e06db3a690f957fab057c839 Mon Sep 17 00:00:00 2001 From: Tristan Druyen Date: Wed, 5 Mar 2025 00:46:21 +0100 Subject: [PATCH] WIP: Add nixpkg and hydra --- .gitignore | 1 - Cargo.lock | 1508 +++++++++++++++++ axum-controller-macros/Cargo.toml | 1 + axum-controller/Cargo.toml | 4 +- flake.lock | 8 +- flake.nix | 18 +- package.nix | 24 + .../.cargo-checksum.json | 1 + vendor/axum-typed-routing-macros/Cargo.toml | 56 + vendor/axum-typed-routing-macros/README.md | 1 + .../src/compilation.rs | 471 +++++ vendor/axum-typed-routing-macros/src/lib.rs | 243 +++ .../axum-typed-routing-macros/src/parsing.rs | 412 +++++ .../axum-typed-routing/.cargo-checksum.json | 1 + vendor/axum-typed-routing/Cargo.toml | 80 + vendor/axum-typed-routing/examples/aide.rs | 28 + vendor/axum-typed-routing/examples/basic.rs | 20 + vendor/axum-typed-routing/src/lib.rs | 125 ++ vendor/axum-typed-routing/tests/main.rs | 234 +++ 19 files changed, 3227 insertions(+), 9 deletions(-) create mode 100644 Cargo.lock create mode 100644 package.nix create mode 100644 vendor/axum-typed-routing-macros/.cargo-checksum.json create mode 100644 vendor/axum-typed-routing-macros/Cargo.toml create mode 100644 vendor/axum-typed-routing-macros/README.md create mode 100644 vendor/axum-typed-routing-macros/src/compilation.rs create mode 100644 vendor/axum-typed-routing-macros/src/lib.rs create mode 100644 vendor/axum-typed-routing-macros/src/parsing.rs create mode 100644 vendor/axum-typed-routing/.cargo-checksum.json create mode 100644 vendor/axum-typed-routing/Cargo.toml create mode 100644 vendor/axum-typed-routing/examples/aide.rs create mode 100644 vendor/axum-typed-routing/examples/basic.rs create mode 100644 vendor/axum-typed-routing/src/lib.rs create mode 100644 vendor/axum-typed-routing/tests/main.rs diff --git a/.gitignore b/.gitignore index 2a455ea..306582c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ target -Cargo.lock .vscode .direnv diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4f0473c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1508 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aide" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef7da148319b3f1ac7d338f7a144521ee399cd65e4381aa0c17994e74304aa8" +dependencies = [ + "axum", + "bytes", + "cfg-if", + "http", + "indexmap", + "schemars", + "serde", + "serde_json", + "thiserror", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "anyhow" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "auto-future" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c1e7e457ea78e524f48639f551fd79703ac3f2237f5ecccdf4708f8a75ad373" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "axum" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-controller" +version = "0.1.1" +dependencies = [ + "axum", + "axum-controller-macros", + "axum-test", + "axum-typed-routing", + "json", + "serde", + "tokio", +] + +[[package]] +name = "axum-controller-macros" +version = "0.1.1" +dependencies = [ + "axum", + "prettyplease", + "proc-macro2", + "quote", + "serde", + "syn", +] + +[[package]] +name = "axum-core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "axum-test" +version = "17.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317c1f4ecc1e68e0ad5decb78478421055c963ce215e736ed97463fa609cd196" +dependencies = [ + "anyhow", + "assert-json-diff", + "auto-future", + "axum", + "bytes", + "bytesize", + "cookie", + "http", + "http-body-util", + "hyper", + "hyper-util", + "mime", + "pretty_assertions", + "reserve-port", + "rust-multipart-rfc7578_2", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "tokio", + "tower", + "url", +] + +[[package]] +name = "axum-typed-routing" +version = "0.2.0" +dependencies = [ + "aide", + "axum", + "axum-macros", + "axum-test", + "axum-typed-routing-macros", + "json", + "schemars", + "serde", + "tokio", +] + +[[package]] +name = "axum-typed-routing-macros" +version = "0.2.0" +dependencies = [ + "aide", + "axum", + "proc-macro2", + "quote", + "schemars", + "serde", + "syn", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" + +[[package]] +name = "bytesize" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2c12f985c78475a6b8d629afd0c360260ef34cfef52efccdcfd31972f81c2e" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "http" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +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", +] + +[[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", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "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 = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.170" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy 0.7.35", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "prettyplease" +version = "0.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha", + "rand_core", + "zerocopy 0.8.21", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "reserve-port" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "359fc315ed556eb0e42ce74e76f4b1cd807b50fa6307f3de4e51f92dbe86e2d5" +dependencies = [ + "lazy_static", + "thiserror", +] + +[[package]] +name = "rust-multipart-rfc7578_2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc4bb9e7c9abe5fa5f30c2d8f8fefb9e0080a2c1e3c2e567318d2907054b35d3" +dependencies = [ + "bytes", + "futures-core", + "futures-util", + "http", + "mime", + "mime_guess", + "rand", + "thiserror", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.218" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.218" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "2.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf01143b2dd5d134f11f545cf9f1431b13b749695cb33bcce051e7568f99478" +dependencies = [ + "zerocopy-derive 0.8.21", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712c8386f4f4299382c9abee219bee7084f78fb939d88b6840fcc1320d5f6da2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/axum-controller-macros/Cargo.toml b/axum-controller-macros/Cargo.toml index 07964b2..76d09f3 100644 --- a/axum-controller-macros/Cargo.toml +++ b/axum-controller-macros/Cargo.toml @@ -1,3 +1,4 @@ +cargo-features = ["edition2024"] [package] authors.workspace = true categories.workspace = true diff --git a/axum-controller/Cargo.toml b/axum-controller/Cargo.toml index 9b1a211..d4c559a 100644 --- a/axum-controller/Cargo.toml +++ b/axum-controller/Cargo.toml @@ -1,3 +1,5 @@ +cargo-features = ["edition2024"] + [package] authors.workspace = true categories.workspace = true @@ -13,7 +15,7 @@ version.workspace = true [dependencies] axum-controller-macros = { path = "../axum-controller-macros", version = "0.1.1" } -axum-typed-routing = { git = "https://github.com/jvdwrf/axum-typed-routing?ref=160684a406d616974d851bbfc6d0d9ffa65367e5", version = "0.2.0" } # version with axum 0.8 compat isn't pushed sadly +axum-typed-routing = { path = "../vendor/axum-typed-routing", version = "0.2.0" } [dev-dependencies] axum = "0.8" diff --git a/flake.lock b/flake.lock index 4ef35d5..b1a965c 100644 --- a/flake.lock +++ b/flake.lock @@ -84,16 +84,16 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1741073343, - "narHash": "sha256-8qmLpDUmaiBGLZkFfVyK5/T5fyTXXGdzCRdqAtO0gf4=", + "lastModified": 1741010256, + "narHash": "sha256-WZNlK/KX7Sni0RyqLSqLPbK8k08Kq7H7RijPJbq9KHM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "72bccb2960235fd31de456566789c324a251f297", + "rev": "ba487dbc9d04e0634c64e3b1f0d25839a0a68246", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-unstable-small", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index 27a0eba..b4b49a6 100644 --- a/flake.nix +++ b/flake.nix @@ -10,7 +10,7 @@ ]; }; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable-small"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; rust-overlay.url = "github:oxalica/rust-overlay"; flake-utils.url = "github:numtide/flake-utils"; flake-parts.url = "github:hercules-ci/flake-parts"; @@ -53,7 +53,6 @@ ]; in { - apps.devshell = self.outputs.devShells.${system}.default.flakeApp; devShells.default = pkgs.mkShell { packages = with pkgs; [ @@ -90,5 +89,18 @@ export MALLOC_CONF=thp:always,metadata_thp:always ''; }; - }); + packages = { + default = pkgs.callPackage ./package.nix { }; + }; + }) // { + hydraJobs = + let + system = "x86_64-linux"; + packages = self.packages."${system}"; + devShells = self.devShells."${system}"; + in + { + inherit packages devShells; + }; + }; } diff --git a/package.nix b/package.nix new file mode 100644 index 0000000..6aefa92 --- /dev/null +++ b/package.nix @@ -0,0 +1,24 @@ +{ lib +, fetchFromGitHub +, rustPlatform +, +}: +rustPlatform.buildRustPackage rec { + pname = "axum-controller"; + version = "0.0.1"; + + src = ./.; + + useFetchCargoVendor = true; + + cargoLock = { + lockFile = ./Cargo.lock; + }; + + meta = { + # description = ""; + # homepage = ""; + # license = lib.licenses.unlicense; + maintainers = [ ]; + }; +} diff --git a/vendor/axum-typed-routing-macros/.cargo-checksum.json b/vendor/axum-typed-routing-macros/.cargo-checksum.json new file mode 100644 index 0000000..d2e641d --- /dev/null +++ b/vendor/axum-typed-routing-macros/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"e56e1669f5c26818c13c68258f40e6d9156d1410ab3f9330219902f0efbcbc4b","README.md":"cdb9d483d904c1c10c86358dd089a805880d4c7b5ac4316589c74e4bf2cdc870","src/compilation.rs":"ea1a35cb02f32ef4a25204546caa9a3aa3c0e6d225666a8966bac87147328c55","src/lib.rs":"482d132fa15cc582e7826e44924de37659713f8c75db1410df962529545e893c","src/parsing.rs":"d81011c3a7d438d25c1607abf57501320c5ce4c9c3b867d78340947bf10e730a"},"package":null} \ No newline at end of file diff --git a/vendor/axum-typed-routing-macros/Cargo.toml b/vendor/axum-typed-routing-macros/Cargo.toml new file mode 100644 index 0000000..86f2d7b --- /dev/null +++ b/vendor/axum-typed-routing-macros/Cargo.toml @@ -0,0 +1,56 @@ +# 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] +autobenches = false +autobins = false +autoexamples = false +autolib = false +autotests = false +build = false +categories = ["web-programming"] +description = "Typed routing macros for axum" +edition = "2021" +homepage = "https://github.com/jvdwrf/axum-typed-routing" +keywords = ["axum", "handler", "macro", "routing", "typed"] +license = "MIT OR Apache-2.0" +name = "axum-typed-routing-macros" +readme = "../README.md" +repository = "https://github.com/jvdwrf/axum-typed-routing" +version = "0.2.0" + +[lib] +name = "axum_typed_routing_macros" +path = "src/lib.rs" +proc-macro = true + +[dependencies] +proc-macro2 = "1" +quote = "1" + +[dependencies.syn] +features = ["full"] +version = "2" + +[dev-dependencies] +schemars = "0.8" + +[dev-dependencies.aide] +features = ["axum", "axum-json", "axum-query"] +version = "0.14" + +[dev-dependencies.axum] +features = [] +version = "0.8" + +[dev-dependencies.serde] +features = ["derive"] +version = "1.0" diff --git a/vendor/axum-typed-routing-macros/README.md b/vendor/axum-typed-routing-macros/README.md new file mode 100644 index 0000000..3ae6b20 --- /dev/null +++ b/vendor/axum-typed-routing-macros/README.md @@ -0,0 +1 @@ +Macro's for `axum-typed-routing`. \ No newline at end of file diff --git a/vendor/axum-typed-routing-macros/src/compilation.rs b/vendor/axum-typed-routing-macros/src/compilation.rs new file mode 100644 index 0000000..81a9e55 --- /dev/null +++ b/vendor/axum-typed-routing-macros/src/compilation.rs @@ -0,0 +1,471 @@ +use quote::ToTokens; +use syn::{spanned::Spanned, LitBool, LitInt, Pat, PatType}; + +use crate::parsing::{OapiOptions, Responses, Security, StrArray}; + +use self::parsing::PathParam; + +use super::*; + +pub struct CompiledRoute { + pub method: Method, + #[allow(clippy::type_complexity)] + pub path_params: Vec<(Slash, PathParam)>, + pub query_params: Vec<(Ident, Box)>, + pub state: Type, + pub route_lit: LitStr, + pub oapi_options: Option, +} + +impl CompiledRoute { + pub fn to_axum_path_string(&self) -> String { + let mut path = String::new(); + + for (_slash, param) in &self.path_params { + path.push('/'); + match param { + PathParam::Capture(lit, _brace_1, _, _, _brace_2) => { + path.push('{'); + path.push_str(&lit.value()); + path.push('}'); + } + PathParam::WildCard(lit, _brace_1, _, _, _, _brace_2) => { + path.push('{'); + path.push('*'); + path.push_str(&lit.value()); + path.push('}'); + } + PathParam::Static(lit) => path.push_str(&lit.value()), + } + // if colon.is_some() { + // path.push(':'); + // } + // path.push_str(&ident.value()); + } + + path + } + + /// Removes the arguments in `route` from `args`, and merges them in the output. + pub fn from_route(mut route: Route, function: &ItemFn, with_aide: bool) -> syn::Result { + if !with_aide && route.oapi_options.is_some() { + return Err(syn::Error::new( + Span::call_site(), + "Use `api_route` instead of `route` to use OpenAPI options", + )); + } else if with_aide && route.oapi_options.is_none() { + route.oapi_options = Some(OapiOptions { + summary: None, + description: None, + id: None, + hidden: None, + tags: None, + security: None, + responses: None, + transform: None, + }); + } + + let sig = &function.sig; + let mut arg_map = sig + .inputs + .iter() + .filter_map(|item| match item { + syn::FnArg::Receiver(_) => None, + syn::FnArg::Typed(pat_type) => Some(pat_type), + }) + .filter_map(|pat_type| match &*pat_type.pat { + syn::Pat::Ident(ident) => Some((ident.ident.clone(), pat_type.ty.clone())), + _ => None, + }) + .collect::>(); + + for (_slash, path_param) in &mut route.path_params { + match path_param { + PathParam::Capture(_lit, _, ident, ty, _) => { + let (new_ident, new_ty) = arg_map.remove_entry(ident).ok_or_else(|| { + syn::Error::new( + ident.span(), + format!("path parameter `{}` not found in function arguments", ident), + ) + })?; + *ident = new_ident; + *ty = new_ty; + } + PathParam::WildCard(_lit, _, _star, ident, ty, _) => { + let (new_ident, new_ty) = arg_map.remove_entry(ident).ok_or_else(|| { + syn::Error::new( + ident.span(), + format!("path parameter `{}` not found in function arguments", ident), + ) + })?; + *ident = new_ident; + *ty = new_ty; + } + PathParam::Static(_lit) => {} + } + } + + let mut query_params = Vec::new(); + for ident in route.query_params { + let (ident, ty) = arg_map.remove_entry(&ident).ok_or_else(|| { + syn::Error::new( + ident.span(), + format!( + "query parameter `{}` not found in function arguments", + ident + ), + ) + })?; + query_params.push((ident, ty)); + } + + if let Some(options) = route.oapi_options.as_mut() { + options.merge_with_fn(function) + } + + Ok(Self { + route_lit: route.route_lit, + method: route.method, + path_params: route.path_params, + query_params, + state: route.state.unwrap_or_else(|| guess_state_type(sig)), + oapi_options: route.oapi_options, + }) + } + + pub fn path_extractor(&self) -> Option { + if !self.path_params.iter().any(|(_, param)| param.captures()) { + return None; + } + + let path_iter = self + .path_params + .iter() + .filter_map(|(_slash, path_param)| path_param.capture()); + let idents = path_iter.clone().map(|item| item.0); + let types = path_iter.clone().map(|item| item.1); + Some(quote! { + ::axum::extract::Path((#(#idents,)*)): ::axum::extract::Path<(#(#types,)*)>, + }) + } + + pub fn query_extractor(&self) -> Option { + if self.query_params.is_empty() { + return None; + } + + let idents = self.query_params.iter().map(|item| &item.0); + Some(quote! { + ::axum::extract::Query(__QueryParams__ { + #(#idents,)* + }): ::axum::extract::Query<__QueryParams__>, + }) + } + + pub fn query_params_struct(&self, with_aide: bool) -> Option { + match self.query_params.is_empty() { + true => None, + false => { + let idents = self.query_params.iter().map(|item| &item.0); + let types = self.query_params.iter().map(|item| &item.1); + let derive = match with_aide { + true => quote! { #[derive(::serde::Deserialize, ::schemars::JsonSchema)] }, + false => quote! { #[derive(::serde::Deserialize)] }, + }; + Some(quote! { + #derive + struct __QueryParams__ { + #(#idents: #types,)* + } + }) + } + } + } + + pub fn extracted_idents(&self) -> Vec { + let mut idents = Vec::new(); + for (_slash, path_param) in &self.path_params { + if let Some((ident, _ty)) = path_param.capture() { + idents.push(ident.clone()); + } + // if let Some((_colon, ident, _ty)) = colon { + // idents.push(ident.clone()); + // } + } + for (ident, _ty) in &self.query_params { + idents.push(ident.clone()); + } + idents + } + + /// The arguments not used in the route. + /// Map the identifier to `___arg___{i}: Type`. + pub fn remaining_pattypes_numbered( + &self, + args: &Punctuated, + ) -> Punctuated { + args.iter() + .enumerate() + .filter_map(|(i, item)| { + if let FnArg::Typed(pat_type) = item { + if let syn::Pat::Ident(pat_ident) = &*pat_type.pat { + if self.path_params.iter().any(|(_slash, path_param)| { + if let Some((path_ident, _ty)) = path_param.capture() { + path_ident == &pat_ident.ident + } else { + false + } + }) || self + .query_params + .iter() + .any(|(query_ident, _)| query_ident == &pat_ident.ident) + { + return None; + } + } + + let mut new_pat_type = pat_type.clone(); + let ident = format_ident!("___arg___{}", i); + new_pat_type.pat = Box::new(parse_quote!(#ident)); + Some(new_pat_type) + } else { + unimplemented!("Self type is not supported") + } + }) + .collect() + } + + pub fn ide_documentation_for_aide_methods(&self) -> TokenStream2 { + let Some(options) = &self.oapi_options else { + return quote! {}; + }; + let summary = options.summary.as_ref().map(|(ident, _)| { + let method = Ident::new("summary", ident.span()); + quote!( let x = x.#method(""); ) + }); + let description = options.description.as_ref().map(|(ident, _)| { + let method = Ident::new("description", ident.span()); + quote!( let x = x.#method(""); ) + }); + let id = options.id.as_ref().map(|(ident, _)| { + let method = Ident::new("id", ident.span()); + quote!( let x = x.#method(""); ) + }); + let hidden = options.hidden.as_ref().map(|(ident, _)| { + let method = Ident::new("hidden", ident.span()); + quote!( let x = x.#method(false); ) + }); + let tags = options.tags.as_ref().map(|(ident, _)| { + let method = Ident::new("tag", ident.span()); + quote!( let x = x.#method(""); ) + }); + let security = options.security.as_ref().map(|(ident, _)| { + let method = Ident::new("security_requirement_scopes", ident.span()); + quote!( let x = x.#method("", [""]); ) + }); + let responses = options.responses.as_ref().map(|(ident, _)| { + let method = Ident::new("response", ident.span()); + quote!( let x = x.#method::<0, String>(); ) + }); + let transform = options.transform.as_ref().map(|(ident, _)| { + let method = Ident::new("with", ident.span()); + quote!( let x = x.#method(|x|x); ) + }); + + quote! { + #[allow(unused)] + #[allow(clippy::no_effect)] + fn ____ide_documentation_for_aide____(x: ::aide::transform::TransformOperation) { + #summary + #description + #id + #hidden + #tags + #security + #responses + #transform + } + } + } + + pub fn get_oapi_summary(&self) -> Option { + if let Some(oapi_options) = &self.oapi_options { + if let Some(summary) = &oapi_options.summary { + return Some(summary.1.clone()); + } + } + None + } + + pub fn get_oapi_description(&self) -> Option { + if let Some(oapi_options) = &self.oapi_options { + if let Some(description) = &oapi_options.description { + return Some(description.1.clone()); + } + } + None + } + + pub fn get_oapi_hidden(&self) -> Option { + if let Some(oapi_options) = &self.oapi_options { + if let Some(hidden) = &oapi_options.hidden { + return Some(hidden.1.clone()); + } + } + None + } + + pub fn get_oapi_tags(&self) -> Vec { + if let Some(oapi_options) = &self.oapi_options { + if let Some(tags) = &oapi_options.tags { + return tags.1 .0.clone(); + } + } + Vec::new() + } + + pub fn get_oapi_id(&self, sig: &Signature) -> Option { + if let Some(oapi_options) = &self.oapi_options { + if let Some(id) = &oapi_options.id { + return Some(id.1.clone()); + } + } + Some(LitStr::new(&sig.ident.to_string(), sig.ident.span())) + } + + pub fn get_oapi_transform(&self) -> syn::Result> { + if let Some(oapi_options) = &self.oapi_options { + if let Some(transform) = &oapi_options.transform { + if transform.1.inputs.len() != 1 { + return Err(syn::Error::new( + transform.1.span(), + "expected a single identifier", + )); + } + + let pat = transform.1.inputs.first().unwrap(); + let body = &transform.1.body; + + if let Pat::Ident(pat_ident) = pat { + let ident = &pat_ident.ident; + return Ok(Some(quote! { + let #ident = __op__; + let __op__ = #body; + })); + } else { + return Err(syn::Error::new( + pat.span(), + "expected a single identifier without type", + )); + } + } + } + Ok(None) + } + + pub fn get_oapi_responses(&self) -> Vec<(LitInt, Type)> { + if let Some(oapi_options) = &self.oapi_options { + if let Some((_ident, Responses(responses))) = &oapi_options.responses { + return responses.clone(); + } + } + Default::default() + } + + pub fn get_oapi_security(&self) -> Vec<(LitStr, Vec)> { + if let Some(oapi_options) = &self.oapi_options { + if let Some((_ident, Security(security))) = &oapi_options.security { + return security + .iter() + .map(|(scheme, StrArray(scopes))| (scheme.clone(), scopes.clone())) + .collect(); + } + } + Default::default() + } + + pub(crate) fn to_doc_comments(&self) -> TokenStream2 { + let mut doc = format!( + "# Handler information +- Method: `{}` +- Path: `{}` +- State: `{}`", + self.method.to_axum_method_name(), + self.route_lit.value(), + self.state.to_token_stream(), + ); + + if let Some(options) = &self.oapi_options { + let summary = options + .summary + .as_ref() + .map(|(_, summary)| format!("\"{}\"", summary.value())) + .unwrap_or("None".to_string()); + let description = options + .description + .as_ref() + .map(|(_, description)| format!("\"{}\"", description.value())) + .unwrap_or("None".to_string()); + let id = options + .id + .as_ref() + .map(|(_, id)| format!("\"{}\"", id.value())) + .unwrap_or("None".to_string()); + let hidden = options + .hidden + .as_ref() + .map(|(_, hidden)| hidden.value().to_string()) + .unwrap_or("None".to_string()); + let tags = options + .tags + .as_ref() + .map(|(_, tags)| tags.to_string()) + .unwrap_or("[]".to_string()); + let security = options + .security + .as_ref() + .map(|(_, security)| security.to_string()) + .unwrap_or("{}".to_string()); + + doc = format!( + "{doc} + +## OpenAPI +- Summary: `{summary}` +- Description: `{description}` +- Operation id: `{id}` +- Tags: `{tags}` +- Security: `{security}` +- Hidden: `{hidden}` +" + ); + } + + quote!( + #[doc = #doc] + ) + } +} + +fn guess_state_type(sig: &syn::Signature) -> Type { + for arg in &sig.inputs { + if let FnArg::Typed(pat_type) = arg { + // Returns `T` if the type of the last segment is exactly `State`. + if let Type::Path(ty) = &*pat_type.ty { + let last_segment = ty.path.segments.last().unwrap(); + if last_segment.ident == "State" { + if let PathArguments::AngleBracketed(args) = &last_segment.arguments { + if args.args.len() == 1 { + if let GenericArgument::Type(ty) = args.args.first().unwrap() { + return ty.clone(); + } + } + } + } + } + } + } + + parse_quote! { () } +} diff --git a/vendor/axum-typed-routing-macros/src/lib.rs b/vendor/axum-typed-routing-macros/src/lib.rs new file mode 100644 index 0000000..36ce1f9 --- /dev/null +++ b/vendor/axum-typed-routing-macros/src/lib.rs @@ -0,0 +1,243 @@ +use compilation::CompiledRoute; +use parsing::{Method, Route}; +use proc_macro::TokenStream; +use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; +use std::collections::HashMap; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + token::{Comma, Slash}, + FnArg, GenericArgument, ItemFn, LitStr, Meta, PathArguments, Signature, Type, +}; +#[macro_use] +extern crate quote; +#[macro_use] +extern crate syn; + +mod compilation; +mod parsing; + +/// A macro that generates statically-typed routes for axum handlers. +/// +/// # Syntax +/// ```ignore +/// #[route( "" [with ])] +/// ``` +/// - `METHOD` is the HTTP method, such as `GET`, `POST`, `PUT`, etc. +/// - `PATH` is the path of the route, with optional path parameters and query parameters, +/// e.g. `/item/:id?amount&offset`. +/// - `STATE` is the type of axum-state, passed to the handler. This is optional, and if not +/// specified, the state type is guessed based on the parameters of the handler. +/// +/// # Example +/// ``` +/// use axum::extract::{State, Json}; +/// use axum_typed_routing_macros::route; +/// +/// #[route(GET "/item/:id?amount&offset")] +/// async fn item_handler( +/// id: u32, +/// amount: Option, +/// offset: Option, +/// State(state): State, +/// Json(json): Json, +/// ) -> String { +/// todo!("handle request") +/// } +/// ``` +/// +/// # State type +/// Normally, the state-type is guessed based on the parameters of the function: +/// If the function has a parameter of type `[..]::State`, then `T` is used as the state type. +/// This should work for most cases, however when not sufficient, the state type can be specified +/// explicitly using the `with` keyword: +/// ```ignore +/// #[route(GET "/item/:id?amount&offset" with String)] +/// ``` +/// +/// # Internals +/// The macro expands to a function with signature `fn() -> (&'static str, axum::routing::MethodRouter)`. +/// The first element of the tuple is the path, and the second is axum's `MethodRouter`. +/// +/// The path and query are extracted using axum's `extract::Path` and `extract::Query` extractors, as the first +/// and second parameters of the function. The remaining parameters are the parameters of the handler. +#[proc_macro_attribute] +pub fn route(attr: TokenStream, mut item: TokenStream) -> TokenStream { + match _route(attr, item.clone(), false) { + Ok(tokens) => tokens.into(), + Err(err) => { + let err: TokenStream = err.to_compile_error().into(); + item.extend(err); + item + } + } +} + +/// Same as [`macro@route`], but with support for OpenApi using `aide`. See [`macro@route`] for more +/// information and examples. +/// +/// # Syntax +/// ```ignore +/// #[api_route( "" [with ] [{ +/// summary: "", +/// description: "", +/// id: "", +/// tags: ["", ..], +/// hidden: , +/// security: { : ["", ..], .. }, +/// responses: { : , .. }, +/// transform: |op| { .. }, +/// }])] +/// ``` +/// - `summary` is the OpenApi summary. If not specified, the first line of the function's doc-comments +/// - `description` is the OpenApi description. If not specified, the rest of the function's doc-comments +/// - `id` is the OpenApi operationId. If not specified, the function's name is used. +/// - `tags` are the OpenApi tags. +/// - `hidden` sets whether docs should be hidden for this route. +/// - `security` is the OpenApi security requirements. +/// - `responses` are the OpenApi responses. +/// - `transform` is a closure that takes an `TransformOperation` and returns an `TransformOperation`. +/// This may override the other options. (see the crate `aide` for more information). +/// +/// # Example +/// ``` +/// use axum::extract::{State, Json}; +/// use axum_typed_routing_macros::api_route; +/// +/// #[api_route(GET "/item/:id?amount&offset" with String { +/// summary: "Get an item", +/// description: "Get an item by id", +/// id: "get-item", +/// tags: ["items"], +/// hidden: false, +/// security: { "bearer": ["read:items"] }, +/// responses: { 200: String }, +/// transform: |op| op.tag("private"), +/// })] +/// async fn item_handler( +/// id: u32, +/// amount: Option, +/// offset: Option, +/// State(state): State, +/// ) -> String { +/// todo!("handle request") +/// } +/// ``` +#[proc_macro_attribute] +pub fn api_route(attr: TokenStream, mut item: TokenStream) -> TokenStream { + match _route(attr, item.clone(), true) { + Ok(tokens) => tokens.into(), + Err(err) => { + let err: TokenStream = err.to_compile_error().into(); + item.extend(err); + item + } + } +} + +fn _route(attr: TokenStream, item: TokenStream, with_aide: bool) -> syn::Result { + // Parse the route and function + let route = syn::parse::(attr)?; + let function = syn::parse::(item)?; + + // Now we can compile the route + let route = CompiledRoute::from_route(route, &function, with_aide)?; + let path_extractor = route.path_extractor(); + let query_extractor = route.query_extractor(); + let query_params_struct = route.query_params_struct(with_aide); + let state_type = &route.state; + let axum_path = route.to_axum_path_string(); + let http_method = route.method.to_axum_method_name(); + let remaining_numbered_pats = route.remaining_pattypes_numbered(&function.sig.inputs); + let extracted_idents = route.extracted_idents(); + let remaining_numbered_idents = remaining_numbered_pats.iter().map(|pat_type| &pat_type.pat); + let route_docs = route.to_doc_comments(); + + // Get the variables we need for code generation + let fn_name = &function.sig.ident; + let fn_output = &function.sig.output; + let vis = &function.vis; + let asyncness = &function.sig.asyncness; + let (impl_generics, ty_generics, where_clause) = &function.sig.generics.split_for_impl(); + let ty_generics = ty_generics.as_turbofish(); + let fn_docs = function + .attrs + .iter() + .filter(|attr| attr.path().is_ident("doc")); + + let (aide_ident_docs, inner_fn_call, method_router_ty) = if with_aide { + let http_method = format_ident!("{}_with", http_method); + let summary = route + .get_oapi_summary() + .map(|summary| quote! { .summary(#summary) }); + let description = route + .get_oapi_description() + .map(|description| quote! { .description(#description) }); + let hidden = route + .get_oapi_hidden() + .map(|hidden| quote! { .hidden(#hidden) }); + let tags = route.get_oapi_tags(); + let id = route + .get_oapi_id(&function.sig) + .map(|id| quote! { .id(#id) }); + let transform = route.get_oapi_transform()?; + let responses = route.get_oapi_responses(); + let response_code = responses.iter().map(|response| &response.0); + let response_type = responses.iter().map(|response| &response.1); + let security = route.get_oapi_security(); + let schemes = security.iter().map(|sec| &sec.0); + let scopes = security.iter().map(|sec| &sec.1); + + ( + route.ide_documentation_for_aide_methods(), + quote! { + ::aide::axum::routing::#http_method( + __inner__function__ #ty_generics, + |__op__| { + let __op__ = __op__ + #summary + #description + #hidden + #id + #(.tag(#tags))* + #(.security_requirement_scopes::, _>(#schemes, vec![#(#scopes),*]))* + #(.response::<#response_code, #response_type>())* + ; + #transform + __op__ + } + ) + }, + quote! { ::aide::axum::routing::ApiMethodRouter }, + ) + } else { + ( + quote!(), + quote! { ::axum::routing::#http_method(__inner__function__ #ty_generics) }, + quote! { ::axum::routing::MethodRouter }, + ) + }; + + // Generate the code + Ok(quote! { + #(#fn_docs)* + #route_docs + #vis fn #fn_name #impl_generics() -> (&'static str, #method_router_ty<#state_type>) #where_clause { + + #query_params_struct + + #aide_ident_docs + #asyncness fn __inner__function__ #impl_generics( + #path_extractor + #query_extractor + #remaining_numbered_pats + ) #fn_output #where_clause { + #function + + #fn_name #ty_generics(#(#extracted_idents,)* #(#remaining_numbered_idents,)* ).await + } + + (#axum_path, #inner_fn_call) + } + }) +} diff --git a/vendor/axum-typed-routing-macros/src/parsing.rs b/vendor/axum-typed-routing-macros/src/parsing.rs new file mode 100644 index 0000000..805ce2e --- /dev/null +++ b/vendor/axum-typed-routing-macros/src/parsing.rs @@ -0,0 +1,412 @@ +use core::panic; + +use quote::ToTokens; +use syn::{ + token::{Brace, Star}, + Attribute, Expr, ExprClosure, Lit, LitBool, LitInt, +}; + +use super::*; + +struct RouteParser { + path_params: Vec<(Slash, PathParam)>, + query_params: Vec, +} + +impl RouteParser { + fn new(lit: LitStr) -> syn::Result { + let val = lit.value(); + let span = lit.span(); + let split_route = val.split('?').collect::>(); + if split_route.len() > 2 { + return Err(syn::Error::new(span, "expected at most one '?'")); + } + + let path = split_route[0]; + if !path.starts_with('/') { + return Err(syn::Error::new(span, "expected path to start with '/'")); + } + let path = path.strip_prefix('/').unwrap(); + + let mut path_params = Vec::new(); + #[allow(clippy::never_loop)] + for path_param in path.split('/') { + path_params.push(( + Slash(span), + PathParam::new(path_param, span, Box::new(parse_quote!(()))), + )); + } + + let path_param_len = path_params.len(); + for (i, (_slash, path_param)) in path_params.iter().enumerate() { + match path_param { + PathParam::WildCard(_, _, _, _, _, _) => { + if i != path_param_len - 1 { + return Err(syn::Error::new( + span, + "wildcard path param must be the last path param", + )); + } + } + PathParam::Capture(_, _, _, _, _) => (), + PathParam::Static(lit) => { + if lit.value() == "*" && i != path_param_len - 1 { + return Err(syn::Error::new( + span, + "wildcard path param must be the last path param", + )); + } + } + } + } + + let mut query_params = Vec::new(); + if split_route.len() == 2 { + let query = split_route[1]; + for query_param in query.split('&') { + query_params.push(Ident::new(query_param, span)); + } + } + + Ok(Self { + path_params, + query_params, + }) + } +} + +pub enum PathParam { + WildCard(LitStr, Brace, Star, Ident, Box, Brace), + Capture(LitStr, Brace, Ident, Box, Brace), + Static(LitStr), +} + +impl PathParam { + pub fn captures(&self) -> bool { + matches!(self, Self::Capture(..) | Self::WildCard(..)) + } + + // pub fn lit(&self) -> &LitStr { + // match self { + // Self::Capture(lit, _, _, _) => lit, + // Self::WildCard(lit, _, _, _) => lit, + // Self::Static(lit) => lit, + // } + // } + + pub fn capture(&self) -> Option<(&Ident, &Type)> { + match self { + Self::Capture(_, _, ident, ty, _) => Some((ident, ty)), + Self::WildCard(_, _, _, ident, ty, _) => Some((ident, ty)), + _ => None, + } + } + + fn new(str: &str, span: Span, ty: Box) -> Self { + if str.starts_with(':') { + let str = str.strip_prefix(':').unwrap(); + Self::Capture( + LitStr::new(str, span), + Brace(span), + Ident::new(str, span), + ty, + Brace(span), + ) + } else if str.starts_with('*') && str.len() > 1 { + let str = str.strip_prefix('*').unwrap(); + Self::WildCard( + LitStr::new(str, span), + Brace(span), + Star(span), + Ident::new(str, span), + ty, + Brace(span), + ) + } else { + Self::Static(LitStr::new(str, span)) + } + } +} + +pub struct OapiOptions { + pub summary: Option<(Ident, LitStr)>, + pub description: Option<(Ident, LitStr)>, + pub id: Option<(Ident, LitStr)>, + pub hidden: Option<(Ident, LitBool)>, + pub tags: Option<(Ident, StrArray)>, + pub security: Option<(Ident, Security)>, + pub responses: Option<(Ident, Responses)>, + pub transform: Option<(Ident, ExprClosure)>, +} + +pub struct Security(pub Vec<(LitStr, StrArray)>); +impl Parse for Security { + fn parse(input: ParseStream) -> syn::Result { + let inner; + braced!(inner in input); + + let mut arr = Vec::new(); + while !inner.is_empty() { + let scheme = inner.parse::()?; + let _ = inner.parse::()?; + let scopes = inner.parse::()?; + let _ = inner.parse::().ok(); + arr.push((scheme, scopes)); + } + + Ok(Self(arr)) + } +} + +impl ToString for Security { + fn to_string(&self) -> String { + let mut s = String::new(); + s.push('{'); + for (i, (scheme, scopes)) in self.0.iter().enumerate() { + if i > 0 { + s.push_str(", "); + } + s.push_str(&scheme.value()); + s.push_str(": "); + s.push_str(&scopes.to_string()); + } + s.push('}'); + s + } +} + +pub struct Responses(pub Vec<(LitInt, Type)>); +impl Parse for Responses { + fn parse(input: ParseStream) -> syn::Result { + let inner; + braced!(inner in input); + + let mut arr = Vec::new(); + while !inner.is_empty() { + let status = inner.parse::()?; + let _ = inner.parse::()?; + let ty = inner.parse::()?; + let _ = inner.parse::().ok(); + arr.push((status, ty)); + } + + Ok(Self(arr)) + } +} + +impl ToString for Responses { + fn to_string(&self) -> String { + let mut s = String::new(); + s.push('{'); + for (i, (status, ty)) in self.0.iter().enumerate() { + if i > 0 { + s.push_str(", "); + } + s.push_str(&status.to_string()); + s.push_str(": "); + s.push_str(&ty.to_token_stream().to_string()); + } + s.push('}'); + s + } +} + +#[derive(Clone)] +pub struct StrArray(pub Vec); +impl Parse for StrArray { + fn parse(input: ParseStream) -> syn::Result { + let inner; + bracketed!(inner in input); + let mut arr = Vec::new(); + while !inner.is_empty() { + arr.push(inner.parse::()?); + inner.parse::().ok(); + } + Ok(Self(arr)) + } +} + +impl ToString for StrArray { + fn to_string(&self) -> String { + let mut s = String::new(); + s.push('['); + for (i, lit) in self.0.iter().enumerate() { + if i > 0 { + s.push_str(", "); + } + s.push('"'); + s.push_str(&lit.value()); + s.push('"'); + } + s.push(']'); + s + } +} + +impl Parse for OapiOptions { + fn parse(input: ParseStream) -> syn::Result { + let mut this = Self { + summary: None, + description: None, + id: None, + hidden: None, + tags: None, + security: None, + responses: None, + transform: None, + }; + + while !input.is_empty() { + let ident = input.parse::()?; + let _ = input.parse::()?; + match ident.to_string().as_str() { + "summary" => this.summary = Some((ident, input.parse()?)), + "description" => this.description = Some((ident, input.parse()?)), + "id" => this.id = Some((ident, input.parse()?)), + "hidden" => this.hidden = Some((ident, input.parse()?)), + "tags" => this.tags = Some((ident, input.parse()?)), + "security" => this.security = Some((ident, input.parse()?)), + "responses" => this.responses = Some((ident, input.parse()?)), + "transform" => this.transform = Some((ident, input.parse()?)), + _ => { + return Err(syn::Error::new( + ident.span(), + "unexpected field, expected one of (summary, description, id, hidden, tags, security, responses, transform)", + )) + } + } + let _ = input.parse::().ok(); + } + + Ok(this) + } +} + +impl OapiOptions { + pub fn merge_with_fn(&mut self, function: &ItemFn) { + if self.description.is_none() { + self.description = doc_iter(&function.attrs) + .skip(2) + .map(|item| item.value()) + .reduce(|mut acc, item| { + acc.push('\n'); + acc.push_str(&item); + acc + }) + .map(|item| (parse_quote!(description), parse_quote!(#item))) + } + if self.summary.is_none() { + self.summary = doc_iter(&function.attrs) + .next() + .map(|item| (parse_quote!(summary), item.clone())) + } + if self.id.is_none() { + let id = &function.sig.ident; + self.id = Some((parse_quote!(id), LitStr::new(&id.to_string(), id.span()))); + } + } +} + +fn doc_iter(attrs: &[Attribute]) -> impl Iterator + '_ { + attrs + .iter() + .filter(|attr| attr.path().is_ident("doc")) + .map(|attr| { + let Meta::NameValue(meta) = &attr.meta else { + panic!("doc attribute is not a name-value attribute"); + }; + let Expr::Lit(lit) = &meta.value else { + panic!("doc attribute is not a string literal"); + }; + let Lit::Str(lit_str) = &lit.lit else { + panic!("doc attribute is not a string literal"); + }; + lit_str + }) +} + +pub struct Route { + pub method: Method, + pub path_params: Vec<(Slash, PathParam)>, + pub query_params: Vec, + pub state: Option, + pub route_lit: LitStr, + pub oapi_options: Option, +} + +impl Parse for Route { + fn parse(input: ParseStream) -> syn::Result { + let method = input.parse::()?; + let route_lit = input.parse::()?; + let route_parser = RouteParser::new(route_lit.clone())?; + let state = match input.parse::() { + Ok(_) => Some(input.parse::()?), + Err(_) => None, + }; + let oapi_options = input + .peek(Brace) + .then(|| { + let inner; + braced!(inner in input); + inner.parse::() + }) + .transpose()?; + + Ok(Route { + method, + path_params: route_parser.path_params, + query_params: route_parser.query_params, + state, + route_lit, + oapi_options, + }) + } +} + +pub enum Method { + Get(Span), + Post(Span), + Put(Span), + Delete(Span), + Head(Span), + Connect(Span), + Options(Span), + Trace(Span), +} + +impl Parse for Method { + fn parse(input: ParseStream) -> syn::Result { + let ident = input.parse::()?; + match ident.to_string().to_uppercase().as_str() { + "GET" => Ok(Self::Get(ident.span())), + "POST" => Ok(Self::Post(ident.span())), + "PUT" => Ok(Self::Put(ident.span())), + "DELETE" => Ok(Self::Delete(ident.span())), + "HEAD" => Ok(Self::Head(ident.span())), + "CONNECT" => Ok(Self::Connect(ident.span())), + "OPTIONS" => Ok(Self::Options(ident.span())), + "TRACE" => Ok(Self::Trace(ident.span())), + _ => Err(input + .error("expected one of (GET, POST, PUT, DELETE, HEAD, CONNECT, OPTIONS, TRACE)")), + } + } +} + +impl Method { + pub fn to_axum_method_name(&self) -> Ident { + match self { + Self::Get(span) => Ident::new("get", *span), + Self::Post(span) => Ident::new("post", *span), + Self::Put(span) => Ident::new("put", *span), + Self::Delete(span) => Ident::new("delete", *span), + Self::Head(span) => Ident::new("head", *span), + Self::Connect(span) => Ident::new("connect", *span), + Self::Options(span) => Ident::new("options", *span), + Self::Trace(span) => Ident::new("trace", *span), + } + } +} + +mod kw { + syn::custom_keyword!(with); +} diff --git a/vendor/axum-typed-routing/.cargo-checksum.json b/vendor/axum-typed-routing/.cargo-checksum.json new file mode 100644 index 0000000..d080b84 --- /dev/null +++ b/vendor/axum-typed-routing/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"97fb5b77f33d2b60a0b82b39fba7ab48f87effdfac5adb5e3df5018d153f8b4b","examples/aide.rs":"a72124d7923cd4dfa76a98bfc10b163c7037048d52819061898c5cfc19c9e0dd","examples/basic.rs":"ef981bcf041580f073808521738f22b864625779c6df628d546a53dbc2e0c95c","src/lib.rs":"3d5a1c1407e5097fd504985b6fb2665b4d1a140db6ed4b15963d64c781a96320","tests/main.rs":"4bed7ddf18d079122144ef8d5d8960cad1f0b51095e23f8c15b00df543c3c49e"},"package":null} \ No newline at end of file diff --git a/vendor/axum-typed-routing/Cargo.toml b/vendor/axum-typed-routing/Cargo.toml new file mode 100644 index 0000000..0e77776 --- /dev/null +++ b/vendor/axum-typed-routing/Cargo.toml @@ -0,0 +1,80 @@ +# 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] +autobenches = false +autobins = false +autoexamples = false +autolib = false +autotests = false +build = false +categories = ["web-programming"] +description = "Typed routing macros for axum" +edition = "2021" +homepage = "https://github.com/jvdwrf/axum-typed-routing" +keywords = ["axum", "handler", "macro", "routing", "typed"] +license = "MIT OR Apache-2.0" +name = "axum-typed-routing" +readme = "../README.md" +repository = "https://github.com/jvdwrf/axum-typed-routing" +version = "0.2.0" + +[package.metadata.docs.rs] +features = ["aide"] + +[features] +aide = ["dep:aide"] +default = [] + +[lib] +name = "axum_typed_routing" +path = "src/lib.rs" + +[[example]] +name = "aide" +path = "examples/aide.rs" + +[[example]] +name = "basic" +path = "examples/basic.rs" + +[[test]] +name = "main" +path = "tests/main.rs" + +[dependencies] +axum = "0.8" +axum-macros = "0.5" + +[dependencies.aide] +features = ["axum"] +optional = true +version = "0.14" + +[dependencies.axum-typed-routing-macros] +path = "../axum-typed-routing-macros" +version = "0.2.0" + +[dev-dependencies] +json = "0.12" +schemars = "0.8" + +[dev-dependencies.axum-test] +features = [] +version = "17" + +[dev-dependencies.serde] +features = ["derive"] +version = "1" + +[dev-dependencies.tokio] +features = ["full"] +version = "1" diff --git a/vendor/axum-typed-routing/examples/aide.rs b/vendor/axum-typed-routing/examples/aide.rs new file mode 100644 index 0000000..b3fd020 --- /dev/null +++ b/vendor/axum-typed-routing/examples/aide.rs @@ -0,0 +1,28 @@ +#![allow(unused)] +use aide::axum::ApiRouter; +use axum::extract::{Json, State}; +use axum_typed_routing::TypedApiRouter; +use axum_typed_routing_macros::api_route; + +#[api_route(GET "/item/:id?amount&offset" { + summary: "Get an item", + description: "Get an item by id", + id: "get-item", + tags: ["items"], + hidden: false +})] +async fn item_handler( + id: u32, + amount: Option, + offset: Option, + State(state): State, + Json(json): Json, +) -> String { + todo!("handle request") +} + +fn main() { + let router: ApiRouter = ApiRouter::new() + .typed_api_route(item_handler) + .with_state("state".to_string()); +} diff --git a/vendor/axum-typed-routing/examples/basic.rs b/vendor/axum-typed-routing/examples/basic.rs new file mode 100644 index 0000000..d86ba11 --- /dev/null +++ b/vendor/axum-typed-routing/examples/basic.rs @@ -0,0 +1,20 @@ +#![allow(unused)] +use axum::extract::{State, Json}; +use axum_typed_routing::{TypedRouter, route}; + +#[route(GET "/item/:id?amount&offset")] +async fn item_handler( + id: u32, + amount: Option, + offset: Option, + State(state): State, + Json(json): Json, +) -> String { + todo!("handle request") +} + +fn main() { + let router: axum::Router = axum::Router::new() + .typed_route(item_handler) + .with_state("state".to_string()); +} \ No newline at end of file diff --git a/vendor/axum-typed-routing/src/lib.rs b/vendor/axum-typed-routing/src/lib.rs new file mode 100644 index 0000000..755d425 --- /dev/null +++ b/vendor/axum-typed-routing/src/lib.rs @@ -0,0 +1,125 @@ +//! +//! ## Basic usage +//! The following example demonstrates the basic usage of the library. +//! On top of any regular handler, you can add the [`route`] macro to create a typed route. +//! Any path- or query-parameters in the url will be type-checked at compile-time, and properly +//! extracted into the handler. +//! +//! The following example shows how the path parameter `id`, and query parameters `amount` and +//! `offset` are type-checked and extracted into the handler. +//! +//! ``` +#![doc = include_str!("../examples/basic.rs")] +//! ``` +//! +//! Some valid url's as get-methods are: +//! - `/item/1?amount=2&offset=3` +//! - `/item/1?amount=2` +//! - `/item/1?offset=3` +//! - `/item/500` +//! +//! By marking the `amount` and `offset` parameters as `Option`, they become optional. +//! +//! ## Example with `aide` +//! When the `aide` feature is enabled, it's possible to automatically generate OpenAPI +//! documentation for the routes. The [`api_route`] macro is used in place of the [`route`] macro. +//! +//! Please read the [`aide`] documentation for more information on usage. +//! ``` +#![doc = include_str!("../examples/aide.rs")] +//! ``` + +use axum::routing::MethodRouter; + +type TypedHandler = fn() -> (&'static str, MethodRouter); +pub use axum_typed_routing_macros::route; + +/// A trait that allows typed routes, created with the [`route`] macro to +/// be added to an axum router. +/// +/// Typed handlers are of the form `fn() -> (&'static str, MethodRouter)`, where +/// `S` is the state type. The first element of the tuple is the path, and the second +/// is the method router. +pub trait TypedRouter: Sized { + /// The state type of the router. + type State: Clone + Send + Sync + 'static; + + /// Add a typed route to the router, usually created with the [`route`] macro. + /// + /// Typed handlers are of the form `fn() -> (&'static str, MethodRouter)`, where + /// `S` is the state type. The first element of the tuple is the path, and the second + /// is the method router. + fn typed_route(self, handler: TypedHandler) -> Self; +} + +impl TypedRouter for axum::Router +where + S: Send + Sync + Clone + 'static, +{ + type State = S; + + fn typed_route(self, handler: TypedHandler) -> Self { + let (path, method_router) = handler(); + self.route(path, method_router) + } +} + +#[cfg(feature = "aide")] +pub use aide_support::*; +#[cfg(feature = "aide")] +mod aide_support { + use crate::{TypedHandler, TypedRouter}; + use aide::{ + axum::{routing::ApiMethodRouter, ApiRouter}, + transform::TransformPathItem, + }; + + type TypedApiHandler = fn() -> (&'static str, ApiMethodRouter); + + pub use axum_typed_routing_macros::api_route; + + impl TypedRouter for ApiRouter + where + S: Send + Sync + Clone + 'static, + { + type State = S; + + fn typed_route(self, handler: TypedHandler) -> Self { + let (path, method_router) = handler(); + self.route(path, method_router) + } + } + + /// Same as [`TypedRouter`], but with support for `aide`. + pub trait TypedApiRouter: TypedRouter { + /// Same as [`TypedRouter::typed_route`], but with support for `aide`. + fn typed_api_route(self, handler: TypedApiHandler) -> Self; + + /// Same as [`TypedApiRouter::typed_api_route`], but with a custom path transform for + /// use with `aide`. + fn typed_api_route_with( + self, + handler: TypedApiHandler, + transform: impl FnOnce(TransformPathItem) -> TransformPathItem, + ) -> Self; + } + + impl TypedApiRouter for ApiRouter + where + S: Send + Sync + Clone + 'static, + { + fn typed_api_route(self, handler: TypedApiHandler) -> Self { + let (path, method_router) = handler(); + self.api_route(path, method_router) + } + + fn typed_api_route_with( + self, + handler: TypedApiHandler, + transform: impl FnOnce(TransformPathItem) -> TransformPathItem, + ) -> Self { + let (path, method_router) = handler(); + self.api_route_with(path, method_router, transform) + } + } +} diff --git a/vendor/axum-typed-routing/tests/main.rs b/vendor/axum-typed-routing/tests/main.rs new file mode 100644 index 0000000..a4680f7 --- /dev/null +++ b/vendor/axum-typed-routing/tests/main.rs @@ -0,0 +1,234 @@ +#![allow(unused)] +#![allow(clippy::extra_unused_type_parameters)] + +use std::net::TcpListener; + +use axum::{ + extract::{Path, State}, + routing::get, + Form, Json, +}; +use axum_test::TestServer; +use axum_typed_routing::TypedRouter; +use axum_typed_routing_macros::route; + +/// This is a handler that is documented! +#[route(GET "/hello/:id?user_id&name")] +async fn generic_handler_with_complex_options( + mut id: u32, + user_id: String, + name: String, + State(state): State, + hello: State, + Json(mut json): Json, +) -> String { + format!("Hello, {id} - {user_id} - {name}!") +} + +#[route(POST "/one")] +async fn one(state: State) -> String { + String::from("Hello!") +} + +#[route(POST "/two")] +async fn two() -> String { + String::from("Hello!") +} + +#[route(GET "/three/:id")] +async fn three(id: u32) -> String { + format!("Hello {id}!") +} + +#[route(GET "/four?id")] +async fn four(id: u32) -> String { + format!("Hello {id:?}!") + // String::from("Hello 123!") +} + +// Tests that hyphens are allowed in route names +#[route(GET "/foo-bar")] +async fn foo_bar() {} + +#[tokio::test] +async fn test_normal() { + let router: axum::Router = axum::Router::new() + .typed_route(generic_handler_with_complex_options::) + .typed_route(one) + .with_state("state".to_string()) + .typed_route(two) + .typed_route(three) + .typed_route(four); + + let server = TestServer::new(router).unwrap(); + + let response = server.post("/one").await; + response.assert_status_ok(); + response.assert_text("Hello!"); + + let response = server.post("/two").await; + response.assert_status_ok(); + response.assert_text("Hello!"); + + let response = server.get("/three/123").await; + response.assert_status_ok(); + response.assert_text("Hello 123!"); + + let response = server.get("/four").add_query_param("id", 123).await; + response.assert_status_ok(); + response.assert_text("Hello 123!"); + + let response = server + .get("/hello/123") + .add_query_param("user_id", 321.to_string()) + .add_query_param("name", "John".to_string()) + .json(&100) + .await; + response.assert_status_ok(); + response.assert_text("Hello, 123 - 321 - John!"); + + let (path, method_router) = generic_handler_with_complex_options::(); + assert_eq!(path, "/hello/{id}"); +} + +#[route(GET "/*")] +async fn wildcard() {} + +#[route(GET "/*capture")] +async fn wildcard_capture(capture: String) -> Json { + Json(capture) +} + +#[route(GET "/")] +async fn root() {} + +#[tokio::test] +async fn test_wildcard() { + let router: axum::Router = axum::Router::new().typed_route(wildcard_capture); + + let server = TestServer::new(router).unwrap(); + + let response = server.get("/foo/bar").await; + response.assert_status_ok(); + assert_eq!(response.json::(), "foo/bar"); +} + + +#[cfg(feature = "aide")] +mod aide_support { + use super::*; + use aide::{axum::ApiRouter, openapi::OpenApi, transform::TransformOperation}; + use axum_typed_routing::TypedApiRouter; + use axum_typed_routing_macros::api_route; + + /// get-summary + /// + /// get-description + #[api_route(GET "/hello")] + async fn get_hello(state: State) -> String { + String::from("Hello!") + } + + /// post-summary + /// + /// post-description + #[api_route(POST "/hello")] + async fn post_hello(state: State) -> String { + String::from("Hello!") + } + + #[test] + fn test_aide() { + let router: aide::axum::ApiRouter = aide::axum::ApiRouter::new() + .typed_route(one) + .typed_api_route(get_hello) + .with_state("state".to_string()); + + let (path, method_router) = get_hello(); + assert_eq!(path, "/hello"); + + let (path, method_router) = post_hello(); + assert_eq!(path, "/hello"); + } + + #[test] + fn summary_and_description_are_generated_from_doc_comments() { + let router = ApiRouter::new() + .typed_api_route(get_hello) + .typed_api_route(post_hello); + let mut api = OpenApi::default(); + router.finish_api(&mut api); + + let get_op = path_item(&api, "/hello").get.as_ref().unwrap(); + let post_op = path_item(&api, "/hello").post.as_ref().unwrap(); + + assert_eq!(get_op.summary, Some(" get-summary".to_string())); + assert_eq!(get_op.description, Some(" get-description".to_string())); + assert!(get_op.tags.is_empty()); + + assert_eq!(post_op.summary, Some(" post-summary".to_string())); + assert_eq!(post_op.description, Some(" post-description".to_string())); + assert!(post_op.tags.is_empty()); + } + + /// unused-summary + /// + /// unused-description + #[api_route(GET "/hello" { + summary: "MySummary", + description: "MyDescription", + hidden: false, + id: "MyRoute", + tags: ["MyTag1", "MyTag2"], + security: { + "MySecurity1": ["MyScope1", "MyScope2"], + "MySecurity2": [], + }, + responses: { + 300: String, + }, + transform: |x| x.summary("OverriddenSummary"), + })] + async fn get_gello_with_attributes(state: State) -> String { + String::from("Hello!") + } + + #[test] + fn generated_from_attributes() { + let router = ApiRouter::new().typed_api_route(get_gello_with_attributes); + let mut api = OpenApi::default(); + router.finish_api(&mut api); + + let get_op = path_item(&api, "/hello").get.as_ref().unwrap(); + + assert_eq!(get_op.summary, Some("OverriddenSummary".to_string())); + assert_eq!(get_op.description, Some("MyDescription".to_string())); + assert_eq!( + get_op.tags, + vec!["MyTag1".to_string(), "MyTag2".to_string()] + ); + assert_eq!(get_op.operation_id, Some("MyRoute".to_string())); + } + + /// summary + /// + /// description + /// description + #[api_route(GET "/hello")] + async fn get_gello_without_attributes(state: State) -> String { + String::from("Hello!") + } + + fn path_item<'a>(api: &'a OpenApi, path: &str) -> &'a aide::openapi::PathItem { + api.paths + .as_ref() + .unwrap() + .iter() + .find(|(p, _)| *p == path) + .unwrap() + .1 + .as_item() + .unwrap() + } +} +