wadl-0.3.2/.cargo_vcs_info.json0000644000000001360000000000100117650ustar { "git": { "sha1": "786778e3b685c2921d7df9cb0ace29ba3a56e3bd" }, "path_in_vcs": "" }wadl-0.3.2/.github/CODEOWNERS000064400000000000000000000000121046102023000135010ustar 00000000000000* @jelmer wadl-0.3.2/.github/FUNDING.yml000064400000000000000000000000171046102023000137300ustar 00000000000000github: jelmer wadl-0.3.2/.github/dependabot.yml000064400000000000000000000006251046102023000147500ustar 00000000000000# Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "cargo" directory: "/" schedule: interval: "weekly" rebase-strategy: "disabled" - package-ecosystem: "github-actions" directory: "/" schedule: interval: weekly wadl-0.3.2/.github/workflows/rust.yml000064400000000000000000000007711046102023000156770ustar 00000000000000name: Rust on: push: pull_request: env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install cargo-all-features run: cargo install cargo-all-features - name: Build run: cargo build --verbose env: RUSTFLAGS: -Dwarnings - name: Run tests run: cargo test-all-features --verbose env: RUSTFLAGS: -Dwarnings - name: Check formatting run: cargo fmt -- --check wadl-0.3.2/.gitignore000064400000000000000000000000121046102023000125360ustar 00000000000000*~ target wadl-0.3.2/Cargo.lock0000644000001375150000000000100077540ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anstream" version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[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 = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cc" version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" dependencies = [ "shlex", ] [[package]] name = "cesu8" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", "quote", "syn 2.0.77", ] [[package]] name = "clap_lex" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "colorchoice" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "combine" version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "memchr", ] [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "encoding_rs" version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] [[package]] name = "env_filter" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", "env_filter", "humantime", "log", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "fastrand" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[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 = "futf" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" dependencies = [ "mac", "new_debug_unreachable", ] [[package]] name = "futures-channel" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-io" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-sink" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", "futures-io", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gimli" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" [[package]] name = "h2" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "html2md" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be92446e11d68f5d71367d571c229d09ced1f24ab6d08ea0bff329d5f6c0b2a3" dependencies = [ "html5ever", "jni", "lazy_static", "markup5ever_rcdom", "percent-encoding", "regex", ] [[package]] name = "html5ever" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" dependencies = [ "log", "mac", "markup5ever", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "http" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[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.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", "futures-util", "h2", "http", "http-body", "httparse", "itoa", "pin-project-lite", "smallvec", "tokio", "want", ] [[package]] name = "hyper-rustls" version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http", "hyper", "hyper-util", "rustls", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", ] [[package]] name = "hyper-tls" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", "hyper", "hyper-util", "native-tls", "tokio", "tokio-native-tls", "tower-service", ] [[package]] name = "hyper-util" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" dependencies = [ "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", "pin-project-lite", "socket2", "tokio", "tower", "tower-service", "tracing", ] [[package]] name = "idna" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "ipnet" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" [[package]] name = "iri-string" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c25163201be6ded9e686703e85532f8f852ea1f92ba625cb3c51f7fe6d07a4a" dependencies = [ "memchr", "serde", ] [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jni" version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" dependencies = [ "cesu8", "combine", "jni-sys", "log", "thiserror", "walkdir", ] [[package]] name = "jni-sys" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] [[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.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[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.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "mac" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" [[package]] name = "maplit" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "markup5ever" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" dependencies = [ "log", "phf", "phf_codegen", "string_cache", "string_cache_codegen", "tendril", ] [[package]] name = "markup5ever_rcdom" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9521dd6750f8e80ee6c53d65e2e4656d7de37064f3a7a5d2d11d05df93839c2" dependencies = [ "html5ever", "markup5ever", "tendril", "xml5ever", ] [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", ] [[package]] name = "mio" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi", "libc", "wasi", "windows-sys 0.52.0", ] [[package]] name = "native-tls" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "new_debug_unreachable" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "object" version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", "syn 2.0.77", ] [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[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 = "phf" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" dependencies = [ "phf_shared", ] [[package]] name = "phf_codegen" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" dependencies = [ "phf_generator", "phf_shared", ] [[package]] name = "phf_generator" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" dependencies = [ "phf_shared", "rand", ] [[package]] name = "phf_shared" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" dependencies = [ "siphasher", ] [[package]] name = "pin-project" version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", "syn 2.0.77", ] [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ "zerocopy", ] [[package]] name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "redox_syscall" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ "base64", "bytes", "encoding_rs", "futures-channel", "futures-core", "futures-util", "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", "mime_guess", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "windows-registry", ] [[package]] name = "ring" version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", "getrandom", "libc", "spin", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "rustls" version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ "once_cell", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pemfile" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ "base64", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] name = "rustls-webpki" version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "schannel" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "serde" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", "syn 2.0.77", ] [[package]] name = "serde_json" version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", "ryu", "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 = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "siphasher" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[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.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "string_cache" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" dependencies = [ "new_debug_unreachable", "once_cell", "parking_lot", "phf_shared", "precomputed-hash", "serde", ] [[package]] name = "string_cache_codegen" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" dependencies = [ "futures-core", ] [[package]] name = "system-configuration" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "tempfile" version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand", "once_cell", "rustix", "windows-sys 0.59.0", ] [[package]] name = "tendril" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" dependencies = [ "futf", "mac", "utf-8", ] [[package]] name = "thiserror" version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", "syn 2.0.77", ] [[package]] name = "tinyvec" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", "libc", "mio", "pin-project-lite", "socket2", "windows-sys 0.52.0", ] [[package]] name = "tokio-native-tls" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", ] [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ "rustls", "rustls-pki-types", "tokio", ] [[package]] name = "tokio-util" version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "tower" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", "pin-project", "pin-project-lite", "tokio", "tower-layer", "tower-service", ] [[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.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 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.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] [[package]] name = "unicode-bidi" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "utf-8" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wadl" version = "0.3.2" dependencies = [ "clap", "env_logger", "form_urlencoded", "html2md", "iri-string", "lazy_static", "log", "maplit", "mime", "proc-macro2", "quote", "reqwest", "serde_json", "syn 2.0.77", "url", "xmltree", ] [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ "try-lock", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn 2.0.77", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "windows-registry" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result", "windows-strings", "windows-targets", ] [[package]] name = "windows-result" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ "windows-targets", ] [[package]] name = "windows-strings" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ "windows-result", "windows-targets", ] [[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-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 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 = "xml-rs" version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26" [[package]] name = "xml5ever" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4034e1d05af98b51ad7214527730626f019682d797ba38b51689212118d8e650" dependencies = [ "log", "mac", "markup5ever", ] [[package]] name = "xmltree" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b619f8c85654798007fb10afa5125590b43b088c225a25fc2fec100a9fad0fc6" dependencies = [ "xml-rs", ] [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", "zerocopy-derive", ] [[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 2.0.77", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" wadl-0.3.2/Cargo.toml0000644000000041530000000000100077660ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "wadl" version = "0.3.2" authors = ["Jelmer Vernooij "] build = false autobins = false autoexamples = false autotests = false autobenches = false default-run = "wadlc" description = "A WADL parser for Rust" readme = "README.md" license = "Apache-2.0" repository = "https://github.com/jelmer/wadl" [lib] name = "wadl" path = "src/lib.rs" [[bin]] name = "wadlc" path = "src/bin/wadlc.rs" required-features = ["cli"] [[bin]] name = "wadl-ast" path = "src/bin/wadl-ast.rs" required-features = ["cli"] [[test]] name = "parsing_tests" path = "tests/parsing_tests.rs" [dependencies.clap] version = "4.5.18" features = [ "derive", "env", ] optional = true [dependencies.env_logger] version = "0.11.5" optional = true [dependencies.form_urlencoded] version = "1.2.1" [dependencies.html2md] version = "0.2.14" optional = true [dependencies.iri-string] version = "0.7.5" features = ["std"] [dependencies.lazy_static] version = "1.5.0" [dependencies.log] version = "0.4.22" [dependencies.mime] version = "0.3.17" [dependencies.proc-macro2] version = "1" optional = true [dependencies.quote] version = "1" optional = true [dependencies.reqwest] version = "0.12.7" features = [ "blocking", "json", "multipart", ] [dependencies.serde_json] version = "1.0.128" [dependencies.syn] version = "2" optional = true [dependencies.url] version = "2.5" [dependencies.xmltree] version = "0.11.0" [dev-dependencies.maplit] version = "1.0.2" [features] cli = [ "dep:clap", "dep:env_logger", "codegen", ] codegen = [ "dep:proc-macro2", "dep:quote", "dep:syn", "dep:html2md", ] default = ["cli"] wadl-0.3.2/Cargo.toml.orig0000644000000022340000000000100107230ustar [package] name = "wadl" version = "0.3.2" edition = "2021" license = "Apache-2.0" description = "A WADL parser for Rust" repository = "https://github.com/jelmer/wadl" authors = ["Jelmer Vernooij "] default-run = "wadlc" [dependencies] clap = { version = "4.5.18", features = ["derive", "env"], optional = true } env_logger = { version = "0.11.5", optional = true } form_urlencoded = "1.2.1" html2md = { version = "0.2.14", optional = true } lazy_static = "1.5.0" log = "0.4.22" mime = "0.3.17" proc-macro2 = { version = "1", optional = true } quote = { version = "1", optional = true } reqwest = { version = "0.12.7", features = ["blocking", "json", "multipart"] } serde_json = "1.0.128" syn = { version = "2", optional = true } url = "2.5" xmltree = "0.11.0" iri-string = { version = "0.7.5", features = ["std"] } [features] default = ["cli"] codegen = ["dep:proc-macro2", "dep:quote", "dep:syn", "dep:html2md"] cli = ["dep:clap", "dep:env_logger", "codegen"] [[bin]] name = "wadlc" path = "src/bin/wadlc.rs" required-features = ["cli"] [[bin]] name = "wadl-ast" path = "src/bin/wadl-ast.rs" required-features = ["cli"] [dev-dependencies] maplit = "1.0.2" wadl-0.3.2/Cargo.toml.orig000064400000000000000000000022341046102023000134450ustar 00000000000000[package] name = "wadl" version = "0.3.2" edition = "2021" license = "Apache-2.0" description = "A WADL parser for Rust" repository = "https://github.com/jelmer/wadl" authors = ["Jelmer Vernooij "] default-run = "wadlc" [dependencies] clap = { version = "4.5.18", features = ["derive", "env"], optional = true } env_logger = { version = "0.11.5", optional = true } form_urlencoded = "1.2.1" html2md = { version = "0.2.14", optional = true } lazy_static = "1.5.0" log = "0.4.22" mime = "0.3.17" proc-macro2 = { version = "1", optional = true } quote = { version = "1", optional = true } reqwest = { version = "0.12.7", features = ["blocking", "json", "multipart"] } serde_json = "1.0.128" syn = { version = "2", optional = true } url = "2.5" xmltree = "0.11.0" iri-string = { version = "0.7.5", features = ["std"] } [features] default = ["cli"] codegen = ["dep:proc-macro2", "dep:quote", "dep:syn", "dep:html2md"] cli = ["dep:clap", "dep:env_logger", "codegen"] [[bin]] name = "wadlc" path = "src/bin/wadlc.rs" required-features = ["cli"] [[bin]] name = "wadl-ast" path = "src/bin/wadl-ast.rs" required-features = ["cli"] [dev-dependencies] maplit = "1.0.2" wadl-0.3.2/README.md000064400000000000000000000024531046102023000120400ustar 00000000000000This crate contains a parser for the [Web Application Description Language (WADL)](https://www.w3.org/submissions/wadl/). It can also generate basic rust bindings based on WADL files, if the ``codegen`` feature is enabled. ## Example usage ### Simply parsing the ast ```rust let app: wadl::ast::Application = wadl::parse_file("1.0.wadl").unwrap(); println!("{:#}", app); ``` ### Generating code Create a build.rs that generates rust code: ```rust fn main() { let config = wadl::codegen::Config { // Set extra options here to influence code generation, // e.g. to rename functions. ..Default::default() }; let wadl = std::fs::read_to_string( concat!(env!("CARGO_MANIFEST_DIR"), "/x.wadl")).unwrap(); let wadl_app = wadl::parse_string(wadl.as_str()).unwrap(); let code = wadl::codegen::generate(&wadl_app, &config); let target_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()) .canonicalize() .unwrap(); let generated = target_dir.join("generated"); std::fs::create_dir_all(&generated).unwrap(); let path = generated.join("x.wadl"); std::fs::write(path, code).unwrap(); } ``` Then, you can include the generated code from your rust code: ```rust include!(concat!(env!("OUT_DIR"), "/generated/x.rs")); ``` wadl-0.3.2/disperse.conf000064400000000000000000000001231046102023000132360ustar 00000000000000timeout_days: 5 tag_name: "v$VERSION" github_url: "https://github.com/jelmer/wadl" wadl-0.3.2/src/ast.rs000064400000000000000000000450271046102023000125110ustar 00000000000000//! Abstract syntax tree for WADL documents. use iri_string::spec::IriSpec; use iri_string::types::RiReferenceString; use std::collections::HashMap; use url::Url; /// Identifier for a resource, method, parameter, etc. pub type Id = String; /// Parameter style #[derive(Debug, PartialEq, Eq, Clone)] pub enum ParamStyle { /// Specifies a component of the representation formatted as a string encoding of the parameter value according to the rules of the media type. Plain, /// Specifies a matrix URI component. Matrix, /// Specifies a URI query parameter represented according to the rules for the query component media type specified by the queryType attribute. Query, /// Specifies a HTTP header that pertains to the HTTP request (resource or request) or HTTP response (response) Header, /// The parameter is represented as a string encoding of the parameter value and is substituted into the value of the path attribute of the resource element as described in section 2.6.1. Template, } /// A WADL application. #[derive(Debug)] pub struct Application { /// Resources defined at the application level. pub resources: Vec, /// Resource types defined at the application level. pub resource_types: Vec, /// Documentation for the application. pub docs: Vec, /// List of grammars pub grammars: Vec, /// Representations defined at the application level. pub representations: Vec, } impl Application { /// Get a resource type by its ID. pub fn get_resource_type_by_id(&self, id: &str) -> Option<&ResourceType> { self.resource_types.iter().find(|rt| id == rt.id.as_str()) } /// Get a resource type by its href, which may be a fragment or a full URL. pub fn get_resource_type_by_href(&self, href: &Url) -> Option<&ResourceType> { // TODO(jelmer): Check that href matches us? if let Some(fragment) = href.fragment() { self.get_resource_type_by_id(fragment) } else { None } } /// Iterate over all resources defined in this application. pub fn iter_resources(&self) -> impl Iterator { self.resources .iter() .flat_map(|rs| rs.resources.iter().map(|r| (r.url(rs.base.as_ref()), r))) } /// Get a resource by its ID. pub fn get_resource_by_href(&self, href: &Url) -> Option<&Resource> { self.iter_resources() .find(|(url, _)| url == href) .map(|(_, r)| r) } /// Iterate over all types defined in this application. pub fn iter_referenced_types(&self) -> impl Iterator + '_ { self.iter_resources() .flat_map(|(_u, r)| r.iter_referenced_types()) .chain( self.resource_types .iter() .flat_map(|rt| rt.iter_referenced_types()), ) } /// Iterate over all parameters defined in this application. pub fn iter_all_params(&self) -> impl Iterator { self.iter_resources() .flat_map(|(_u, r)| r.iter_all_params()) .chain( self.resource_types .iter() .flat_map(|rt| rt.iter_all_params()), ) .chain( self.representations .iter() .flat_map(|r| r.iter_all_params()), ) } } impl std::str::FromStr for Application { type Err = crate::parse::Error; fn from_str(s: &str) -> Result { crate::parse::parse_string(s) } } #[derive(Debug)] /// A collection of resources. pub struct Resources { /// The base URL for the resources. pub base: Option, /// The resources defined at this level. pub resources: Vec, } #[derive(Debug)] /// A grammar pub struct Grammar { /// The href of the grammar. pub href: RiReferenceString, } #[derive(Debug, Clone, PartialEq, Eq)] /// A reference to a resource type. pub enum ResourceTypeRef { /// A reference to a resource type defined in the same document. Id(Id), /// A reference to a resource type defined in another document. Link(Url), /// An empty reference. Empty, } impl std::str::FromStr for ResourceTypeRef { type Err = String; fn from_str(s: &str) -> Result { match s { "" => Ok(ResourceTypeRef::Empty), s => { if let Some(s) = s.strip_prefix('#') { Ok(ResourceTypeRef::Id(s.to_string())) } else { Ok(ResourceTypeRef::Link( s.parse().map_err(|e| format!("{}", e))?, )) } } } } } #[test] fn parse_resource_type_ref() { use crate::ast::ResourceTypeRef::*; use std::str::FromStr; assert_eq!(Empty, ResourceTypeRef::from_str("").unwrap()); assert_eq!( Id("id".to_owned()), ResourceTypeRef::from_str("#id").unwrap() ); assert_eq!( Link(Url::parse("https://example.com").unwrap()), ResourceTypeRef::from_str("https://example.com").unwrap() ); } impl ResourceTypeRef { /// Return the ID of the resource type reference. pub fn id(&self) -> Option<&str> { match self { ResourceTypeRef::Id(id) => Some(id), ResourceTypeRef::Link(l) => l.fragment(), ResourceTypeRef::Empty => None, } } } #[derive(Debug, Clone, PartialEq, Eq)] /// An option element defines one of a set of possible values for the parameter represented by its parent param element. pub struct Options(HashMap>); impl std::hash::Hash for Options { fn hash(&self, state: &mut H) { let mut items = self.0.iter().collect::>(); items.sort(); for (key, value) in items { key.hash(state); value.hash(state); } } } impl Options { /// Create a new options object pub fn new() -> Self { Self(HashMap::new()) } /// Number of items in this Options pub fn len(&self) -> usize { self.0.len() } /// Iterate over all items in this Options pub fn iter(&self) -> impl Iterator)> { self.0.iter().map(|(k, v)| (k.as_str(), v.as_ref())) } /// Return an iterator over all keys pub fn keys(&self) -> impl Iterator { self.0.keys().map(|k| k.as_str()) } /// Check if this Options is empty pub fn is_empty(&self) -> bool { self.0.is_empty() } /// Insert a new key-value pair into this Options pub fn insert(&mut self, key: String, value: Option) { self.0.insert(key, value); } /// Get the value for a key pub fn get(&self, key: &str) -> Option<&Option> { self.0.get(key) } } impl From> for Options { fn from(v: Vec) -> Self { Self(v.into_iter().map(|s| (s, None)).collect()) } } impl From> for Options { fn from(v: Vec<&str>) -> Self { Self(v.into_iter().map(|s| (s.to_string(), None)).collect()) } } #[derive(Debug, Clone)] /// A resource pub struct Resource { /// The ID of the resource. pub id: Option, /// The path of the resource. pub path: Option, /// The types of the resource. pub r#type: Vec, /// The query type of the resource. pub query_type: mime::Mime, /// The methods defined at this level. pub methods: Vec, /// The docs for the resource. pub docs: Vec, /// Sub-resources of this resource. pub subresources: Vec, /// The params for this resource. pub params: Vec, } impl Resource { /// Get the URL of this resource. pub fn url(&self, base_url: Option<&Url>) -> Url { if let Some(base_url) = base_url { base_url.join(self.path.as_ref().unwrap()).unwrap() } else { Url::parse(self.path.as_ref().unwrap()).unwrap() } } /// Iterate over all parameters defined in this resource. pub(crate) fn iter_all_params(&self) -> impl Iterator { let mut params = self.params.iter().collect::>(); params.extend(self.subresources.iter().flat_map(|r| r.iter_all_params())); params.extend(self.methods.iter().flat_map(|m| m.iter_all_params())); params.into_iter() } /// Iterate over all types referenced by this resource. pub fn iter_referenced_types(&self) -> impl Iterator + '_ { self.iter_all_params().map(|p| p.r#type.clone()) } } #[test] fn test_resource_url() { let r = Resource { id: None, path: Some("/foo".to_string()), r#type: vec![], query_type: mime::APPLICATION_JSON, methods: vec![], docs: vec![], subresources: vec![], params: vec![], }; assert_eq!( r.url(Some(&Url::parse("http://example.com").unwrap())), Url::parse("http://example.com/foo").unwrap() ); assert_eq!( r.url(Some(&Url::parse("http://example.com/bar").unwrap())), Url::parse("http://example.com/foo").unwrap() ); } #[derive(Debug, Clone)] /// A HTTP Method pub struct Method { /// Identifier of this method pub id: Id, /// The name of the method. pub name: String, /// The docs for the method. pub docs: Vec, /// The request for the method. pub request: Request, /// The responses for the method. pub responses: Vec, } impl Method { fn iter_all_params(&self) -> impl Iterator { self.request .iter_all_params() .chain(self.responses.iter().flat_map(|r| r.iter_all_params())) } } #[derive(Debug, Clone, PartialEq, Eq, Default)] /// Documentation pub struct Doc { /// The title of the documentation. pub title: Option, /// The language of the documentation. pub lang: Option, /// The content of the documentation. pub content: String, /// The namespace of the documentation. pub xmlns: Option, } impl Doc { /// Create a new documentation object. pub fn new(content: String) -> Self { Self { content, ..Default::default() } } } #[derive(Debug, Clone, PartialEq, Eq)] /// A link to another resource. pub struct Link { /// The resource type of the link. pub resource_type: Option, /// Optional token that identifies the relationship of the resource identified by the link to /// the resource whose representation the link is embedded in. The value is scoped by the value /// of the ancestor representation element's profile attribute. pub relation: Option, /// An optional token that identifies the relationship of the resource whose representation /// the link is embedded in to the resource identified by the link. This is the reverse /// relationship to that identified by the rel attribute. The value is scoped by the value /// of the ancestor representation element's profile attribute. pub reverse_relation: Option, /// Optional documentation pub doc: Option, } #[derive(Debug, Clone, PartialEq, Eq)] /// A parameter pub struct Param { /// The style of the parameter. pub style: ParamStyle, /// The ID of the parameter. pub id: Option, /// The name of the parameter. pub name: String, /// The type of the parameter. pub r#type: String, /// Path of the parameter. pub path: Option, /// Whether the parameter is required. pub required: bool, /// Whether the parameter is repeating. pub repeating: bool, /// The fixed value of the parameter. pub fixed: Option, /// The documentation for the parameter. pub doc: Option, /// The links for the parameter. pub links: Vec, /// The options for the parameter. pub options: Option, } #[derive(Debug, Clone, PartialEq, Eq, Default)] /// A representation definition pub struct RepresentationDef { /// The ID of the representation. pub id: Option, /// The media type of the representation. pub media_type: Option, /// The element of the representation. pub element: Option, /// The profile of the representation. pub profile: Option, /// The documentation for the representation. pub docs: Vec, /// The parameters for the representation. pub params: Vec, } impl RepresentationDef { fn iter_all_params(&self) -> impl Iterator { self.params.iter() } } #[derive(Debug, Clone)] /// A reference to a representation. pub enum RepresentationRef { /// A reference to a representation defined in the same document. Id(Id), /// A reference to a representation defined in another document. Link(Url), } impl RepresentationRef { /// Return the ID of the representation reference. pub fn id(&self) -> Option<&str> { match self { RepresentationRef::Id(id) => Some(id), RepresentationRef::Link(l) => l.fragment(), } } } #[derive(Debug, Clone)] /// A representation pub enum Representation { /// A reference to a representation defined in the same document. Reference(RepresentationRef), /// A definition of a representation. Definition(RepresentationDef), } impl Representation { /// Return the content type of this representation. pub fn media_type(&self) -> Option<&mime::Mime> { match self { Representation::Reference(_) => None, Representation::Definition(d) => d.media_type.as_ref(), } } /// Return the URL of this representation. pub fn url(&self, base_url: &Url) -> Option { match self { Representation::Reference(RepresentationRef::Id(id)) => { let mut url = base_url.clone(); url.set_fragment(Some(id)); Some(url) } Representation::Reference(RepresentationRef::Link(l)) => Some(l.clone()), Representation::Definition(d) => d.url(base_url), } } /// Return the definition of this representation. pub fn as_def(&self) -> Option<&RepresentationDef> { match self { Representation::Reference(_) => None, Representation::Definition(d) => Some(d), } } /// Iterate over all parameters defined in this representation. pub fn iter_all_params(&self) -> impl Iterator { // TODO: Make this into a proper iterator let params = match self { Representation::Reference(_) => vec![], Representation::Definition(d) => d.iter_all_params().collect::>(), }; params.into_iter() } } #[test] fn test_representation_url() { let base_url = Url::parse("http://example.com").unwrap(); let r = Representation::Reference(RepresentationRef::Id("foo".to_string())); assert_eq!( r.url(&base_url).unwrap(), Url::parse("http://example.com#foo").unwrap() ); let r = Representation::Reference(RepresentationRef::Link( Url::parse("http://example.com#foo").unwrap(), )); assert_eq!( r.url(&base_url).unwrap(), Url::parse("http://example.com#foo").unwrap() ); let r = Representation::Definition(RepresentationDef { id: Some("foo".to_string()), ..Default::default() }); assert_eq!( r.url(&base_url).unwrap(), Url::parse("http://example.com#foo").unwrap() ); } #[test] fn test_representation_id() { let r = Representation::Reference(RepresentationRef::Id("foo".to_string())); assert_eq!(r.as_def(), None); let r = Representation::Definition(RepresentationDef { id: Some("foo".to_string()), ..Default::default() }); assert_eq!(r.as_def().unwrap().id, Some("foo".to_string())); } impl RepresentationDef { /// Fully qualify the URL of this representation. pub fn url(&self, base_url: &Url) -> Option { if let Some(id) = &self.id { let mut url = base_url.clone(); url.set_fragment(Some(id)); Some(url) } else { None } } } #[derive(Debug, Default, Clone)] /// A request pub struct Request { /// The docs for the request. pub docs: Vec, /// The parameters for the request. pub params: Vec, /// The representations for the request. pub representations: Vec, } impl Request { fn iter_all_params(&self) -> impl Iterator { self.params.iter().chain( self.representations .iter() .filter_map(|r| r.as_def().map(|r| r.iter_all_params())) .flatten(), ) } } #[derive(Debug, Clone, Default)] /// A response pub struct Response { /// The docs for the response. pub docs: Vec, /// The parameters for the response. pub params: Vec, /// The status of the response. pub status: Option, /// The representations for the response. pub representations: Vec, } impl Response { fn iter_all_params(&self) -> impl Iterator { self.params.iter().chain( self.representations .iter() .filter_map(|r| r.as_def().map(|r| r.iter_all_params())) .flatten(), ) } } #[derive(Debug)] /// A resource type pub struct ResourceType { /// The ID of the resource type. pub id: Id, /// The query type of the resource type. pub query_type: mime::Mime, /// The methods defined at this level. pub methods: Vec, /// The docs for the resource type. pub docs: Vec, /// The subresources of the resource type. pub subresources: Vec, /// The params for the resource type. pub params: Vec, } impl ResourceType { /// Iterate over all parameters defined in this resource type. pub(crate) fn iter_all_params(&self) -> impl Iterator { self.params .iter() .chain(self.methods.iter().flat_map(|m| m.iter_all_params())) } /// Returns an iterator over all types referenced by this resource type. pub fn iter_referenced_types(&self) -> impl Iterator + '_ { self.iter_all_params().map(|p| p.r#type.clone()) } } wadl-0.3.2/src/codegen.rs000064400000000000000000002024111046102023000133160ustar 00000000000000//! Generate Rust code from WADL files use crate::ast::*; use std::collections::HashMap; /// MIME type for XHTML pub const XHTML_MIME_TYPE: &str = "application/xhtml+xml"; #[allow(missing_docs)] pub enum ParamContainer<'a> { Request(&'a Method, &'a Request), Response(&'a Method, &'a Response), Representation(&'a RepresentationDef), } /// Convert wadl names (with dashes) to camel-case Rust names pub fn camel_case_name(name: &str) -> String { let mut it = name.chars().peekable(); let mut result = String::new(); // Uppercase the first letter if let Some(c) = it.next() { result.push_str(&c.to_uppercase().collect::()); } while it.peek().is_some() { let c = it.next().unwrap(); if c == '_' || c == '-' { if let Some(next) = it.next() { result.push_str(&next.to_uppercase().collect::()); } } else { result.push(c); } } result } #[test] fn test_camel_case_name() { assert_eq!(camel_case_name("foo-bar"), "FooBar"); assert_eq!(camel_case_name("foo-bar-baz"), "FooBarBaz"); assert_eq!(camel_case_name("foo-bar-baz-quux"), "FooBarBazQuux"); assert_eq!(camel_case_name("_foo-bar"), "_fooBar"); assert_eq!(camel_case_name("service-root-json"), "ServiceRootJson"); assert_eq!(camel_case_name("get-some-URL"), "GetSomeURL"); } /// Convert wadl names (with dashes) to snake-case Rust names pub fn snake_case_name(name: &str) -> String { let mut name = name.to_string(); name = name.replace('-', "_"); let it = name.chars().peekable(); let mut result = String::new(); let mut started = false; for c in it { if c.is_uppercase() { if !result.is_empty() && !started && !result.ends_with('_') { result.push('_'); started = true; } result.push_str(c.to_lowercase().collect::().as_str()); } else { result.push(c); started = false; } } result } #[test] fn test_snake_case_name() { assert_eq!(snake_case_name("F"), "f"); assert_eq!(snake_case_name("FooBar"), "foo_bar"); assert_eq!(snake_case_name("FooBarBaz"), "foo_bar_baz"); assert_eq!(snake_case_name("FooBarBazQuux"), "foo_bar_baz_quux"); assert_eq!(snake_case_name("_FooBar"), "_foo_bar"); assert_eq!(snake_case_name("ServiceRootJson"), "service_root_json"); assert_eq!(snake_case_name("GetSomeURL"), "get_some_url"); } fn strip_code_examples(input: String) -> String { let mut in_example = false; input .lines() .filter(|line| { if !in_example && (line.starts_with("```python") || *line == "```") { in_example = true; false } else if line.starts_with("```") { in_example = false; false } else { !in_example } }) .collect::>() .join("\n") } #[test] fn test_strip_code_examples() { let input = r#"This is a test ```python def foo(): pass ``` This is another test ```python def bar(): pass ``` "#; let expected = r#"This is a test This is another test"#; assert_eq!(strip_code_examples(input.to_string()), expected); } /// Format the given `Doc` object into a string. /// /// # Arguments /// * `input` - The `Doc` object to format. /// * `config` - The configuration to use. /// /// # Returns /// The formatted string. fn format_doc(input: &Doc, config: &Config) -> String { match input.xmlns.as_ref().map(|x| x.as_str()) { Some("http://www.w3.org/1999/xhtml") => { let mut text = html2md::parse_html(&input.content); if config.strip_code_examples { text = strip_code_examples(text); } text.lines().collect::>().join("\n") } Some(xmlns) => { log::warn!("Unknown xmlns: {}", xmlns); input.content.lines().collect::>().join("\n") } None => input.content.lines().collect::>().join("\n"), } } /// Generate a docstring from the given `Doc` object. /// /// # Arguments /// * `input` - The `Doc` object to generate the docstring from. /// * `indent` - The indentation level to use. /// * `config` - The configuration to use. /// /// # Returns /// A vector of strings, each representing a line of the docstring. pub fn generate_doc(input: &Doc, indent: usize, config: &Config) -> Vec { let mut lines: Vec = vec![]; if let Some(title) = input.title.as_ref() { lines.extend(vec![format!("/// # {}\n", title), "///\n".to_string()]); } let mut text = format_doc(input, config); if let Some(reformat_docstring) = config.reformat_docstring.as_ref() { text = reformat_docstring(&text); } lines.extend( text.lines() .map(|line| format!("///{}{}\n", if line.is_empty() { "" } else { " " }, line)), ); lines .into_iter() .map(|line| format!("{:indent$}{}", "", line, indent = indent * 4)) .collect() } #[test] fn test_format_doc_plain() { let doc = Doc { title: None, lang: None, content: "This is a test".to_string(), xmlns: None, }; assert_eq!( format_doc(&doc, &Config::default()), "This is a test".to_string() ); } #[test] fn test_format_doc_html() { let doc = Doc { title: None, lang: None, content: "

This is a test

".to_string(), xmlns: Some("http://www.w3.org/1999/xhtml".parse().unwrap()), }; assert_eq!( format_doc(&doc, &Config::default()), "This is a test".to_string() ); } #[test] fn test_format_doc_html_link() { let doc = Doc { title: None, lang: None, content: "

This is a test

".to_string(), xmlns: Some("http://www.w3.org/1999/xhtml".parse().unwrap()), }; assert_eq!( format_doc(&doc, &Config::default()), "This is a [test](https://example.com)".to_string() ); } #[test] fn test_generate_doc_plain() { let doc = Doc { title: Some("Foo".to_string()), lang: None, content: "This is a test".to_string(), xmlns: None, }; assert_eq!( generate_doc(&doc, 0, &Config::default()), vec![ "/// # Foo\n".to_string(), "///\n".to_string(), "/// This is a test\n".to_string(), ] ); } #[test] fn test_generate_doc_html() { let doc = Doc { title: Some("Foo".to_string()), lang: None, content: "

This is a test

".to_string(), xmlns: Some("http://www.w3.org/1999/xhtml".parse().unwrap()), }; assert_eq!( generate_doc(&doc, 0, &Config::default()), vec![ "/// # Foo\n".to_string(), "///\n".to_string(), "/// This is a test\n".to_string(), ] ); } #[test] fn test_generate_doc_multiple_lines() { let doc = Doc { title: Some("Foo".to_string()), lang: None, content: "This is a test\n\nThis is another test".to_string(), xmlns: None, }; assert_eq!( generate_doc(&doc, 0, &Config::default()), vec![ "/// # Foo\n".to_string(), "///\n".to_string(), "/// This is a test\n".to_string(), "///\n".to_string(), "/// This is another test\n".to_string(), ] ); } fn generate_resource_type_ref_accessors( field_name: &str, input: &ResourceTypeRef, param: &Param, config: &Config, ) -> Vec { let mut lines = vec![]; if let Some(id) = input.id() { let deprecated = config .deprecated_param .as_ref() .map(|x| x(param)) .unwrap_or(false); if let Some(doc) = param.doc.as_ref() { lines.extend(generate_doc(doc, 1, config)); } let field_type = camel_case_name(id); let mut ret_type = field_type.to_string(); let map_fn = if let Some((map_type, map_fn)) = config .map_type_for_accessor .as_ref() .and_then(|x| x(field_type.as_str())) { ret_type = map_type; Some(map_fn) } else { None }; if !param.required { ret_type = format!("Option<{}>", ret_type); } let accessor_name = if let Some(rename_fn) = config.param_accessor_rename.as_ref() { rename_fn(param.name.as_str(), ret_type.as_str()) } else { None } .unwrap_or_else(|| field_name.to_string()); let visibility = config .accessor_visibility .as_ref() .and_then(|x| x(accessor_name.as_str(), field_type.as_str())) .unwrap_or_else(|| "pub".to_string()); if deprecated { lines.push(" #[deprecated]".to_string()); } lines.push(format!( " {}fn {}(&self) -> {} {{\n", if visibility.is_empty() { "".to_string() } else { format!("{} ", visibility) }, accessor_name, ret_type )); if param.required { if let Some(map_fn) = map_fn { lines.push(format!( " {}({}(self.{}.clone())\n", map_fn, field_type, field_name )); } else { lines.push(format!( " {}(self.{}.clone())\n", field_type, field_name )); } } else { lines.push(format!( " self.{}.as_ref().map(|x| {}(x.clone())){}\n", field_name, field_type, if let Some(map_fn) = map_fn { format!(".map({})", map_fn) } else { "".to_string() } )); } lines.push(" }\n".to_string()); lines.push("\n".to_string()); if deprecated { lines.push(" #[deprecated]".to_string()); } lines.push(format!( " {}fn set_{}(&mut self, value: {}) {{\n", if visibility.is_empty() { "".to_string() } else { format!("{} ", visibility) }, accessor_name, ret_type )); if param.required { lines.push(format!( " self.{} = value.url().clone();\n", field_name )); } else { lines.push(format!( " self.{} = value.map(|x| x.url().clone());\n", field_name )); } lines.push(" }\n".to_string()); if let Some(extend_accessor) = config.extend_accessor.as_ref() { lines.extend(extend_accessor( param, accessor_name.as_str(), ret_type.as_str(), config, )); } } lines } fn generate_representation( input: &RepresentationDef, config: &Config, options_names: &HashMap, ) -> Vec { let mut lines = vec![]; if input.media_type == Some(mime::APPLICATION_JSON) { lines.extend(generate_representation_struct_json( input, config, options_names, )); } else { panic!("Unknown media type: {:?}", input.media_type); } let name = input.id.as_ref().unwrap().as_str(); let name = camel_case_name(name); lines.push(format!("impl {} {{\n", name)); for param in &input.params { let field_name = snake_case_name(param.name.as_str()); // We expect to support multiple types here in the future for link in ¶m.links { if let Some(r) = link.resource_type.as_ref() { lines.extend(generate_resource_type_ref_accessors( &field_name, r, param, config, )); } } } lines.push("}\n".to_string()); lines.push("\n".to_string()); if let Some(generate) = config.generate_representation_traits.as_ref() { lines.extend(generate(input, name.as_str(), input, config).unwrap_or(vec![])); } lines } /// Generate the Rust type for a representation fn resource_type_rust_type(r: &ResourceTypeRef) -> String { if let Some(id) = r.id() { camel_case_name(id) } else { "url::Url".to_string() } } #[test] fn test_resource_type_rust_type() { use std::str::FromStr; let rt = ResourceTypeRef::from_str("https://api.launchpad.net/1.0/#person").unwrap(); assert_eq!(resource_type_rust_type(&rt), "Person"); } fn simple_type_rust_type( container: &ParamContainer, type_name: &str, param: &Param, config: &Config, ) -> (String, Vec) { let tn = if let Some(override_name) = config.override_type_name.as_ref() { override_name(container, type_name, param.name.as_str()) } else { None }; if let Some(tn) = tn { return (tn, vec![]); } match type_name.split_once(':').map_or(type_name, |(_, n)| n) { "date" => ("chrono::NaiveDate".to_string(), vec![]), "dateTime" => ("chrono::DateTime".to_string(), vec![]), "time" => ("(chrono::Time".to_string(), vec![]), "int" => ("i32".to_string(), vec![]), "string" => ("String".to_string(), vec![]), "binary" => ("Vec".to_string(), vec![]), u => panic!("Unknown type: {}", u), } } fn param_rust_type( container: &ParamContainer, param: &Param, config: &Config, resource_type_rust_type: impl Fn(&ResourceTypeRef) -> String, options_names: &HashMap, ) -> (String, Vec) { let (mut param_type, annotations) = if !param.links.is_empty() { if let Some(rt) = param.links[0].resource_type.as_ref() { let name = resource_type_rust_type(rt); if let Some(override_type_name) = config .override_type_name .as_ref() .and_then(|x| x(container, name.as_str(), param.name.as_str())) { (override_type_name, vec![]) } else { (name, vec![]) } } else { ("url::Url".to_string(), vec![]) } } else if let Some(os) = param.options.as_ref() { let options_name = options_names.get(os).unwrap_or_else(|| { panic!("Unknown options {:?} for {}", os, param.name); }); (options_name.clone(), vec![]) } else { simple_type_rust_type(container, param.r#type.as_str(), param, config) }; if param.repeating { param_type = format!("Vec<{}>", param_type); } if !param.required { param_type = format!("Option<{}>", param_type); } (param_type, annotations) } #[test] fn test_param_rust_type() { use std::str::FromStr; let rt = ResourceTypeRef::from_str("https://api.launchpad.net/1.0/#person").unwrap(); let mut param = Param { name: "person".to_string(), r#type: "string".to_string(), required: true, repeating: false, fixed: None, doc: None, options: None, id: None, style: ParamStyle::Plain, path: None, links: vec![crate::ast::Link { resource_type: Some(rt), relation: None, reverse_relation: None, doc: None, }], }; let method = Method { docs: vec![], id: "getPerson".to_string(), name: "getPerson".to_string(), request: Request { docs: vec![], params: vec![param.clone()], representations: vec![], }, responses: vec![Response { status: None, docs: vec![], params: vec![param.clone()], representations: vec![], }], }; let container = ParamContainer::Request(&method, &method.request); let (param_type, _) = param_rust_type( &container, ¶m, &Config::default(), resource_type_rust_type, &HashMap::new(), ); assert_eq!(param_type, "Person"); param.required = false; let (param_type, _) = param_rust_type( &container, ¶m, &Config::default(), resource_type_rust_type, &HashMap::new(), ); assert_eq!(param_type, "Option"); param.repeating = true; param.required = true; let (param_type, _) = param_rust_type( &container, ¶m, &Config::default(), resource_type_rust_type, &HashMap::new(), ); assert_eq!(param_type, "Vec"); param.repeating = false; param.r#type = "string".to_string(); param.links = vec![]; let (param_type, _) = param_rust_type( &container, ¶m, &Config::default(), resource_type_rust_type, &HashMap::new(), ); assert_eq!(param_type, "String"); param.r#type = "binary".to_string(); let (param_type, _) = param_rust_type( &container, ¶m, &Config::default(), resource_type_rust_type, &HashMap::new(), ); assert_eq!(param_type, "Vec"); param.r#type = "xsd:date".to_string(); let (param_type, _) = param_rust_type( &container, ¶m, &Config::default(), resource_type_rust_type, &HashMap::new(), ); assert_eq!(param_type, "chrono::NaiveDate"); param.r#type = "string".to_string(); param.options = Some(Options::from(vec!["one".to_string(), "two".to_string()])); let (param_type, _) = param_rust_type( &container, ¶m, &Config::default(), resource_type_rust_type, &maplit::hashmap! { Options::from(vec!["one".to_string(), "two".to_string()]) => "MyOptions".to_string(), }, ); assert_eq!(param_type, "MyOptions"); } fn readonly_rust_type(name: &str) -> String { if name.starts_with("Option<") && name.ends_with('>') { return format!( "Option<{}>", readonly_rust_type(name[7..name.len() - 1].trim()) ); } match name { "String" => "&str".to_string(), x if x.starts_with("Vec<") && x.ends_with('>') => { format!("&[{}]", x[4..x.len() - 1].trim()) } x if x.starts_with('*') => x[1..].to_string(), x => format!("&{}", x), } } #[test] fn test_readonly_rust_type() { assert_eq!(readonly_rust_type("String"), "&str"); assert_eq!(readonly_rust_type("Vec"), "&[String]"); assert_eq!( readonly_rust_type("Option>"), "Option<&[String]>" ); assert_eq!(readonly_rust_type("Option"), "Option<&str>"); assert_eq!(readonly_rust_type("usize"), "&usize"); } fn representation_rust_type(r: &RepresentationRef) -> String { if let Some(id) = r.id() { camel_case_name(id) } else { "serde_json::Value".to_string() } } fn escape_rust_reserved(name: &str) -> &str { match name { "type" => "r#type", "match" => "r#match", "move" => "r#move", "use" => "r#use", "loop" => "r#loop", "continue" => "r#continue", "break" => "r#break", "fn" => "r#fn", "struct" => "r#struct", "enum" => "r#enum", "trait" => "r#trait", "impl" => "r#impl", "pub" => "r#pub", "as" => "r#as", "const" => "r#const", "let" => "r#let", name => name, } } #[test] fn test_escape_rust_reserved() { assert_eq!(escape_rust_reserved("type"), "r#type"); assert_eq!(escape_rust_reserved("match"), "r#match"); assert_eq!(escape_rust_reserved("move"), "r#move"); assert_eq!(escape_rust_reserved("use"), "r#use"); assert_eq!(escape_rust_reserved("loop"), "r#loop"); assert_eq!(escape_rust_reserved("continue"), "r#continue"); assert_eq!(escape_rust_reserved("break"), "r#break"); assert_eq!(escape_rust_reserved("fn"), "r#fn"); assert_eq!(escape_rust_reserved("struct"), "r#struct"); assert_eq!(escape_rust_reserved("enum"), "r#enum"); assert_eq!(escape_rust_reserved("trait"), "r#trait"); assert_eq!(escape_rust_reserved("impl"), "r#impl"); assert_eq!(escape_rust_reserved("pub"), "r#pub"); assert_eq!(escape_rust_reserved("as"), "r#as"); assert_eq!(escape_rust_reserved("const"), "r#const"); assert_eq!(escape_rust_reserved("let"), "r#let"); assert_eq!(escape_rust_reserved("foo"), "foo"); } #[test] fn test_representation_rust_type() { let rt = RepresentationRef::Id("person".to_string()); assert_eq!(representation_rust_type(&rt), "Person"); } fn generate_representation_struct_json( input: &RepresentationDef, config: &Config, options_names: &HashMap, ) -> Vec { let mut lines: Vec = vec![]; let name = input.id.as_ref().unwrap().as_str(); let name = camel_case_name(name); let container = ParamContainer::Representation(input); for doc in &input.docs { lines.extend(generate_doc(doc, 0, config)); } if input.docs.is_empty() { lines.push(format!( "/// Representation of the `{}` resource\n", input.id.as_ref().unwrap() )); } let derive_default = input.params.iter().all(|x| !x.required); lines.push( "#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\n".to_string(), ); let visibility = config .representation_visibility .as_ref() .and_then(|x| x(name.as_str())) .unwrap_or_else(|| "pub".to_string()); lines.push(format!( "{}struct {} {{\n", if visibility.is_empty() { "".to_string() } else { format!("{} ", visibility) }, name )); for param in &input.params { let param_name = snake_case_name(param.name.as_str()); let param_name = escape_rust_reserved(param_name.as_str()); let (param_type, annotations) = param_rust_type( &container, param, config, |_x| "url::Url".to_string(), options_names, ); // We provide accessors for resource types let is_pub = true; lines.push(format!(" // was: {}\n", param.r#type)); if let Some(doc) = param.doc.as_ref() { lines.extend(generate_doc(doc, 1, config)); } for ann in annotations { lines.push(format!(" {}\n", ann)); } lines.push(format!( " {}{}: {},\n", if is_pub { "pub " } else { "" }, param_name, param_type )); lines.push("\n".to_string()); } lines.push("}\n".to_string()); if derive_default { lines.push(format!("impl Default for {} {{\n", name)); lines.push(" fn default() -> Self {\n".to_string()); lines.push(" Self {\n".to_string()); for param in &input.params { let param_name = snake_case_name(param.name.as_str()); let param_name = escape_rust_reserved(param_name.as_str()); lines.push(format!(" {}: Default::default(),\n", param_name)); } lines.push(" }\n".to_string()); lines.push(" }\n".to_string()); lines.push("}\n".to_string()); lines.push("\n".to_string()); } lines.push("\n".to_string()); lines } #[test] fn test_generate_representation() { let input = RepresentationDef { media_type: Some("application/json".parse().unwrap()), element: None, profile: None, docs: vec![], id: Some("person".to_string()), params: vec![ Param { name: "name".to_string(), r#type: "string".to_string(), style: ParamStyle::Plain, required: true, doc: Some(Doc::new("The name of the person".to_string())), path: None, id: None, repeating: false, fixed: None, links: vec![], options: None, }, Param { name: "age".to_string(), r#type: "xs:int".to_string(), required: true, doc: Some(Doc::new("The age of the person".to_string())), style: ParamStyle::Query, path: None, id: None, repeating: false, fixed: None, links: vec![], options: None, }, ], }; let config = Config::default(); let lines = generate_representation_struct_json(&input, &config, &HashMap::new()); assert_eq!( lines, vec![ "/// Representation of the `person` resource\n".to_string(), "#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\n" .to_string(), "pub struct Person {\n".to_string(), " // was: string\n".to_string(), " /// The name of the person\n".to_string(), " pub name: String,\n".to_string(), "\n".to_string(), " // was: xs:int\n".to_string(), " /// The age of the person\n".to_string(), " pub age: i32,\n".to_string(), "\n".to_string(), "}\n".to_string(), "\n".to_string(), ] ); } fn supported_representation_def(_d: &RepresentationDef) -> bool { false } #[test] fn test_supported_representation_def() { let mut d = RepresentationDef::default(); d.media_type = Some(crate::WADL_MIME_TYPE.parse().unwrap()); assert!(!supported_representation_def(&d)); d.media_type = Some(XHTML_MIME_TYPE.parse().unwrap()); assert!(!supported_representation_def(&d)); d.media_type = Some("application/json".parse().unwrap()); assert!(!supported_representation_def(&d)); } /// Generate the Rust type for a representation /// /// # Arguments /// * `input` - The representation to generate the Rust type for /// * `name` - The name of the representation /// /// # Returns /// /// The Rust type for the representation fn rust_type_for_response( method: &Method, input: &Response, name: &str, options_names: &HashMap, ) -> String { let container = ParamContainer::Response(method, input); let representations = input .representations .iter() .filter(|r| match r { Representation::Definition(ref d) => supported_representation_def(d), _ => true, }) .collect::>(); if representations.len() == 1 { assert!(input.params.is_empty()); match representations[0] { Representation::Reference(ref r) => { let id = r.id().unwrap().to_string(); camel_case_name(id.as_str()) } Representation::Definition(ref d) => { assert!(d.params.iter().all(|p| p.style == ParamStyle::Header)); let mut ret = Vec::new(); for param in &input.params { let (param_type, _annotations) = param_rust_type( &container, param, &Config::default(), resource_type_rust_type, options_names, ); ret.push(param_type); } if ret.len() == 1 { ret[0].clone() } else { format!("({})", ret.join(", ")) } } } } else if representations.is_empty() { let mut ret = Vec::new(); for param in &input.params { let (param_type, _annotations) = param_rust_type( &container, param, &Config::default(), resource_type_rust_type, options_names, ); ret.push(param_type); } if ret.len() == 1 { ret[0].clone() } else { format!("({})", ret.join(", ")) } } else { todo!( "multiple representations for response: {}: {:?}", name, representations ); } } #[test] fn test_rust_type_for_response() { let mut input = Response { params: vec![Param { id: Some("foo".to_string()), name: "foo".to_string(), r#type: "string".to_string(), style: ParamStyle::Header, doc: None, required: true, repeating: false, fixed: None, path: None, links: Vec::new(), options: None, }], ..Default::default() }; let method = Method { name: "GET".to_string(), id: "get".to_string(), docs: Vec::new(), request: Request::default(), responses: vec![input.clone()], }; assert_eq!( rust_type_for_response(&method, &input, "foo", &HashMap::new()), "String".to_string() ); input.params = vec![ Param { id: Some("foo".to_string()), name: "foo".to_string(), r#type: "string".to_string(), style: ParamStyle::Header, doc: None, required: true, repeating: false, fixed: None, path: None, links: Vec::new(), options: None, }, Param { id: Some("bar".to_string()), name: "bar".to_string(), r#type: "string".to_string(), style: ParamStyle::Header, doc: None, required: true, repeating: false, fixed: None, path: None, links: Vec::new(), options: None, }, ]; assert_eq!( rust_type_for_response(&method, &input, "foo", &HashMap::new()), "(String, String)".to_string() ); input.params = vec![Param { id: Some("foo".to_string()), name: "foo".to_string(), r#type: "string".to_string(), style: ParamStyle::Header, doc: None, required: true, repeating: false, fixed: None, path: None, links: vec![Link { relation: None, reverse_relation: None, resource_type: Some("http://example.com/#foo".parse().unwrap()), doc: None, }], options: None, }]; assert_eq!( rust_type_for_response(&method, &input, "foo", &HashMap::new()), "Foo".to_string() ); input.params = vec![Param { id: Some("foo".to_string()), name: "foo".to_string(), r#type: "string".to_string(), style: ParamStyle::Header, doc: None, required: true, repeating: false, fixed: None, path: None, links: vec![Link { relation: None, reverse_relation: None, resource_type: Some("http://example.com/#foo".parse().unwrap()), doc: None, }], options: None, }]; assert_eq!( rust_type_for_response(&method, &input, "foo", &HashMap::new()), "Foo".to_string() ); input.params = vec![Param { id: None, name: "foo".to_string(), r#type: "string".to_string(), style: ParamStyle::Header, doc: None, required: true, repeating: false, fixed: None, options: None, path: None, links: vec![Link { relation: None, reverse_relation: None, resource_type: None, doc: None, }], }]; assert_eq!( rust_type_for_response(&method, &input, "foo", &HashMap::new()), "url::Url".to_string() ); } fn format_arg_doc(name: &str, doc: Option<&crate::ast::Doc>, config: &Config) -> Vec { let mut lines = Vec::new(); if let Some(doc) = doc.as_ref() { let doc = format_doc(doc, config); let mut doc_lines = doc .trim_start_matches('\n') .split('\n') .collect::>() .into_iter(); lines.push(format!( " /// * `{}`: {}\n", name, doc_lines.next().unwrap().trim_end_matches(' ') )); for doc_line in doc_lines { if doc_line.is_empty() { lines.push(" ///\n".to_string()); } else { lines.push(format!(" /// {}\n", doc_line.trim_end_matches(' '))); } } } else { lines.push(format!(" /// * `{}`\n", name)); } lines } #[test] fn test_format_arg_doc() { let config = Config::default(); assert_eq!( format_arg_doc("foo", None, &config), vec![" /// * `foo`\n".to_string()] ); assert_eq!( format_arg_doc("foo", Some(&Doc::new("bar".to_string())), &config), vec![" /// * `foo`: bar\n".to_string()] ); assert_eq!( format_arg_doc("foo", Some(&Doc::new("bar\nbaz".to_string())), &config), vec![ " /// * `foo`: bar\n".to_string(), " /// baz\n".to_string() ] ); assert_eq!( format_arg_doc("foo", Some(&Doc::new("bar\n\nbaz".to_string())), &config), vec![ " /// * `foo`: bar\n".to_string(), " ///\n".to_string(), " /// baz\n".to_string() ] ); } fn apply_map_fn(map_fn: Option<&str>, ret: &str, required: bool) -> String { if let Some(map_fn) = map_fn { if required { if map_fn.starts_with('|') { format!("({})({})", map_fn, ret) } else { format!("{}({})", map_fn, ret) } } else { format!("{}.map({})", ret, map_fn) } } else { ret.to_string() } } #[test] fn test_apply_map_fn() { assert_eq!(apply_map_fn(None, "x", true), "x".to_string()); assert_eq!(apply_map_fn(Some("Some"), "x", true), "Some(x)".to_string()); assert_eq!( apply_map_fn(Some("Some"), "x", false), "x.map(Some)".to_string() ); assert_eq!( apply_map_fn(Some("|y|y+1"), "x", true), "(|y|y+1)(x)".to_string() ); assert_eq!( apply_map_fn(Some("|y|y+1"), "x", false), "x.map(|y|y+1)".to_string() ); } fn serialize_representation_def( def: &RepresentationDef, config: &Config, options_names: &HashMap, ) -> Vec { let mut lines = vec![]; fn process_param( param: &Param, container: &ParamContainer, config: &Config, cb: impl Fn(&str, &str, &str) -> String, options_names: &HashMap, ) -> Vec { let param_name = escape_rust_reserved(param.name.as_str()); let (param_type, _annotations) = param_rust_type( &container, param, config, resource_type_rust_type, options_names, ); let param_type = readonly_rust_type(¶m_type); let mut indent = 4; let mut lines = vec![]; let needs_iter = param_type.starts_with("Vec<") || param_type.starts_with("Option { lines.push("let mut form = reqwest::blocking::multipart::Form::new();\n".to_string()); for param in def.params.iter() { lines.extend(process_param( param, &container, config, |param_type, name, value| { format!( "form = form.part(\"{}\", {});", name, if let Some(convert_to_multipart) = config .convert_to_multipart .as_ref() .and_then(|x| x(param_type, value)) { convert_to_multipart } else { format!( "reqwest::blocking::multipart::Part::text({})", value.strip_prefix('&').unwrap_or(value) ) } ) }, options_names, )); } lines.push("req = req.multipart(form);\n".to_string()); } Some("application/x-www-form-urlencoded") => { lines.push( "let mut serializer = form_urlencoded::Serializer::new(String::new());\n" .to_string(), ); for param in def.params.iter() { lines.extend(process_param(param, &container, config, |r#type, name, value| { if r#type.contains("[") { format!("for value in {} {{ serializer.append_pair(\"{}\", &value.to_string()); }}", value.strip_prefix("&").unwrap().strip_suffix(".to_string()").unwrap(), name) } else { format!("serializer.append_pair(\"{}\", {});", name, value) } }, options_names)); } lines.push("req = req.header(reqwest::header::CONTENT_TYPE, \"application/x-www-form-urlencoded\");\n".to_string()); lines.push("req = req.body(serializer.finish());\n".to_string()); } Some("application/json") => { lines.push("let mut o = serde_json::Value::Object::new();".to_string()); for param in def.params.iter() { lines.extend(process_param( param, &container, config, |_type, name, value| format!("o.insert(\"{}\", {});", name, value), options_names, )); } lines.push("req = req.json(&o);\n".to_string()); } o => { panic!("unsupported media type {:?}", o); } } lines } fn generate_method( input: &Method, parent_id: &str, config: &Config, options_names: &HashMap, ) -> Vec { let mut lines = generate_method_representation(input, parent_id, config, options_names); for response in input.responses.iter() { if response.representations.iter().any(|r| { r.media_type().as_ref().map(|s| s.to_string()).as_deref() == Some(crate::WADL_MIME_TYPE) }) { lines.extend(generate_method_wadl(input, parent_id, config)) } } lines } fn generate_method_wadl(input: &Method, parent_id: &str, _config: &Config) -> Vec { let mut lines = vec![]; let name = input.id.as_str(); let name = name .strip_prefix(format!("{}-", parent_id).as_str()) .unwrap_or(name); let name = snake_case_name(name); lines.push(format!(" pub fn {}_wadl<'a>(&self, client: &'a dyn wadl::Client) -> std::result::Result {{\n", name)); lines.push(" let mut url_ = self.url().clone();\n".to_string()); for param in input .request .params .iter() .filter(|p| p.style == ParamStyle::Query) { if let Some(fixed) = param.fixed.as_ref() { assert!(!param.repeating); lines.push(format!( " url_.query_pairs_mut().append_pair(\"{}\", \"{}\");\n", param.name, fixed )); } } lines.push("\n".to_string()); let method = input.name.as_str(); lines.push(format!( " let mut req = client.request(reqwest::Method::{}, url_);\n", method )); lines.push(format!( " req = req.header(reqwest::header::ACCEPT, \"{}\");\n", crate::WADL_MIME_TYPE )); lines.push("\n".to_string()); lines.push(" let wadl: wadl::ast::Application = req.send()?.error_for_status()?.text()?.parse()?;\n".to_string()); lines.push( " let resource = wadl.get_resource_by_href(self.url()).unwrap();\n".to_string(), ); lines.push(" Ok(resource.clone())\n".to_string()); lines.push(" }\n".to_string()); lines.push("\n".to_string()); lines } fn generate_method_representation( input: &Method, parent_id: &str, config: &Config, options_names: &HashMap, ) -> Vec { let mut lines = vec![]; let name = input.id.as_str(); let name = name .strip_prefix(format!("{}-", parent_id).as_str()) .unwrap_or(name); let name = snake_case_name(name); let (ret_type, map_fn) = if input.responses.is_empty() { ("()".to_string(), None) } else { assert_eq!(1, input.responses.len(), "expected 1 response for {}", name); let mut return_type = rust_type_for_response( &input, &input.responses[0], input.id.as_str(), options_names, ); let map_fn = if let Some((map_type, map_fn)) = config .map_type_for_response .as_ref() .and_then(|r| r(&name, &return_type)) { return_type = map_type; Some(map_fn) } else { None }; (return_type, map_fn) }; let visibility = config .method_visibility .as_ref() .and_then(|x| x(&name, &ret_type)) .unwrap_or("pub".to_string()); let mut line = format!( " {}fn {}<'a>(&self, client: &'a dyn wadl::Client", if visibility.is_empty() { "".to_string() } else { format!("{} ", visibility) }, name ); let mut params = input.request.params.iter().collect::>(); params.extend( input .request .representations .iter() .filter_map(|r| match r { Representation::Definition(d) => Some(&d.params), Representation::Reference(_) => None, }) .flatten(), ); for doc in &input.docs { lines.extend(generate_doc(doc, 1, config)); } if !params.is_empty() { lines.push(" /// # Arguments\n".to_string()); } for representation in &input.request.representations { match representation { Representation::Definition(_) => {} Representation::Reference(r) => { let id = camel_case_name(r.id().unwrap()); line.push_str(format!(", representation: &{}", id).as_str()); } } } let container = ParamContainer::Request(&input, &input.request); for param in ¶ms { if param.fixed.is_some() { continue; } let (param_type, _annotations) = param_rust_type( &container, param, config, resource_type_rust_type, options_names, ); let param_type = readonly_rust_type(param_type.as_str()); let param_name = param.name.clone(); let param_name = escape_rust_reserved(param_name.as_str()); line.push_str(format!(", {}: {}", param_name, param_type).as_str()); lines.extend(format_arg_doc(param_name, param.doc.as_ref(), config)); } line.push_str(") -> std::result::Result<"); line.push_str(ret_type.as_str()); line.push_str(", Error> {\n"); lines.push(line); assert!(input .request .params .iter() .all(|p| [ParamStyle::Header, ParamStyle::Query].contains(&p.style))); lines.push(" let mut url_ = self.url().clone();\n".to_string()); for param in input .request .params .iter() .filter(|p| p.style == ParamStyle::Query) { if let Some(fixed) = param.fixed.as_ref() { assert!(!param.repeating); lines.push(format!( " url_.query_pairs_mut().append_pair(\"{}\", \"{}\");\n", param.name, fixed )); } else { let param_name = param.name.as_str(); let param_name = snake_case_name(param_name); let param_name = escape_rust_reserved(param_name.as_str()); let (param_type, _annotations) = param_rust_type( &container, param, config, resource_type_rust_type, options_names, ); let value = if !param.links.is_empty() { format!("&{}.url().to_string()", param_name) } else { format!("&{}.to_string()", param_name) }; let mut indent = 0; let needs_iter = param.repeating || param_type.starts_with("Vec<") || param_type.starts_with("Option 0 { lines.push(format!("{:indent$} }}\n", "", indent = indent)); indent -= 4; } } } lines.push("\n".to_string()); let method = input.name.as_str(); lines.push(format!( " let mut req = client.request(reqwest::Method::{}, url_);\n", method )); for representation in &input.request.representations { match representation { Representation::Definition(ref d) => { lines.extend(indent( 2, serialize_representation_def(d, config, options_names).into_iter(), )); } Representation::Reference(_r) => { // TODO(jelmer): Support non-JSON representations lines.push(" req = req.json(&representation);\n".to_string()); } }; } let response_mime_types = input .responses .iter() .flat_map(|x| { x.representations.iter().filter_map(|x| match x { Representation::Definition(ref d) if supported_representation_def(d) => { d.media_type.clone() } Representation::Reference(_) => { // TODO: Look up media type of reference Some(mime::APPLICATION_JSON) } _ => None, }) }) .collect::>(); if !response_mime_types.is_empty() { lines.push(format!( " req = req.header(reqwest::header::ACCEPT, \"{}\");\n", response_mime_types .into_iter() .map(|x| x.to_string()) .collect::>() .join(", ") )); } for param in params.iter().filter(|p| p.style == ParamStyle::Header) { let value = if let Some(fixed) = param.fixed.as_ref() { format!("\"{}\"", fixed) } else { let param_name = param.name.as_str(); let param_name = snake_case_name(param_name); let param_name = escape_rust_reserved(param_name.as_str()); format!("&{}.to_string()", param_name) }; lines.push(format!( " req = req.header(\"{}\", {});\n", param.name, value )); } lines.push("\n".to_string()); lines.push(" let resp = req.send()?;\n".to_string()); lines.push(" match resp.status() {\n".to_string()); let serialize_return_types = |return_types: Vec<(String, bool)>| { if return_types.is_empty() { "Ok(())".to_string() } else if return_types.len() == 1 { format!( "Ok({})", apply_map_fn(map_fn.as_deref(), &return_types[0].0, return_types[0].1) ) } else { let v = format!( "({})", return_types .iter() .map(|x| x.0.clone()) .collect::>() .join(", ") ); format!("Ok({})", apply_map_fn(map_fn.as_deref(), &v, true)) } }; for response in input.responses.iter() { let mut return_types = vec![]; for param in response.params.iter() { match ¶m.style { ParamStyle::Header => { if !param.links.is_empty() { let r = ¶m.links[0].resource_type.as_ref().unwrap(); if param.required { return_types.push(( format!( "{}(resp.headers().get(\"{}\")?.to_str()?.parse().unwrap())", resource_type_rust_type(r), param.name ), true, )); } else { return_types.push((format!( "resp.headers().get(\"{}\").map(|x| {}(x.to_str().unwrap().parse().unwrap()))", param.name, resource_type_rust_type(r), ), false)); } } else { todo!( "header param type {:?} for {} in {:?}", param.r#type, param.name, input.id ); } } t => todo!("param style {:?}", t), } } // TODO(jelmer): match on media type if let Some(status) = response.status { lines.push(format!( " s if s.as_u16() == reqwest::StatusCode::{} => {{\n", status )); } else { lines.push(" s if s.is_success() => {\n".to_string()); } if !response.representations.is_empty() { lines.push(" let content_type: Option = resp.headers().get(reqwest::header::CONTENT_TYPE).map(|x| x.to_str().unwrap()).map(|x| x.parse().unwrap());\n".to_string()); lines.push( " match content_type.as_ref().map(|x| x.essence_str()) {\n" .to_string(), ); for representation in response.representations.iter() { let media_type = representation .media_type() .unwrap_or(&mime::APPLICATION_JSON); lines.push(format!( " Some(\"{}\") => {{\n", media_type )); let t = match representation { Representation::Definition(_) => None, Representation::Reference(r) => { let rt = representation_rust_type(r); Some((format!("resp.json::<{}>()?", rt), true)) } }; if let Some(t) = t { let mut return_types = return_types.clone(); return_types.insert(0, t); lines.push(format!( " {}\n", serialize_return_types(return_types) )); } else { lines.push(" unimplemented!();\n".to_string()); } lines.push(" }\n".to_string()); } lines.push( " _ => { Err(Error::UnhandledContentType(resp)) }\n".to_string(), ); lines.push(" }\n".to_string()); } else { lines.push(format!( " {}\n", serialize_return_types(return_types) )); } lines.push(" }\n".to_string()); } if input.responses.is_empty() { lines.push(" s if s.is_success() => Ok(()),\n".to_string()); } lines.push(" _ => Err(wadl::Error::UnhandledStatus(resp))\n".to_string()); lines.push(" }\n".to_string()); lines.push(" }\n".to_string()); lines.push("\n".to_string()); if let Some(extend_method) = config.extend_method.as_ref() { lines.extend(extend_method(parent_id, &name, &ret_type, config)); } lines } #[test] fn test_generate_method() { let input = Method { id: "foo".to_string(), name: "GET".to_string(), docs: vec![], request: Request { docs: vec![], params: vec![], representations: vec![], }, responses: vec![], }; let config = Config::default(); let lines = generate_method(&input, "bar", &config, &HashMap::new()); assert_eq!(lines, vec![ " pub fn foo<'a>(&self, client: &'a dyn wadl::Client) -> std::result::Result<(), Error> {\n".to_string(), " let mut url_ = self.url().clone();\n".to_string(), "\n".to_string(), " let mut req = client.request(reqwest::Method::GET, url_);\n".to_string(), "\n".to_string(), " let resp = req.send()?;\n".to_string(), " match resp.status() {\n".to_string(), " s if s.is_success() => Ok(()),\n".to_string(), " _ => Err(wadl::Error::UnhandledStatus(resp))\n".to_string(), " }\n".to_string(), " }\n".to_string(), "\n".to_string(), ]); } fn generate_resource_type( input: &ResourceType, config: &Config, options_names: &HashMap, ) -> Vec { let mut lines = vec![]; for doc in &input.docs { lines.extend(generate_doc(doc, 0, config)); } let name = input.id.as_str(); let name = camel_case_name(name); let visibility = config .resource_type_visibility .as_ref() .and_then(|x| x(name.as_str())) .unwrap_or("pub".to_string()); lines.push(format!( "{}struct {} (reqwest::Url);\n", if visibility.is_empty() { "".to_string() } else { format!("{} ", visibility) }, name )); lines.push("\n".to_string()); lines.push(format!("impl {} {{\n", name)); for method in &input.methods { lines.extend(generate_method( method, input.id.as_str(), config, options_names, )); } lines.push("}\n".to_string()); lines.push("\n".to_string()); lines.push(format!("impl Resource for {} {{\n", name)); lines.push(" fn url(&self) -> &reqwest::Url {\n".to_string()); lines.push(" &self.0\n".to_string()); lines.push(" }\n".to_string()); lines.push("}\n".to_string()); lines.push("\n".to_string()); lines } #[test] fn test_generate_resource_type() { let input = ResourceType { id: "foo".to_string(), docs: vec![], methods: vec![], query_type: mime::APPLICATION_JSON, params: vec![], subresources: vec![], }; let config = Config::default(); let lines = generate_resource_type(&input, &config, &HashMap::new()); assert_eq!( lines, vec![ "pub struct Foo (reqwest::Url);\n".to_string(), "\n".to_string(), "impl Foo {\n".to_string(), "}\n".to_string(), "\n".to_string(), "impl Resource for Foo {\n".to_string(), " fn url(&self) -> &reqwest::Url {\n".to_string(), " &self.0\n".to_string(), " }\n".to_string(), "}\n".to_string(), "\n".to_string(), ] ); } #[derive(Default)] #[allow(clippy::type_complexity)] /// Configuration for code generation pub struct Config { /// Based on the listed type and name of a parameter, determine the rust type pub override_type_name: Option Option>>, /// Support renaming param accessor functions pub param_accessor_rename: Option Option>>, /// Whether to strip code examples from the docstrings /// /// This is useful if the code examples are not valid rust code. pub strip_code_examples: bool, /// Generate custom trait implementations for representations pub generate_representation_traits: Option< Box Option>>, >, /// Return the visibility of a representation pub representation_visibility: Option Option>>, /// Return the visibility of a representation accessor pub accessor_visibility: Option Option>>, /// Return the visibility of a resource type pub resource_type_visibility: Option Option>>, /// Map a method response type to a different type and a function to map the response pub map_type_for_response: Option Option<(String, String)>>>, /// Map an accessor function name to a different type pub map_type_for_accessor: Option Option<(String, String)>>>, /// Extend the generated accessor pub extend_accessor: Option Vec>>, /// Extend the generated method pub extend_method: Option Vec>>, /// Retrieve visibility for a method pub method_visibility: Option Option>>, /// Return whether a param is deprecated pub deprecated_param: Option bool>>, /// Return the name for an enum representation a set of options /// /// The callback can be used to determine if the name is already taken. pub options_enum_name: Option bool>) -> String>>, /// Reformat a docstring; should already be in markdown pub reformat_docstring: Option String>>, /// Convert a string to a multipart Part, given a type name and value pub convert_to_multipart: Option Option>>, } fn enum_rust_value(option: &str) -> String { let name = camel_case_name(option.replace(' ', "-").as_str()); // Now, strip all characters not allowed in rust identifiers let name = name .chars() .filter(|c| c.is_alphanumeric() || *c == '_') .collect::(); // If the identifier starts with a digit, prefix it with '_' to make it a valid identifier if name.chars().next().unwrap().is_numeric() { format!("_{}", name) } else { name } } #[cfg(test)] mod tests { use super::*; #[test] fn test_enum_rust_value() { assert_eq!(enum_rust_value("foo"), "Foo"); assert_eq!(enum_rust_value("foo bar"), "FooBar"); assert_eq!(enum_rust_value("foo bar blah"), "FooBarBlah"); assert_eq!(enum_rust_value("foo-bar"), "FooBar"); } } fn generate_options(name: &str, options: &crate::ast::Options) -> Vec { let mut lines = vec![]; lines.push("#[derive(Debug, Clone, Copy, PartialEq, Eq, std::hash::Hash, serde::Serialize, serde::Deserialize)]\n".to_string()); lines.push(format!("pub enum {} {{\n", name)); let mut option_map = HashMap::new(); for option in options.keys() { let rust_name = enum_rust_value(option); lines.push(format!(" #[serde(rename = \"{}\")]\n", option)); lines.push(format!(" {},\n", rust_name)); option_map.insert(option, rust_name); } lines.push("}\n".to_string()); lines.push("\n".to_string()); lines.push(format!("impl std::fmt::Display for {} {{\n", name)); lines.push( " fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n".to_string(), ); lines.push(" match self {\n".to_string()); for (option, rust_name) in option_map { lines.push(format!( " {}::{} => write!(f, \"{}\"),\n", name, rust_name, option )); } lines.push(" }\n".to_string()); lines.push(" }\n".to_string()); lines.push("}\n".to_string()); lines } fn options_rust_enum_name(param: &Param, options: &HashMap) -> String { let mut name = camel_case_name(param.name.as_str()); while options.values().any(|v| v == &name) { name = format!("{}_", name); } name } /// Generate code from a WADL application definition. /// /// This function generates Rust code from a WADL application definition. /// The generated code includes Rust types for the representations and /// resource types defined in the WADL application, as well as methods /// for interacting with the resources. /// /// # Arguments /// * `app` - The WADL application definition. /// * `config` - Configuration for the code generation. pub fn generate(app: &Application, config: &Config) -> String { let mut lines = vec![]; let mut options = HashMap::new(); for param in app.iter_all_params() { if let Some(os) = ¶m.options { if options.contains_key(os) { continue; } let name = if let Some(enum_name_fn) = config.options_enum_name.as_ref() { let cb_options = options.clone(); let name = enum_name_fn( param, Box::new(move |name: &str| -> bool { cb_options.values().any(|v| v == name) }), ); let taken = options .iter() .filter_map(|(k, v)| if v == &name { Some(k) } else { None }) .collect::>(); if !taken.is_empty() { panic!( "Enum name {} is already taken by {:?} ({:?})", name, taken, options ); } name } else { options_rust_enum_name(param, &options) }; let enum_lines = generate_options(name.as_str(), os); options.insert(os.clone(), name); lines.extend(enum_lines); } } for doc in &app.docs { lines.extend(generate_doc(doc, 0, config)); } for representation in &app.representations { lines.extend(generate_representation(representation, config, &options)); } for resource_type in &app.resource_types { lines.extend(generate_resource_type(resource_type, config, &options)); } lines.concat() } fn indent(indent: usize, lines: impl Iterator) -> impl Iterator { lines.map(move |line| format!("{}{}", " ".repeat(indent * 4), line)) } #[test] fn test_generate_empty() { let input = crate::ast::Application { docs: vec![], representations: vec![], resource_types: vec![], resources: vec![], grammars: vec![], }; let config = Config::default(); let lines = generate(&input, &config); assert_eq!(lines, "".to_string()); } wadl-0.3.2/src/lib.rs000064400000000000000000000075571046102023000124760ustar 00000000000000#![deny(missing_docs)] //! # WADL //! //! A crate for parsing WADL files and generating Rust code from them. pub mod ast; #[cfg(feature = "codegen")] pub mod codegen; mod parse; /// The MIME type of WADL files. pub const WADL_MIME_TYPE: &str = "application/vnd.sun.wadl+xml"; pub use parse::{parse, parse_bytes, parse_file, parse_string, Error as ParseError}; use url::Url; /// The root of the web service. pub trait Resource { /// The URL of the resource fn url(&self) -> &Url; } /// A client for a WADL API pub trait Client { /// Create a new request builder fn request(&self, method: reqwest::Method, url: url::Url) -> reqwest::blocking::RequestBuilder; } impl Client for reqwest::blocking::Client { fn request(&self, method: reqwest::Method, url: url::Url) -> reqwest::blocking::RequestBuilder { self.request(method, url) } } #[derive(Debug)] /// The error type for this crate. pub enum Error { /// The URL is invalid. InvalidUrl, /// A reqwest error occurred. Reqwest(reqwest::Error), /// The URL could not be parsed. Url(url::ParseError), /// The JSON could not be parsed. Json(serde_json::Error), /// The WADL could not be parsed. Wadl(ParseError), /// The response status was not handled by the library. UnhandledStatus(reqwest::blocking::Response), /// The response content type was not handled by the library. UnhandledContentType(reqwest::blocking::Response), /// An I/O error occurred. Io(std::io::Error), } impl From for Error { fn from(err: std::io::Error) -> Self { Error::Io(err) } } impl From for Error { fn from(err: serde_json::Error) -> Self { Error::Json(err) } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Error::InvalidUrl => write!(f, "Invalid URL"), Error::Reqwest(err) => write!(f, "Reqwest error: {}", err), Error::Url(err) => write!(f, "URL error: {}", err), Error::Json(err) => write!(f, "JSON error: {}", err), Error::Wadl(err) => write!(f, "WADL error: {}", err), Error::UnhandledStatus(res) => write!( f, "Unhandled response. Code: {}, response type: {}", res.status(), res.headers() .get("content-type") .unwrap_or(&reqwest::header::HeaderValue::from_static("unknown")) .to_str() .unwrap_or("unknown") ), Error::UnhandledContentType(res) => write!( f, "Unhandled response content type: {}", res.headers() .get("content-type") .unwrap_or(&reqwest::header::HeaderValue::from_static("unknown")) .to_str() .unwrap_or("unknown") ), Error::Io(err) => write!(f, "IO error: {}", err), } } } impl std::error::Error for Error {} impl From for Error { fn from(err: reqwest::Error) -> Self { Error::Reqwest(err) } } impl From for Error { fn from(err: url::ParseError) -> Self { Error::Url(err) } } impl From for Error { fn from(err: ParseError) -> Self { Error::Wadl(err) } } /// Get the WADL AST from a URL. pub fn get_wadl_resource_by_href( client: &dyn Client, href: &url::Url, ) -> Result { let mut req = client.request(reqwest::Method::GET, href.clone()); req = req.header(reqwest::header::ACCEPT, WADL_MIME_TYPE); let res = req.send()?; let text = res.text()?; let application = parse_string(&text)?; let resource = application.get_resource_by_href(href).unwrap(); Ok(resource.clone()) } wadl-0.3.2/src/parse.rs000064400000000000000000000756561046102023000130470ustar 00000000000000use crate::ast::*; use iri_string::spec::IriSpec; use iri_string::types::RiReferenceString; use std::io::Read; use xmltree::Element; #[allow(unused)] /// The namespace of the WADL XML schema. pub const WADL_NS: &str = "http://wadl.dev.java.net/2009/02"; #[derive(Debug)] /// Errors that can occur while parsing a WADL document. pub enum Error { /// An I/O error occurred while reading the document. Io(std::io::Error), /// An error occurred while parsing the XML document. Xml(xmltree::ParseError), /// An error occurred while parsing a URL. Url(url::ParseError), /// An error occurred while parsing a MIME type. Mime(mime::FromStrError), } impl From for Error { fn from(e: std::io::Error) -> Self { Error::Io(e) } } impl From for Error { fn from(e: xmltree::ParseError) -> Self { Error::Xml(e) } } impl From for Error { fn from(e: url::ParseError) -> Self { Error::Url(e) } } impl From for Error { fn from(e: mime::FromStrError) -> Self { Error::Mime(e) } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match &self { Error::Io(e) => write!(f, "IO error: {}", e), Error::Xml(e) => write!(f, "XML error: {}", e), Error::Url(e) => write!(f, "URL error: {}", e), Error::Mime(e) => write!(f, "MIME error: {}", e), } } } impl std::error::Error for Error {} pub fn parse_options(element: &Element) -> Option { let mut options = Options::new(); for option_node in &element.children { if let Some(element) = option_node.as_element() { if element.name == "option" { let value = element.attributes.get("value").cloned(); let media_type = element .attributes .get("mediaType") .cloned() .map(|x| x.parse().unwrap()); options.insert(value.unwrap(), media_type); } } } if options.is_empty() { None } else { Some(options) } } #[test] fn test_parse_options() { let xml = r#"