From 179181aa2b24b1cfdda952743d2c74dd6cf3de1f Mon Sep 17 00:00:00 2001 From: jasonaix Date: Thu, 11 Dec 2025 18:43:27 +0800 Subject: [PATCH 1/6] add templates and src --- .github/workflows/build.yml | 43 + .github/workflows/publish.yml | 33 + .gitignore | 1 + Cargo.lock | 2834 +++++++++++++++++ Cargo.toml | 20 + LICENSE | 21 + README.md | 117 + api/crawl4ai-openapi.json | 1225 +++++++ openapitools.json | 7 + scripts/check_dirty_files.sh | 8 + scripts/generate_api_sdk.sh | 19 + scripts/generate_templates.sh | 14 + scripts/verify_version.sh | 59 + src/generated/apis/configuration.rs | 48 + src/generated/apis/default_api.rs | 1308 ++++++++ src/generated/apis/mod.rs | 118 + src/generated/mod.rs | 9 + src/generated/models/crawl_job_payload.rs | 40 + .../models/crawl_request_with_hooks.rs | 52 + src/generated/models/filter_type.rs | 41 + src/generated/models/hook_config.rs | 33 + src/generated/models/html_request.rs | 24 + src/generated/models/http_validation_error.rs | 24 + src/generated/models/js_endpoint_request.rs | 27 + src/generated/models/llm_job_payload.rs | 72 + src/generated/models/markdown_request.rs | 73 + src/generated/models/mod.rs | 32 + src/generated/models/pdf_request.rs | 34 + src/generated/models/raw_code.rs | 24 + src/generated/models/screenshot_request.rs | 42 + src/generated/models/token_request.rs | 24 + src/generated/models/validation_error.rs | 32 + .../models/validation_error_loc_inner.rs | 21 + src/generated/models/webhook_config.rs | 42 + src/lib.rs | 3 + templates/.travis.yml | 1 + templates/Cargo.mustache | 121 + templates/README.mustache | 54 + templates/api_doc.mustache | 47 + templates/git_push.sh.mustache | 57 + templates/gitignore.mustache | 3 + templates/hyper/api.mustache | 182 ++ templates/hyper/api_mod.mustache | 83 + templates/hyper/client.mustache | 55 + templates/hyper/configuration.mustache | 83 + templates/hyper0x/api.mustache | 181 ++ templates/hyper0x/api_mod.mustache | 64 + templates/hyper0x/client.mustache | 54 + templates/hyper0x/configuration.mustache | 34 + templates/lib.mustache | 17 + templates/model.mustache | 229 ++ templates/model_doc.mustache | 54 + templates/model_mod.mustache | 45 + templates/partial_header.mustache | 13 + templates/request.rs | 247 ++ templates/reqwest-trait/api.mustache | 566 ++++ templates/reqwest-trait/api_mod.mustache | 236 ++ .../reqwest-trait/configuration.mustache | 120 + templates/reqwest/api.mustache | 545 ++++ templates/reqwest/api_mod.mustache | 164 + templates/reqwest/configuration.mustache | 122 + tests/default_api_health_test.rs | 28 + 62 files changed, 9929 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 api/crawl4ai-openapi.json create mode 100644 openapitools.json create mode 100755 scripts/check_dirty_files.sh create mode 100755 scripts/generate_api_sdk.sh create mode 100755 scripts/generate_templates.sh create mode 100755 scripts/verify_version.sh create mode 100644 src/generated/apis/configuration.rs create mode 100644 src/generated/apis/default_api.rs create mode 100644 src/generated/apis/mod.rs create mode 100644 src/generated/mod.rs create mode 100644 src/generated/models/crawl_job_payload.rs create mode 100644 src/generated/models/crawl_request_with_hooks.rs create mode 100644 src/generated/models/filter_type.rs create mode 100644 src/generated/models/hook_config.rs create mode 100644 src/generated/models/html_request.rs create mode 100644 src/generated/models/http_validation_error.rs create mode 100644 src/generated/models/js_endpoint_request.rs create mode 100644 src/generated/models/llm_job_payload.rs create mode 100644 src/generated/models/markdown_request.rs create mode 100644 src/generated/models/mod.rs create mode 100644 src/generated/models/pdf_request.rs create mode 100644 src/generated/models/raw_code.rs create mode 100644 src/generated/models/screenshot_request.rs create mode 100644 src/generated/models/token_request.rs create mode 100644 src/generated/models/validation_error.rs create mode 100644 src/generated/models/validation_error_loc_inner.rs create mode 100644 src/generated/models/webhook_config.rs create mode 100644 src/lib.rs create mode 100644 templates/.travis.yml create mode 100644 templates/Cargo.mustache create mode 100644 templates/README.mustache create mode 100644 templates/api_doc.mustache create mode 100644 templates/git_push.sh.mustache create mode 100644 templates/gitignore.mustache create mode 100644 templates/hyper/api.mustache create mode 100644 templates/hyper/api_mod.mustache create mode 100644 templates/hyper/client.mustache create mode 100644 templates/hyper/configuration.mustache create mode 100644 templates/hyper0x/api.mustache create mode 100644 templates/hyper0x/api_mod.mustache create mode 100644 templates/hyper0x/client.mustache create mode 100644 templates/hyper0x/configuration.mustache create mode 100644 templates/lib.mustache create mode 100644 templates/model.mustache create mode 100644 templates/model_doc.mustache create mode 100644 templates/model_mod.mustache create mode 100644 templates/partial_header.mustache create mode 100644 templates/request.rs create mode 100644 templates/reqwest-trait/api.mustache create mode 100644 templates/reqwest-trait/api_mod.mustache create mode 100644 templates/reqwest-trait/configuration.mustache create mode 100644 templates/reqwest/api.mustache create mode 100644 templates/reqwest/api_mod.mustache create mode 100644 templates/reqwest/configuration.mustache create mode 100644 tests/default_api_health_test.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..f2d387d --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,43 @@ +name: Build + +on: + push: + branches: ["main"] + pull_request: + branches: ["*"] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Toolchain + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + components: rustfmt, clippy, llvm-tools-preview + + - name: Build + env: + RUSTFLAGS: "-D warnings" + run: cargo build + + - name: Build All Features + env: + RUSTFLAGS: "-D warnings" + run: cargo build --all-features + + - name: Check Format + run: cargo fmt --all -- --check + + - name: Run Clippy + timeout-minutes: 60 + run: cargo clippy --all-targets --all-features -j 4 -- -D warnings + + - name: Run Test + run: cargo test --all-features + + - name: Check Dirty Files + run: scripts/check_dirty_files.sh diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..2d081cb --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,33 @@ +name: Publish + +on: + push: + tags: + - 'v*.*.*' + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Toolchain + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + + - name: Extract Version + id: version + run: | + VERSION=${GITHUB_REF#refs/tags/v} + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Publishing version: $VERSION" + + - name: Verify Version + run: ./scripts/verify_version.sh ${{ steps.version.outputs.version }} + + - name: Publish to crates.io + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f97022 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a2a054f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2834 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-object-pool" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333c456b97c3f2d50604e8b2624253b7f787208cb72eb75e64b0ad11b221652c" +dependencies = [ + "async-std", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel 2.5.0", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.4.1", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-signal" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-attributes", + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[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.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "basic-cookies" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67bd8fd42c16bdb08688243dc5f0cc117a3ca9efeeaba3a345a18a6159ad96f7" +dependencies = [ + "lalrpop", + "lalrpop-util", + "regex", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "cc" +version = "1.2.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[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 = "crawl4ai-client" +version = "0.1.0" +dependencies = [ + "httpmock", + "reqwest", + "serde", + "serde_json", + "serde_with", + "tokio", + "url", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.111", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.4.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "httpmock" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ec9586ee0910472dec1a1f0f8acf52f0fdde93aea74d70d4a3107b4be0fd5b" +dependencies = [ + "assert-json-diff", + "async-object-pool", + "async-std", + "async-trait", + "base64 0.21.7", + "basic-cookies", + "crossbeam-utils", + "form_urlencoded", + "futures-util", + "hyper 0.14.32", + "lazy_static", + "levenshtein", + "log", + "regex", + "serde", + "serde_json", + "serde_regex", + "similar", + "tokio", + "url", +] + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.4.0", + "hyper 1.8.1", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[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 1.8.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.8.1", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.1", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lalrpop" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "levenshtein" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +dependencies = [ + "value-bag", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[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 = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +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 = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +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.111", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.12.1", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +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.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.6.1", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.1", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "reqwest" +version = "0.12.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime_guess", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[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.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[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.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "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 = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.12.1", + "schemars 0.9.0", + "schemars 1.1.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +dependencies = [ + "libc", +] + +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[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.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +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.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.1", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "iri-string", + "pin-project-lite", + "tower", + "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.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "value-bag" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[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.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.111", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..06f75ec --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "crawl4ai-client" +version = "0.1.0" +edition = "2024" + +[features] +default = ["rustls-tls"] +native-tls = ["reqwest/native-tls"] +rustls-tls = ["reqwest/rustls-tls"] + +[dependencies] +serde = { version = "1.0.228", features = ["derive"] } +serde_json = { version = "1.0.145" } +serde_with = { version = "3.15.1", default-features = false, features = ["base64", "std", "macros"] } +url = { version = "2.5.7" } +reqwest = { version = "0.12.24", default-features = false, features = ["json", "multipart", "rustls-tls"] } + +[dev-dependencies] +tokio = { version = "1.47.1", features = ["full"] } +httpmock = { version = "0.7.0" } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..aae06ac --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Nurok AI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 8b13789..1e00480 100644 --- a/README.md +++ b/README.md @@ -1 +1,118 @@ +# crawl4ai-client-rust +[![Build](https://github.com/nurokhq/crawl4ai-client-rust/actions/workflows/build.yml/badge.svg)](https://github.com/nurokhq/crawl4ai-client-rust/actions/workflows/build.yml) +[![Rust](https://img.shields.io/badge/rust-1.70+-blue.svg)](https://www.rust-lang.org/) +[![Code style: rustfmt](https://img.shields.io/badge/code%20style-rustfmt-000000.svg)](https://github.com/rust-lang/rustfmt) +[![Linter: clippy](https://img.shields.io/badge/linter-clippy-blue.svg)](https://github.com/rust-lang/rust-clippy) + +Rust SDK for the [Crawl4AI API](https://github.com/unclecode/crawl4ai), providing type-safe bindings to interact with Crawl4AI. + +## Overview + +This client is auto-generated from `api/crawl4ai-openapi.json`. It targets teams that want a lightweight, async-first Rust interface to Crawl4AI for crawling, enrichment, and downstream LLM workflows. + +## Features + +- **Type-safe client** generated from Crawl4AI OpenAPI +- **Async/await** powered by `reqwest` +- **TLS options**: `rustls-tls` (default) or `native-tls` +- **Crawling + streaming**: `/crawl` and `/crawl/stream` endpoints +- **Job orchestration**: enqueue and poll crawl/LLM jobs +- **Content generation**: HTML, PDF, screenshots, markdown helpers +- **JS execution**: run site-specific JS via the API + +## Installation + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +crawl4ai-client = { version = "0.1.0", features = ["rustls-tls"] } +``` + +Or with native TLS: + +```toml +[dependencies] +crawl4ai-client = { version = "0.1.0", features = ["native-tls"] } +``` + +If the crate is not yet published on crates.io, use the Git dependency: + +```toml +[dependencies] +crawl4ai-client = { git = "https://github.com/nurokhq/crawl4ai-client-rust", features = ["rustls-tls"] } +``` + +## Version Compatibility + +The client tracks the Crawl4AI OpenAPI version shipped in this repo. + +| Client Version | Crawl4AI API Version | +|----------------|----------------------| +| 0.1.0 | 0.7.8 | + +Please ensure you use a compatible client version for your Crawl4AI deployment. + +## Usage + +```rust +use crawl4ai_client::apis::{configuration::{ApiKey, Configuration}, default_api}; +use crawl4ai_client::models::CrawlRequestWithHooks; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Configure the client + let mut config = Configuration::default(); + config.base_path = "https://api.crawl4ai.com".to_string(); // set to your deployment + config.api_key = Some(ApiKey { prefix: None, key: "YOUR_API_KEY".into() }); + + // Build a crawl request + let request = CrawlRequestWithHooks::new(vec![ + "https://example.com".to_string(), + ]); + + // Fire the crawl + let response = default_api::crawl_crawl_post(&config, request).await?; + println!("{response}"); + + Ok(()) +} +``` + +## Features + +- `default`: Enables `rustls-tls` feature +- `rustls-tls`: Use rustls for TLS (default, recommended) +- `native-tls`: Use native TLS implementation + +## Development + +This SDK is generated from the OpenAPI specification using [OpenAPI Generator](https://openapi-generator.tech). + +To regenerate the SDK: + +```bash +./scripts/generate_api_sdk.sh +``` + +This script: +1. Generates the SDK from `api/crawl4ai-openapi.json` +2. Copies the generated code to `src/generated/` +3. Formats the code with `cargo fmt` + +## Requirements + +- Rust 1.70 or later +- OpenAPI Generator CLI (for regeneration) + +## License + +See [LICENSE](LICENSE) file for details. + +## Contributing + +Contributions are welcome! Please ensure that: +- Code is formatted with `cargo fmt` +- All tests pass with `cargo test` +- Clippy checks pass with `cargo clippy` diff --git a/api/crawl4ai-openapi.json b/api/crawl4ai-openapi.json new file mode 100644 index 0000000..8a812d7 --- /dev/null +++ b/api/crawl4ai-openapi.json @@ -0,0 +1,1225 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Crawl4AI API", + "version": "1.0.0" + }, + "paths": { + "/": { + "get": { + "summary": "Root", + "operationId": "root__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/metrics": { + "get": { + "summary": "Metrics", + "operationId": "metrics_metrics_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/llm/job": { + "post": { + "summary": "Llm Job Enqueue", + "operationId": "llm_job_enqueue_llm_job_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LlmJobPayload" + } + } + }, + "required": true + }, + "responses": { + "202": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/llm/job/{task_id}": { + "get": { + "summary": "Llm Job Status", + "operationId": "llm_job_status_llm_job__task_id__get", + "parameters": [ + { + "name": "task_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Task Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/crawl/job": { + "post": { + "summary": "Crawl Job Enqueue", + "operationId": "crawl_job_enqueue_crawl_job_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CrawlJobPayload" + } + } + }, + "required": true + }, + "responses": { + "202": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/crawl/job/{task_id}": { + "get": { + "summary": "Crawl Job Status", + "operationId": "crawl_job_status_crawl_job__task_id__get", + "parameters": [ + { + "name": "task_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Task Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/token": { + "post": { + "summary": "Get Token", + "operationId": "get_token_token_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TokenRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/config/dump": { + "post": { + "summary": "Config Dump", + "operationId": "config_dump_config_dump_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RawCode" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/md": { + "post": { + "summary": "Get Markdown", + "operationId": "get_markdown_md_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MarkdownRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/html": { + "post": { + "summary": "Generate Html", + "description": "Crawls the URL, preprocesses the raw HTML for schema extraction, and returns the processed HTML.\nUse when you need sanitized HTML structures for building schemas or further processing.", + "operationId": "generate_html_html_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTMLRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/screenshot": { + "post": { + "summary": "Generate Screenshot", + "description": "Capture a full-page PNG screenshot of the specified URL, waiting an optional delay before capture,\nUse when you need an image snapshot of the rendered page. Its recommened to provide an output path to save the screenshot.\nThen in result instead of the screenshot you will get a path to the saved file.", + "operationId": "generate_screenshot_screenshot_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScreenshotRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/pdf": { + "post": { + "summary": "Generate Pdf", + "description": "Generate a PDF document of the specified URL,\nUse when you need a printable or archivable snapshot of the page. It is recommended to provide an output path to save the PDF.\nThen in result instead of the PDF you will get a path to the saved file.", + "operationId": "generate_pdf_pdf_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PDFRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/execute_js": { + "post": { + "summary": "Execute Js", + "description": "Execute a sequence of JavaScript snippets on the specified URL.\nReturn the full CrawlResult JSON (first result).\nUse this when you need to interact with dynamic pages using JS.\nREMEMBER: Scripts accept a list of separated JS snippets to execute and execute them in order.\nIMPORTANT: Each script should be an expression that returns a value. It can be an IIFE or an async function. You can think of it as such.\n Your script will replace '{script}' and execute in the browser context. So provide either an IIFE or a sync/async function that returns a value.\nReturn Format:\n - The return result is an instance of CrawlResult, so you have access to markdown, links, and other stuff. If this is enough, you don't need to call again for other endpoints.\n\n ```python\n class CrawlResult(BaseModel):\n url: str\n html: str\n success: bool\n cleaned_html: Optional[str] = None\n media: Dict[str, List[Dict]] = {}\n links: Dict[str, List[Dict]] = {}\n downloaded_files: Optional[List[str]] = None\n js_execution_result: Optional[Dict[str, Any]] = None\n screenshot: Optional[str] = None\n pdf: Optional[bytes] = None\n mhtml: Optional[str] = None\n _markdown: Optional[MarkdownGenerationResult] = PrivateAttr(default=None)\n extracted_content: Optional[str] = None\n metadata: Optional[dict] = None\n error_message: Optional[str] = None\n session_id: Optional[str] = None\n response_headers: Optional[dict] = None\n status_code: Optional[int] = None\n ssl_certificate: Optional[SSLCertificate] = None\n dispatch_result: Optional[DispatchResult] = None\n redirected_url: Optional[str] = None\n network_requests: Optional[List[Dict[str, Any]]] = None\n console_messages: Optional[List[Dict[str, Any]]] = None\n\n class MarkdownGenerationResult(BaseModel):\n raw_markdown: str\n markdown_with_citations: str\n references_markdown: str\n fit_markdown: Optional[str] = None\n fit_html: Optional[str] = None\n ```", + "operationId": "execute_js_execute_js_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JSEndpointRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/llm/{url}": { + "get": { + "summary": "Llm Endpoint", + "operationId": "llm_endpoint_llm__url__get", + "parameters": [ + { + "name": "url", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Url" + } + }, + { + "name": "q", + "in": "query", + "required": true, + "schema": { + "type": "string", + "title": "Q" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/schema": { + "get": { + "summary": "Get Schema", + "operationId": "get_schema_schema_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/hooks/info": { + "get": { + "summary": "Get Hooks Info", + "description": "Get information about available hook points and their signatures", + "operationId": "get_hooks_info_hooks_info_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/health": { + "get": { + "summary": "Health", + "operationId": "health_health_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/crawl": { + "post": { + "summary": "Crawl", + "description": "Crawl a list of URLs and return the results as JSON.\nFor streaming responses, use /crawl/stream endpoint.\nSupports optional user-provided hook functions for customization.", + "operationId": "crawl_crawl_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CrawlRequestWithHooks" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/crawl/stream": { + "post": { + "summary": "Crawl Stream", + "operationId": "crawl_stream_crawl_stream_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CrawlRequestWithHooks" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/ask": { + "get": { + "summary": "Get Context", + "description": "This end point is design for any questions about Crawl4ai library. It returns a plain text markdown with extensive information about Crawl4ai. \nYou can use this as a context for any AI assistant. Use this endpoint for AI assistants to retrieve library context for decision making or code generation tasks.\nAlway is BEST practice you provide a query to filter the context. Otherwise the lenght of the response will be very long.\n\nParameters:\n- context_type: Specify \"code\" for code context, \"doc\" for documentation context, or \"all\" for both.\n- query: RECOMMENDED search query to filter paragraphs using BM25. You can leave this empty to get all the context.\n- score_ratio: Minimum score as a fraction of the maximum score for filtering results.\n- max_results: Maximum number of results to return. Default is 20.\n\nReturns:\n- JSON response with the requested context.\n- If \"code\" is specified, returns the code context.\n- If \"doc\" is specified, returns the documentation context.\n- If \"all\" is specified, returns both code and documentation contexts.", + "operationId": "get_context_ask_get", + "parameters": [ + { + "name": "context_type", + "in": "query", + "required": false, + "schema": { + "type": "string", + "pattern": "^(code|doc|all)$", + "default": "all", + "title": "Context Type" + } + }, + { + "name": "query", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "search query to filter chunks", + "title": "Query" + }, + "description": "search query to filter chunks" + }, + { + "name": "score_ratio", + "in": "query", + "required": false, + "schema": { + "type": "number", + "maximum": 1.0, + "minimum": 0.0, + "description": "min score as fraction of max_score", + "default": 0.5, + "title": "Score Ratio" + }, + "description": "min score as fraction of max_score" + }, + { + "name": "max_results", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "description": "absolute cap on returned chunks", + "default": 20, + "title": "Max Results" + }, + "description": "absolute cap on returned chunks" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/mcp/sse": { + "get": { + "summary": " Mcp Sse", + "operationId": "_mcp_sse_mcp_sse_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/mcp/schema": { + "get": { + "summary": " Schema Endpoint", + "operationId": "_schema_endpoint_mcp_schema_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + } + }, + "components": { + "schemas": { + "CrawlJobPayload": { + "properties": { + "urls": { + "items": { + "type": "string", + "maxLength": 2083, + "minLength": 1, + "format": "uri" + }, + "type": "array", + "title": "Urls" + }, + "browser_config": { + "additionalProperties": true, + "type": "object", + "title": "Browser Config", + "default": {} + }, + "crawler_config": { + "additionalProperties": true, + "type": "object", + "title": "Crawler Config", + "default": {} + }, + "webhook_config": { + "anyOf": [ + { + "$ref": "#/components/schemas/WebhookConfig" + }, + { + "type": "null" + } + ] + } + }, + "type": "object", + "required": [ + "urls" + ], + "title": "CrawlJobPayload" + }, + "CrawlRequestWithHooks": { + "properties": { + "urls": { + "items": { + "type": "string" + }, + "type": "array", + "maxItems": 100, + "minItems": 1, + "title": "Urls" + }, + "browser_config": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Browser Config" + }, + "crawler_config": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Crawler Config" + }, + "hooks": { + "anyOf": [ + { + "$ref": "#/components/schemas/HookConfig" + }, + { + "type": "null" + } + ], + "description": "Optional user-provided hook functions" + } + }, + "type": "object", + "required": [ + "urls" + ], + "title": "CrawlRequestWithHooks", + "description": "Extended crawl request with hooks support" + }, + "FilterType": { + "type": "string", + "enum": [ + "raw", + "fit", + "bm25", + "llm" + ], + "title": "FilterType" + }, + "HTMLRequest": { + "properties": { + "url": { + "type": "string", + "title": "Url" + } + }, + "type": "object", + "required": [ + "url" + ], + "title": "HTMLRequest" + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "HookConfig": { + "properties": { + "code": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "title": "Code", + "description": "Map of hook points to Python code strings" + }, + "timeout": { + "type": "integer", + "maximum": 120.0, + "minimum": 1.0, + "title": "Timeout", + "description": "Timeout in seconds for each hook execution", + "default": 30 + } + }, + "type": "object", + "title": "HookConfig", + "description": "Configuration for user-provided hooks" + }, + "JSEndpointRequest": { + "properties": { + "url": { + "type": "string", + "title": "Url" + }, + "scripts": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Scripts", + "description": "List of separated JavaScript snippets to execute" + } + }, + "type": "object", + "required": [ + "url", + "scripts" + ], + "title": "JSEndpointRequest" + }, + "LlmJobPayload": { + "properties": { + "url": { + "type": "string", + "maxLength": 2083, + "minLength": 1, + "format": "uri", + "title": "Url" + }, + "q": { + "type": "string", + "title": "Q" + }, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Schema" + }, + "cache": { + "type": "boolean", + "title": "Cache", + "default": false + }, + "provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Provider" + }, + "webhook_config": { + "anyOf": [ + { + "$ref": "#/components/schemas/WebhookConfig" + }, + { + "type": "null" + } + ] + }, + "temperature": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Temperature" + }, + "base_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Base Url" + } + }, + "type": "object", + "required": [ + "url", + "q" + ], + "title": "LlmJobPayload" + }, + "MarkdownRequest": { + "properties": { + "url": { + "type": "string", + "title": "Url", + "description": "Absolute http/https URL to fetch" + }, + "f": { + "$ref": "#/components/schemas/FilterType", + "description": "Content\u2011filter strategy: fit, raw, bm25, or llm", + "default": "fit" + }, + "q": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Q", + "description": "Query string used by BM25/LLM filters" + }, + "c": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "C", + "description": "Cache\u2011bust / revision counter", + "default": "0" + }, + "provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Provider", + "description": "LLM provider override (e.g., 'anthropic/claude-3-opus')" + }, + "temperature": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Temperature", + "description": "LLM temperature override (0.0-2.0)" + }, + "base_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Base Url", + "description": "LLM API base URL override" + } + }, + "type": "object", + "required": [ + "url" + ], + "title": "MarkdownRequest", + "description": "Request body for the /md endpoint." + }, + "PDFRequest": { + "properties": { + "url": { + "type": "string", + "title": "Url" + }, + "output_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Output Path" + } + }, + "type": "object", + "required": [ + "url" + ], + "title": "PDFRequest" + }, + "RawCode": { + "properties": { + "code": { + "type": "string", + "title": "Code" + } + }, + "type": "object", + "required": [ + "code" + ], + "title": "RawCode" + }, + "ScreenshotRequest": { + "properties": { + "url": { + "type": "string", + "title": "Url" + }, + "screenshot_wait_for": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Screenshot Wait For", + "default": 2 + }, + "output_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Output Path" + } + }, + "type": "object", + "required": [ + "url" + ], + "title": "ScreenshotRequest" + }, + "TokenRequest": { + "properties": { + "email": { + "type": "string", + "format": "email", + "title": "Email" + } + }, + "type": "object", + "required": [ + "email" + ], + "title": "TokenRequest" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + }, + "WebhookConfig": { + "properties": { + "webhook_url": { + "type": "string", + "maxLength": 2083, + "minLength": 1, + "format": "uri", + "title": "Webhook Url" + }, + "webhook_data_in_payload": { + "type": "boolean", + "title": "Webhook Data In Payload", + "default": false + }, + "webhook_headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Webhook Headers" + } + }, + "type": "object", + "required": [ + "webhook_url" + ], + "title": "WebhookConfig", + "description": "Configuration for webhook notifications." + } + } + } +} diff --git a/openapitools.json b/openapitools.json new file mode 100644 index 0000000..8244df4 --- /dev/null +++ b/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "7.16.0" + } +} diff --git a/scripts/check_dirty_files.sh b/scripts/check_dirty_files.sh new file mode 100755 index 0000000..c328601 --- /dev/null +++ b/scripts/check_dirty_files.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +./scripts/generate_api_sdk.sh + +if git status --porcelain | grep -q "M"; then + echo "Error: There are dirty files" + exit 1 +fi \ No newline at end of file diff --git a/scripts/generate_api_sdk.sh b/scripts/generate_api_sdk.sh new file mode 100755 index 0000000..13af191 --- /dev/null +++ b/scripts/generate_api_sdk.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +openapi-generator-cli generate \ + -i ./api/crawl4ai-openapi.json \ + -g rust \ + -o ./api/crawl4ai-client \ + -t ./templates + +# find ./api/crawl4ai-client -type f -name "*.rs" -exec sed -i '' 's/use crate::models;/use crate::generated::models;/g' {} \; +# find ./api/crawl4ai-client -type f -name "*.rs" -exec sed -i '' 's/crate::apis/crate::generated::apis/g' {} \; +# find ./api/crawl4ai-client -type f -name "*.rs" -exec sed -i '' 's/use crate::{apis::ResponseContent, models};/use crate::generated::{apis::ResponseContent, models};/g' {} \; + +rm -rf ./src/generated/apis +rm -rf ./src/generated/models +cp -r ./api/crawl4ai-client/src/apis ./src/generated/apis +cp -r ./api/crawl4ai-client/src/models ./src/generated/models +rm -rf ./api/crawl4ai-client + +cargo fmt diff --git a/scripts/generate_templates.sh b/scripts/generate_templates.sh new file mode 100755 index 0000000..a8247e1 --- /dev/null +++ b/scripts/generate_templates.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Script to extract default Rust templates from OpenAPI Generator + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +echo "Extracting Rust templates from OpenAPI Generator..." + +cd "$PROJECT_ROOT" || exit 1 + +openapi-generator-cli author template \ + -g rust \ + -o ./templates diff --git a/scripts/verify_version.sh b/scripts/verify_version.sh new file mode 100755 index 0000000..1d1962b --- /dev/null +++ b/scripts/verify_version.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +set -e + +# Extract version from tag +# Can be provided as argument or extracted from GITHUB_REF +if [ -n "$1" ]; then + TAG_VERSION="$1" + # Remove 'v' prefix if present + TAG_VERSION="${TAG_VERSION#v}" +elif [ -n "$GITHUB_REF" ]; then + TAG_VERSION="${GITHUB_REF#refs/tags/v}" +else + echo "Error: No version provided. Usage: $0 or set GITHUB_REF" + echo "Example: $0 0.1.0 or $0 v0.1.0" + exit 1 +fi + +echo "Verifying version: $TAG_VERSION" + +# Extract version from Cargo.toml +CARGO_VERSION=$(grep '^version = ' Cargo.toml | head -1 | cut -d '"' -f 2) +if [ -z "$CARGO_VERSION" ]; then + echo "Error: Could not find version in Cargo.toml" + exit 1 +fi + +echo "Cargo.toml version: $CARGO_VERSION" + +# Extract versions from README.md +# Look for version in dependency declarations: version = "X.Y.Z" +README_VERSIONS=$(grep -o 'version = "[0-9]\+\.[0-9]\+\.[0-9]\+"' README.md | cut -d '"' -f 2 | sort -u) +if [ -z "$README_VERSIONS" ]; then + echo "Warning: Could not find version declarations in README.md" +else + echo "README.md versions found:" + echo "$README_VERSIONS" | while read -r ver; do + echo " - $ver" + done +fi + +# Verify Cargo.toml matches tag +if [ "$CARGO_VERSION" != "$TAG_VERSION" ]; then + echo "Error: Cargo.toml version ($CARGO_VERSION) does not match tag version ($TAG_VERSION)" + exit 1 +fi + +# Verify README versions match tag +if [ -n "$README_VERSIONS" ]; then + while IFS= read -r readme_ver; do + if [ "$readme_ver" != "$TAG_VERSION" ]; then + echo "Error: README.md version ($readme_ver) does not match tag version ($TAG_VERSION)" + exit 1 + fi + done <<< "$README_VERSIONS" +fi + +echo "✓ All versions match: $TAG_VERSION" +exit 0 \ No newline at end of file diff --git a/src/generated/apis/configuration.rs b/src/generated/apis/configuration.rs new file mode 100644 index 0000000..b42685e --- /dev/null +++ b/src/generated/apis/configuration.rs @@ -0,0 +1,48 @@ +/* + * Crawl4AI API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Debug, Clone)] +pub struct Configuration { + pub base_path: String, + pub user_agent: Option, + pub client: reqwest::Client, + pub basic_auth: Option, + pub oauth_access_token: Option, + pub bearer_access_token: Option, + pub api_key: Option, +} + +pub type BasicAuth = (String, Option); + +#[derive(Debug, Clone)] +pub struct ApiKey { + pub prefix: Option, + pub key: String, +} + +impl Configuration { + pub fn new() -> Configuration { + Configuration::default() + } +} + +impl Default for Configuration { + fn default() -> Self { + Configuration { + base_path: "http://localhost".to_owned(), + user_agent: Some("OpenAPI-Generator/1.0.0/rust".to_owned()), + client: reqwest::Client::new(), + basic_auth: None, + oauth_access_token: None, + bearer_access_token: None, + api_key: None, + } + } +} diff --git a/src/generated/apis/default_api.rs b/src/generated/apis/default_api.rs new file mode 100644 index 0000000..a681691 --- /dev/null +++ b/src/generated/apis/default_api.rs @@ -0,0 +1,1308 @@ +/* + * Crawl4AI API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +use super::{ContentType, Error, configuration}; +use crate::{apis::ResponseContent, models}; +use reqwest; +use serde::{Deserialize, Serialize, de::Error as _}; + +/// struct for typed errors of method [`config_dump_config_dump_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ConfigDumpConfigDumpPostError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`crawl_crawl_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum CrawlCrawlPostError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`crawl_job_enqueue_crawl_job_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum CrawlJobEnqueueCrawlJobPostError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`crawl_job_status_crawl_job_task_id_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum CrawlJobStatusCrawlJobTaskIdGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`crawl_stream_crawl_stream_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum CrawlStreamCrawlStreamPostError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`execute_js_execute_js_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ExecuteJsExecuteJsPostError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`generate_html_html_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GenerateHtmlHtmlPostError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`generate_pdf_pdf_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GeneratePdfPdfPostError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`generate_screenshot_screenshot_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GenerateScreenshotScreenshotPostError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_context_ask_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetContextAskGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_hooks_info_hooks_info_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetHooksInfoHooksInfoGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_markdown_md_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetMarkdownMdPostError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_schema_schema_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetSchemaSchemaGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_token_token_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetTokenTokenPostError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`health_health_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum HealthHealthGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`llm_endpoint_llm_url_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum LlmEndpointLlmUrlGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`llm_job_enqueue_llm_job_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum LlmJobEnqueueLlmJobPostError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`llm_job_status_llm_job_task_id_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum LlmJobStatusLlmJobTaskIdGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`mcp_sse_mcp_sse_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum McpSseMcpSseGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`metrics_metrics_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum MetricsMetricsGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`root_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum RootGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`schema_endpoint_mcp_schema_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SchemaEndpointMcpSchemaGetError { + UnknownValue(serde_json::Value), +} + +pub async fn config_dump_config_dump_post( + configuration: &configuration::Configuration, + raw_code: models::RawCode, +) -> Result> { + // add a prefix to parameters to efficiently prevent name collisions + let p_body_raw_code = raw_code; + + let uri_str = format!("{}/config/dump", configuration.base_path); + let mut req_builder = configuration + .client + .request(reqwest::Method::POST, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + req_builder = req_builder.json(&p_body_raw_code); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Crawl a list of URLs and return the results as JSON. For streaming responses, use /crawl/stream endpoint. Supports optional user-provided hook functions for customization. +pub async fn crawl_crawl_post( + configuration: &configuration::Configuration, + crawl_request_with_hooks: models::CrawlRequestWithHooks, +) -> Result> { + // add a prefix to parameters to efficiently prevent name collisions + let p_body_crawl_request_with_hooks = crawl_request_with_hooks; + + let uri_str = format!("{}/crawl", configuration.base_path); + let mut req_builder = configuration + .client + .request(reqwest::Method::POST, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + req_builder = req_builder.json(&p_body_crawl_request_with_hooks); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +pub async fn crawl_job_enqueue_crawl_job_post( + configuration: &configuration::Configuration, + crawl_job_payload: models::CrawlJobPayload, +) -> Result> { + // add a prefix to parameters to efficiently prevent name collisions + let p_body_crawl_job_payload = crawl_job_payload; + + let uri_str = format!("{}/crawl/job", configuration.base_path); + let mut req_builder = configuration + .client + .request(reqwest::Method::POST, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + req_builder = req_builder.json(&p_body_crawl_job_payload); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +pub async fn crawl_job_status_crawl_job_task_id_get( + configuration: &configuration::Configuration, + task_id: &str, +) -> Result> { + // add a prefix to parameters to efficiently prevent name collisions + let p_path_task_id = task_id; + + let uri_str = format!( + "{}/crawl/job/{task_id}", + configuration.base_path, + task_id = crate::apis::urlencode(p_path_task_id) + ); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +pub async fn crawl_stream_crawl_stream_post( + configuration: &configuration::Configuration, + crawl_request_with_hooks: models::CrawlRequestWithHooks, +) -> Result> { + // add a prefix to parameters to efficiently prevent name collisions + let p_body_crawl_request_with_hooks = crawl_request_with_hooks; + + let uri_str = format!("{}/crawl/stream", configuration.base_path); + let mut req_builder = configuration + .client + .request(reqwest::Method::POST, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + req_builder = req_builder.json(&p_body_crawl_request_with_hooks); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Execute a sequence of JavaScript snippets on the specified URL. Return the full CrawlResult JSON (first result). Use this when you need to interact with dynamic pages using JS. REMEMBER: Scripts accept a list of separated JS snippets to execute and execute them in order. IMPORTANT: Each script should be an expression that returns a value. It can be an IIFE or an async function. You can think of it as such. Your script will replace '{script}' and execute in the browser context. So provide either an IIFE or a sync/async function that returns a value. Return Format: - The return result is an instance of CrawlResult, so you have access to markdown, links, and other stuff. If this is enough, you don't need to call again for other endpoints. ```python class CrawlResult(BaseModel): url: str html: str success: bool cleaned_html: Optional[str] = None media: Dict[str, List[Dict]] = {} links: Dict[str, List[Dict]] = {} downloaded_files: Optional[List[str]] = None js_execution_result: Optional[Dict[str, Any]] = None screenshot: Optional[str] = None pdf: Optional[bytes] = None mhtml: Optional[str] = None _markdown: Optional[MarkdownGenerationResult] = PrivateAttr(default=None) extracted_content: Optional[str] = None metadata: Optional[dict] = None error_message: Optional[str] = None session_id: Optional[str] = None response_headers: Optional[dict] = None status_code: Optional[int] = None ssl_certificate: Optional[SSLCertificate] = None dispatch_result: Optional[DispatchResult] = None redirected_url: Optional[str] = None network_requests: Optional[List[Dict[str, Any]]] = None console_messages: Optional[List[Dict[str, Any]]] = None class MarkdownGenerationResult(BaseModel): raw_markdown: str markdown_with_citations: str references_markdown: str fit_markdown: Optional[str] = None fit_html: Optional[str] = None ``` +pub async fn execute_js_execute_js_post( + configuration: &configuration::Configuration, + js_endpoint_request: models::JsEndpointRequest, +) -> Result> { + // add a prefix to parameters to efficiently prevent name collisions + let p_body_js_endpoint_request = js_endpoint_request; + + let uri_str = format!("{}/execute_js", configuration.base_path); + let mut req_builder = configuration + .client + .request(reqwest::Method::POST, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + req_builder = req_builder.json(&p_body_js_endpoint_request); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Crawls the URL, preprocesses the raw HTML for schema extraction, and returns the processed HTML. Use when you need sanitized HTML structures for building schemas or further processing. +pub async fn generate_html_html_post( + configuration: &configuration::Configuration, + html_request: models::HtmlRequest, +) -> Result> { + // add a prefix to parameters to efficiently prevent name collisions + let p_body_html_request = html_request; + + let uri_str = format!("{}/html", configuration.base_path); + let mut req_builder = configuration + .client + .request(reqwest::Method::POST, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + req_builder = req_builder.json(&p_body_html_request); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Generate a PDF document of the specified URL, Use when you need a printable or archivable snapshot of the page. It is recommended to provide an output path to save the PDF. Then in result instead of the PDF you will get a path to the saved file. +pub async fn generate_pdf_pdf_post( + configuration: &configuration::Configuration, + pdf_request: models::PdfRequest, +) -> Result> { + // add a prefix to parameters to efficiently prevent name collisions + let p_body_pdf_request = pdf_request; + + let uri_str = format!("{}/pdf", configuration.base_path); + let mut req_builder = configuration + .client + .request(reqwest::Method::POST, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + req_builder = req_builder.json(&p_body_pdf_request); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Capture a full-page PNG screenshot of the specified URL, waiting an optional delay before capture, Use when you need an image snapshot of the rendered page. Its recommened to provide an output path to save the screenshot. Then in result instead of the screenshot you will get a path to the saved file. +pub async fn generate_screenshot_screenshot_post( + configuration: &configuration::Configuration, + screenshot_request: models::ScreenshotRequest, +) -> Result> { + // add a prefix to parameters to efficiently prevent name collisions + let p_body_screenshot_request = screenshot_request; + + let uri_str = format!("{}/screenshot", configuration.base_path); + let mut req_builder = configuration + .client + .request(reqwest::Method::POST, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + req_builder = req_builder.json(&p_body_screenshot_request); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// This end point is design for any questions about Crawl4ai library. It returns a plain text markdown with extensive information about Crawl4ai. You can use this as a context for any AI assistant. Use this endpoint for AI assistants to retrieve library context for decision making or code generation tasks. Alway is BEST practice you provide a query to filter the context. Otherwise the lenght of the response will be very long. Parameters: - context_type: Specify \"code\" for code context, \"doc\" for documentation context, or \"all\" for both. - query: RECOMMENDED search query to filter paragraphs using BM25. You can leave this empty to get all the context. - score_ratio: Minimum score as a fraction of the maximum score for filtering results. - max_results: Maximum number of results to return. Default is 20. Returns: - JSON response with the requested context. - If \"code\" is specified, returns the code context. - If \"doc\" is specified, returns the documentation context. - If \"all\" is specified, returns both code and documentation contexts. +pub async fn get_context_ask_get( + configuration: &configuration::Configuration, + context_type: Option<&str>, + query: Option<&str>, + score_ratio: Option, + max_results: Option, +) -> Result> { + // add a prefix to parameters to efficiently prevent name collisions + let p_query_context_type = context_type; + let p_query_query = query; + let p_query_score_ratio = score_ratio; + let p_query_max_results = max_results; + + let uri_str = format!("{}/ask", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref param_value) = p_query_context_type { + req_builder = req_builder.query(&[("context_type", ¶m_value.to_string())]); + } + if let Some(ref param_value) = p_query_query { + req_builder = req_builder.query(&[("query", ¶m_value.to_string())]); + } + if let Some(ref param_value) = p_query_score_ratio { + req_builder = req_builder.query(&[("score_ratio", ¶m_value.to_string())]); + } + if let Some(ref param_value) = p_query_max_results { + req_builder = req_builder.query(&[("max_results", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Get information about available hook points and their signatures +pub async fn get_hooks_info_hooks_info_get( + configuration: &configuration::Configuration, +) -> Result> { + let uri_str = format!("{}/hooks/info", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +pub async fn get_markdown_md_post( + configuration: &configuration::Configuration, + markdown_request: models::MarkdownRequest, +) -> Result> { + // add a prefix to parameters to efficiently prevent name collisions + let p_body_markdown_request = markdown_request; + + let uri_str = format!("{}/md", configuration.base_path); + let mut req_builder = configuration + .client + .request(reqwest::Method::POST, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + req_builder = req_builder.json(&p_body_markdown_request); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +pub async fn get_schema_schema_get( + configuration: &configuration::Configuration, +) -> Result> { + let uri_str = format!("{}/schema", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +pub async fn get_token_token_post( + configuration: &configuration::Configuration, + token_request: models::TokenRequest, +) -> Result> { + // add a prefix to parameters to efficiently prevent name collisions + let p_body_token_request = token_request; + + let uri_str = format!("{}/token", configuration.base_path); + let mut req_builder = configuration + .client + .request(reqwest::Method::POST, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + req_builder = req_builder.json(&p_body_token_request); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +pub async fn health_health_get( + configuration: &configuration::Configuration, +) -> Result> { + let uri_str = format!("{}/health", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +pub async fn llm_endpoint_llm_url_get( + configuration: &configuration::Configuration, + url: &str, + q: &str, +) -> Result> { + // add a prefix to parameters to efficiently prevent name collisions + let p_path_url = url; + let p_query_q = q; + + let uri_str = format!( + "{}/llm/{url}", + configuration.base_path, + url = crate::apis::urlencode(p_path_url) + ); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + req_builder = req_builder.query(&[("q", &p_query_q.to_string())]); + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +pub async fn llm_job_enqueue_llm_job_post( + configuration: &configuration::Configuration, + llm_job_payload: models::LlmJobPayload, +) -> Result> { + // add a prefix to parameters to efficiently prevent name collisions + let p_body_llm_job_payload = llm_job_payload; + + let uri_str = format!("{}/llm/job", configuration.base_path); + let mut req_builder = configuration + .client + .request(reqwest::Method::POST, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + req_builder = req_builder.json(&p_body_llm_job_payload); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +pub async fn llm_job_status_llm_job_task_id_get( + configuration: &configuration::Configuration, + task_id: &str, +) -> Result> { + // add a prefix to parameters to efficiently prevent name collisions + let p_path_task_id = task_id; + + let uri_str = format!( + "{}/llm/job/{task_id}", + configuration.base_path, + task_id = crate::apis::urlencode(p_path_task_id) + ); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +pub async fn mcp_sse_mcp_sse_get( + configuration: &configuration::Configuration, +) -> Result> { + let uri_str = format!("{}/mcp/sse", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +pub async fn metrics_metrics_get( + configuration: &configuration::Configuration, +) -> Result> { + let uri_str = format!("{}/metrics", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +pub async fn root_get( + configuration: &configuration::Configuration, +) -> Result> { + let uri_str = format!("{}/", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +pub async fn schema_endpoint_mcp_schema_get( + configuration: &configuration::Configuration, +) -> Result> { + let uri_str = format!("{}/mcp/schema", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `serde_json::Value`", + ))), + ContentType::Unsupported(unknown_type) => { + Err(Error::from(serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `serde_json::Value`" + )))) + } + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} diff --git a/src/generated/apis/mod.rs b/src/generated/apis/mod.rs new file mode 100644 index 0000000..7712316 --- /dev/null +++ b/src/generated/apis/mod.rs @@ -0,0 +1,118 @@ +use std::error; +use std::fmt; + +#[derive(Debug, Clone)] +pub struct ResponseContent { + pub status: reqwest::StatusCode, + pub content: String, + pub entity: Option, +} + +#[derive(Debug)] +pub enum Error { + Reqwest(reqwest::Error), + Serde(serde_json::Error), + Io(std::io::Error), + ResponseError(ResponseContent), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (module, e) = match self { + Error::Reqwest(e) => ("reqwest", e.to_string()), + Error::Serde(e) => ("serde", e.to_string()), + Error::Io(e) => ("IO", e.to_string()), + Error::ResponseError(e) => ("response", format!("status code {}", e.status)), + }; + write!(f, "error in {}: {}", module, e) + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + Some(match self { + Error::Reqwest(e) => e, + Error::Serde(e) => e, + Error::Io(e) => e, + Error::ResponseError(_) => return None, + }) + } +} + +impl From for Error { + fn from(e: reqwest::Error) -> Self { + Error::Reqwest(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Serde(e) + } +} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + Error::Io(e) + } +} + +pub fn urlencode>(s: T) -> String { + ::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect() +} + +pub fn parse_deep_object(prefix: &str, value: &serde_json::Value) -> Vec<(String, String)> { + if let serde_json::Value::Object(object) = value { + let mut params = vec![]; + + for (key, value) in object { + match value { + serde_json::Value::Object(_) => params.append(&mut parse_deep_object( + &format!("{}[{}]", prefix, key), + value, + )), + serde_json::Value::Array(array) => { + for (i, value) in array.iter().enumerate() { + params.append(&mut parse_deep_object( + &format!("{}[{}][{}]", prefix, key, i), + value, + )); + } + } + serde_json::Value::String(s) => { + params.push((format!("{}[{}]", prefix, key), s.clone())) + } + _ => params.push((format!("{}[{}]", prefix, key), value.to_string())), + } + } + + return params; + } + + unimplemented!("Only objects are supported with style=deepObject") +} + +/// Internal use only +/// A content type supported by this client. +#[allow(dead_code)] +enum ContentType { + Json, + Text, + Unsupported(String), +} + +impl From<&str> for ContentType { + fn from(content_type: &str) -> Self { + if content_type.starts_with("application") && content_type.contains("json") { + Self::Json + } else if content_type.starts_with("text/plain") { + Self::Text + } else { + Self::Unsupported(content_type.to_string()) + } + } +} + +pub mod default_api; + +pub mod configuration; diff --git a/src/generated/mod.rs b/src/generated/mod.rs new file mode 100644 index 0000000..84c9662 --- /dev/null +++ b/src/generated/mod.rs @@ -0,0 +1,9 @@ +#![allow(unused_imports)] +#![allow(clippy::too_many_arguments)] +extern crate reqwest; +extern crate serde; +extern crate serde_json; +extern crate url; + +pub mod apis; +pub mod models; diff --git a/src/generated/models/crawl_job_payload.rs b/src/generated/models/crawl_job_payload.rs new file mode 100644 index 0000000..d37a8ce --- /dev/null +++ b/src/generated/models/crawl_job_payload.rs @@ -0,0 +1,40 @@ +/* + * Crawl4AI API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct CrawlJobPayload { + #[serde(rename = "urls")] + pub urls: Vec, + #[serde(rename = "browser_config", skip_serializing_if = "Option::is_none")] + pub browser_config: Option>, + #[serde(rename = "crawler_config", skip_serializing_if = "Option::is_none")] + pub crawler_config: Option>, + #[serde( + rename = "webhook_config", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub webhook_config: Option>>, +} + +impl CrawlJobPayload { + pub fn new(urls: Vec) -> CrawlJobPayload { + CrawlJobPayload { + urls, + browser_config: None, + crawler_config: None, + webhook_config: None, + } + } +} diff --git a/src/generated/models/crawl_request_with_hooks.rs b/src/generated/models/crawl_request_with_hooks.rs new file mode 100644 index 0000000..7f02820 --- /dev/null +++ b/src/generated/models/crawl_request_with_hooks.rs @@ -0,0 +1,52 @@ +/* + * Crawl4AI API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +/// CrawlRequestWithHooks : Extended crawl request with hooks support +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct CrawlRequestWithHooks { + #[serde(rename = "urls")] + pub urls: Vec, + #[serde( + rename = "browser_config", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub browser_config: Option>>, + #[serde( + rename = "crawler_config", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub crawler_config: Option>>, + #[serde( + rename = "hooks", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub hooks: Option>>, +} + +impl CrawlRequestWithHooks { + /// Extended crawl request with hooks support + pub fn new(urls: Vec) -> CrawlRequestWithHooks { + CrawlRequestWithHooks { + urls, + browser_config: None, + crawler_config: None, + hooks: None, + } + } +} diff --git a/src/generated/models/filter_type.rs b/src/generated/models/filter_type.rs new file mode 100644 index 0000000..cbf7113 --- /dev/null +++ b/src/generated/models/filter_type.rs @@ -0,0 +1,41 @@ +/* + * Crawl4AI API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub enum FilterType { + #[serde(rename = "raw")] + Raw, + #[serde(rename = "fit")] + Fit, + #[serde(rename = "bm25")] + Bm25, + #[serde(rename = "llm")] + Llm, +} + +impl std::fmt::Display for FilterType { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Raw => write!(f, "raw"), + Self::Fit => write!(f, "fit"), + Self::Bm25 => write!(f, "bm25"), + Self::Llm => write!(f, "llm"), + } + } +} + +impl Default for FilterType { + fn default() -> FilterType { + Self::Raw + } +} diff --git a/src/generated/models/hook_config.rs b/src/generated/models/hook_config.rs new file mode 100644 index 0000000..606f385 --- /dev/null +++ b/src/generated/models/hook_config.rs @@ -0,0 +1,33 @@ +/* + * Crawl4AI API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +/// HookConfig : Configuration for user-provided hooks +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct HookConfig { + /// Map of hook points to Python code strings + #[serde(rename = "code", skip_serializing_if = "Option::is_none")] + pub code: Option>, + /// Timeout in seconds for each hook execution + #[serde(rename = "timeout", skip_serializing_if = "Option::is_none")] + pub timeout: Option, +} + +impl HookConfig { + /// Configuration for user-provided hooks + pub fn new() -> HookConfig { + HookConfig { + code: None, + timeout: None, + } + } +} diff --git a/src/generated/models/html_request.rs b/src/generated/models/html_request.rs new file mode 100644 index 0000000..d1770f1 --- /dev/null +++ b/src/generated/models/html_request.rs @@ -0,0 +1,24 @@ +/* + * Crawl4AI API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct HtmlRequest { + #[serde(rename = "url")] + pub url: String, +} + +impl HtmlRequest { + pub fn new(url: String) -> HtmlRequest { + HtmlRequest { url } + } +} diff --git a/src/generated/models/http_validation_error.rs b/src/generated/models/http_validation_error.rs new file mode 100644 index 0000000..a2075e8 --- /dev/null +++ b/src/generated/models/http_validation_error.rs @@ -0,0 +1,24 @@ +/* + * Crawl4AI API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct HttpValidationError { + #[serde(rename = "detail", skip_serializing_if = "Option::is_none")] + pub detail: Option>, +} + +impl HttpValidationError { + pub fn new() -> HttpValidationError { + HttpValidationError { detail: None } + } +} diff --git a/src/generated/models/js_endpoint_request.rs b/src/generated/models/js_endpoint_request.rs new file mode 100644 index 0000000..a2149d0 --- /dev/null +++ b/src/generated/models/js_endpoint_request.rs @@ -0,0 +1,27 @@ +/* + * Crawl4AI API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct JsEndpointRequest { + #[serde(rename = "url")] + pub url: String, + /// List of separated JavaScript snippets to execute + #[serde(rename = "scripts")] + pub scripts: Vec, +} + +impl JsEndpointRequest { + pub fn new(url: String, scripts: Vec) -> JsEndpointRequest { + JsEndpointRequest { url, scripts } + } +} diff --git a/src/generated/models/llm_job_payload.rs b/src/generated/models/llm_job_payload.rs new file mode 100644 index 0000000..8e5f852 --- /dev/null +++ b/src/generated/models/llm_job_payload.rs @@ -0,0 +1,72 @@ +/* + * Crawl4AI API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct LlmJobPayload { + #[serde(rename = "url")] + pub url: String, + #[serde(rename = "q")] + pub q: String, + #[serde( + rename = "schema", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub schema: Option>, + #[serde(rename = "cache", skip_serializing_if = "Option::is_none")] + pub cache: Option, + #[serde( + rename = "provider", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub provider: Option>, + #[serde( + rename = "webhook_config", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub webhook_config: Option>>, + #[serde( + rename = "temperature", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub temperature: Option>, + #[serde( + rename = "base_url", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub base_url: Option>, +} + +impl LlmJobPayload { + pub fn new(url: String, q: String) -> LlmJobPayload { + LlmJobPayload { + url, + q, + schema: None, + cache: None, + provider: None, + webhook_config: None, + temperature: None, + base_url: None, + } + } +} diff --git a/src/generated/models/markdown_request.rs b/src/generated/models/markdown_request.rs new file mode 100644 index 0000000..ac6f398 --- /dev/null +++ b/src/generated/models/markdown_request.rs @@ -0,0 +1,73 @@ +/* + * Crawl4AI API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +/// MarkdownRequest : Request body for the /md endpoint. +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct MarkdownRequest { + /// Absolute http/https URL to fetch + #[serde(rename = "url")] + pub url: String, + /// Content‑filter strategy: fit, raw, bm25, or llm + #[serde(rename = "f", skip_serializing_if = "Option::is_none")] + pub f: Option, + #[serde( + rename = "q", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub q: Option>, + #[serde( + rename = "c", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub c: Option>, + #[serde( + rename = "provider", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub provider: Option>, + #[serde( + rename = "temperature", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub temperature: Option>, + #[serde( + rename = "base_url", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub base_url: Option>, +} + +impl MarkdownRequest { + /// Request body for the /md endpoint. + pub fn new(url: String) -> MarkdownRequest { + MarkdownRequest { + url, + f: None, + q: None, + c: None, + provider: None, + temperature: None, + base_url: None, + } + } +} diff --git a/src/generated/models/mod.rs b/src/generated/models/mod.rs new file mode 100644 index 0000000..f31b675 --- /dev/null +++ b/src/generated/models/mod.rs @@ -0,0 +1,32 @@ +pub mod crawl_job_payload; +pub use self::crawl_job_payload::CrawlJobPayload; +pub mod crawl_request_with_hooks; +pub use self::crawl_request_with_hooks::CrawlRequestWithHooks; +pub mod filter_type; +pub use self::filter_type::FilterType; +pub mod hook_config; +pub use self::hook_config::HookConfig; +pub mod html_request; +pub use self::html_request::HtmlRequest; +pub mod http_validation_error; +pub use self::http_validation_error::HttpValidationError; +pub mod js_endpoint_request; +pub use self::js_endpoint_request::JsEndpointRequest; +pub mod llm_job_payload; +pub use self::llm_job_payload::LlmJobPayload; +pub mod markdown_request; +pub use self::markdown_request::MarkdownRequest; +pub mod pdf_request; +pub use self::pdf_request::PdfRequest; +pub mod raw_code; +pub use self::raw_code::RawCode; +pub mod screenshot_request; +pub use self::screenshot_request::ScreenshotRequest; +pub mod token_request; +pub use self::token_request::TokenRequest; +pub mod validation_error; +pub use self::validation_error::ValidationError; +pub mod validation_error_loc_inner; +pub use self::validation_error_loc_inner::ValidationErrorLocInner; +pub mod webhook_config; +pub use self::webhook_config::WebhookConfig; diff --git a/src/generated/models/pdf_request.rs b/src/generated/models/pdf_request.rs new file mode 100644 index 0000000..8fa5b46 --- /dev/null +++ b/src/generated/models/pdf_request.rs @@ -0,0 +1,34 @@ +/* + * Crawl4AI API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct PdfRequest { + #[serde(rename = "url")] + pub url: String, + #[serde( + rename = "output_path", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub output_path: Option>, +} + +impl PdfRequest { + pub fn new(url: String) -> PdfRequest { + PdfRequest { + url, + output_path: None, + } + } +} diff --git a/src/generated/models/raw_code.rs b/src/generated/models/raw_code.rs new file mode 100644 index 0000000..7047327 --- /dev/null +++ b/src/generated/models/raw_code.rs @@ -0,0 +1,24 @@ +/* + * Crawl4AI API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct RawCode { + #[serde(rename = "code")] + pub code: String, +} + +impl RawCode { + pub fn new(code: String) -> RawCode { + RawCode { code } + } +} diff --git a/src/generated/models/screenshot_request.rs b/src/generated/models/screenshot_request.rs new file mode 100644 index 0000000..1ddcf01 --- /dev/null +++ b/src/generated/models/screenshot_request.rs @@ -0,0 +1,42 @@ +/* + * Crawl4AI API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct ScreenshotRequest { + #[serde(rename = "url")] + pub url: String, + #[serde( + rename = "screenshot_wait_for", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub screenshot_wait_for: Option>, + #[serde( + rename = "output_path", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub output_path: Option>, +} + +impl ScreenshotRequest { + pub fn new(url: String) -> ScreenshotRequest { + ScreenshotRequest { + url, + screenshot_wait_for: None, + output_path: None, + } + } +} diff --git a/src/generated/models/token_request.rs b/src/generated/models/token_request.rs new file mode 100644 index 0000000..750e213 --- /dev/null +++ b/src/generated/models/token_request.rs @@ -0,0 +1,24 @@ +/* + * Crawl4AI API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct TokenRequest { + #[serde(rename = "email")] + pub email: String, +} + +impl TokenRequest { + pub fn new(email: String) -> TokenRequest { + TokenRequest { email } + } +} diff --git a/src/generated/models/validation_error.rs b/src/generated/models/validation_error.rs new file mode 100644 index 0000000..a066f3a --- /dev/null +++ b/src/generated/models/validation_error.rs @@ -0,0 +1,32 @@ +/* + * Crawl4AI API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct ValidationError { + #[serde(rename = "loc")] + pub loc: Vec, + #[serde(rename = "msg")] + pub msg: String, + #[serde(rename = "type")] + pub r#type: String, +} + +impl ValidationError { + pub fn new( + loc: Vec, + msg: String, + r#type: String, + ) -> ValidationError { + ValidationError { loc, msg, r#type } + } +} diff --git a/src/generated/models/validation_error_loc_inner.rs b/src/generated/models/validation_error_loc_inner.rs new file mode 100644 index 0000000..2b86bde --- /dev/null +++ b/src/generated/models/validation_error_loc_inner.rs @@ -0,0 +1,21 @@ +/* + * Crawl4AI API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct ValidationErrorLocInner {} + +impl ValidationErrorLocInner { + pub fn new() -> ValidationErrorLocInner { + ValidationErrorLocInner {} + } +} diff --git a/src/generated/models/webhook_config.rs b/src/generated/models/webhook_config.rs new file mode 100644 index 0000000..9dd6f50 --- /dev/null +++ b/src/generated/models/webhook_config.rs @@ -0,0 +1,42 @@ +/* + * Crawl4AI API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +/// WebhookConfig : Configuration for webhook notifications. +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct WebhookConfig { + #[serde(rename = "webhook_url")] + pub webhook_url: String, + #[serde( + rename = "webhook_data_in_payload", + skip_serializing_if = "Option::is_none" + )] + pub webhook_data_in_payload: Option, + #[serde( + rename = "webhook_headers", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub webhook_headers: Option>>, +} + +impl WebhookConfig { + /// Configuration for webhook notifications. + pub fn new(webhook_url: String) -> WebhookConfig { + WebhookConfig { + webhook_url, + webhook_data_in_payload: None, + webhook_headers: None, + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..086cd6d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3 @@ +mod generated; + +pub use generated::*; diff --git a/templates/.travis.yml b/templates/.travis.yml new file mode 100644 index 0000000..22761ba --- /dev/null +++ b/templates/.travis.yml @@ -0,0 +1 @@ +language: rust diff --git a/templates/Cargo.mustache b/templates/Cargo.mustache new file mode 100644 index 0000000..459cf3d --- /dev/null +++ b/templates/Cargo.mustache @@ -0,0 +1,121 @@ +[package] +name = "{{{packageName}}}" +version = "{{#lambdaVersion}}{{{packageVersion}}}{{/lambdaVersion}}" +{{#infoEmail}} +authors = ["{{{.}}}"] +{{/infoEmail}} +{{^infoEmail}} +authors = ["OpenAPI Generator team and contributors"] +{{/infoEmail}} +{{#appDescription}} +description = "{{{.}}}" +{{/appDescription}} +{{#licenseInfo}} +license = "{{.}}" +{{/licenseInfo}} +{{^licenseInfo}} +# Override this license by providing a License Object in the OpenAPI. +license = "Unlicense" +{{/licenseInfo}} +edition = "2021" +{{#publishRustRegistry}} +publish = ["{{.}}"] +{{/publishRustRegistry}} +{{#repositoryUrl}} +repository = "{{.}}" +{{/repositoryUrl}} +{{#documentationUrl}} +documentation = "{{.}}" +{{/documentationUrl}} +{{#homePageUrl}} +homepage = "{{.}}" +{{/homePageUrl}} + +[dependencies] +serde = { version = "^1.0", features = ["derive"] } +{{#serdeWith}} +serde_with = { version = "^3.8", default-features = false, features = ["base64", "std", "macros"] } +{{/serdeWith}} +serde_json = "^1.0" +serde_repr = "^0.1" +url = "^2.5" +{{#hasUUIDs}} +uuid = { version = "^1.8", features = ["serde", "v4"] } +{{/hasUUIDs}} +{{#hyper}} +{{#hyper0x}} +hyper = { version = "~0.14", features = ["full"] } +hyper-tls = "~0.5" +{{/hyper0x}} +{{^hyper0x}} +hyper = { version = "^1.3.1", features = ["full"] } +hyper-util = { version = "0.1.5", features = ["client", "client-legacy", "http1", "http2"] } +http-body-util = { version = "0.1.2" } +{{/hyper0x}} +http = "~0.2" +base64 = "~0.7.0" +futures = "^0.3" +{{/hyper}} +{{#withAWSV4Signature}} +aws-sigv4 = "0.3.0" +http = "0.2.5" +secrecy = "0.8.0" +{{/withAWSV4Signature}} +{{#reqwest}} +{{^supportAsync}} +reqwest = { version = "^0.12", default-features = false, features = ["json", "blocking", "multipart"] } +{{#supportMiddleware}} +reqwest-middleware = { version = "^0.4", features = ["json", "blocking", "multipart"] } +{{/supportMiddleware}} +{{/supportAsync}} +{{#supportAsync}} +{{#useAsyncFileStream}} +tokio = { version = "^1.46.0", features = ["fs"] } +tokio-util = { version = "^0.7", features = ["codec"] } +reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart", "stream"] } +{{/useAsyncFileStream}} +{{^useAsyncFileStream}} +reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart"] } +{{/useAsyncFileStream}} +{{#supportMiddleware}} +reqwest-middleware = { version = "^0.4", features = ["json", "multipart"] } +{{/supportMiddleware}} +{{#supportTokenSource}} +async-trait = "^0.1" +# TODO: propose to Yoshidan to externalize this as non google related crate, so that it can easily be extended for other cloud providers. +google-cloud-token = "^0.1" +{{/supportTokenSource}} +{{/supportAsync}} + +[features] +default = ["native-tls"] +native-tls = ["reqwest/native-tls"] +rustls-tls = ["reqwest/rustls-tls"] +{{/reqwest}} +{{#reqwestTrait}} +async-trait = "^0.1" +reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart"] } +{{#supportMiddleware}} +reqwest-middleware = { version = "^0.4", features = ["json", "multipart"] } +{{/supportMiddleware}} +{{#supportTokenSource}} +# TODO: propose to Yoshidan to externalize this as non google related crate, so that it can easily be extended for other cloud providers. +google-cloud-token = "^0.1" +{{/supportTokenSource}} +{{#mockall}} +mockall = { version = "^0.13", optional = true} +{{/mockall}} +{{#useBonBuilder}} +bon = { version = "2.3", optional = true } +{{/useBonBuilder}} +[features] +default = ["native-tls"] +native-tls = ["reqwest/native-tls"] +rustls-tls = ["reqwest/rustls-tls"] +{{#mockall}} +mockall = ["dep:mockall"] +{{/mockall}} +{{#useBonBuilder}} +bon = ["dep:bon"] +{{/useBonBuilder}} +{{/reqwestTrait}} diff --git a/templates/README.mustache b/templates/README.mustache new file mode 100644 index 0000000..0e8bfb3 --- /dev/null +++ b/templates/README.mustache @@ -0,0 +1,54 @@ +# Rust API client for {{{packageName}}} + +{{#appDescriptionWithNewLines}} +{{{.}}} +{{/appDescriptionWithNewLines}} + +{{#infoUrl}} +For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) +{{/infoUrl}} + +## Overview + +This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [openapi-spec](https://openapis.org) from a remote server, you can easily generate an API client. + +- API version: {{{appVersion}}} +- Package version: {{{packageVersion}}} +{{^hideGenerationTimestamp}} +- Build date: {{{generatedDate}}} +{{/hideGenerationTimestamp}} +- Generator version: {{generatorVersion}} +- Build package: `{{{generatorClass}}}` + +## Installation + +Put the package under your project folder in a directory named `{{packageName}}` and add the following to `Cargo.toml` under `[dependencies]`: + +``` +{{{packageName}}} = { path = "./{{{packageName}}}" } +``` + +## Documentation for API Endpoints + +All URIs are relative to *{{{basePath}}}* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{{classname}}}* | [**{{{operationId}}}**]({{{apiDocPath}}}{{classname}}.md#{{{operationIdLowerCase}}}) | **{{{httpMethod}}}** {{{path}}} | {{{summary}}} +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} + +## Documentation For Models + +{{#models}}{{#model}} - [{{{classname}}}]({{{modelDocPath}}}{{{classname}}}.md) +{{/model}}{{/models}} + +To get access to the crate's generated documentation, use: + +``` +cargo doc --open +``` + +## Author + +{{#apiInfo}}{{#apis}}{{#-last}}{{{infoEmail}}} +{{/-last}}{{/apis}}{{/apiInfo}} diff --git a/templates/api_doc.mustache b/templates/api_doc.mustache new file mode 100644 index 0000000..f8ea520 --- /dev/null +++ b/templates/api_doc.mustache @@ -0,0 +1,47 @@ +# {{{invokerPackage}}}\{{{classname}}}{{#description}} + +{{{.}}}{{/description}} + +All URIs are relative to *{{{basePath}}}* + +Method | HTTP request | Description +------------- | ------------- | ------------- +{{#operations}}{{#operation}}[**{{{operationId}}}**]({{{classname}}}.md#{{{operationId}}}) | **{{{httpMethod}}}** {{{path}}} | {{{summary}}} +{{/operation}}{{/operations}} + +{{#operations}} +{{#operation}} + +## {{{operationId}}} + +> {{#returnType}}{{{.}}} {{/returnType}}{{{operationId}}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}) +{{{summary}}}{{#notes}} + +{{{.}}}{{/notes}} + +### Parameters + +{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}} +Name | Type | Description | Required | Notes +------------- | ------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}} +{{#allParams}} +**{{{paramName}}}** | {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{#isPrimitiveType}}**{{{dataType}}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{{dataType}}}**]({{{baseType}}}.md){{/isPrimitiveType}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}} | {{{description}}} | {{#required}}[required]{{/required}} |{{#defaultValue}}[default to {{{.}}}]{{/defaultValue}} +{{/allParams}} + +### Return type + +{{#returnType}}{{#returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{{returnType}}}**]({{{returnBaseType}}}.md){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}} (empty response body){{/returnType}} + +### Authorization + +{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{{name}}}](../README.md#{{{name}}}){{^-last}}, {{/-last}}{{/authMethods}} + +### HTTP request headers + +- **Content-Type**: {{#consumes}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} +- **Accept**: {{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}{{^produces}}Not defined{{/produces}} + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +{{/operation}} +{{/operations}} diff --git a/templates/git_push.sh.mustache b/templates/git_push.sh.mustache new file mode 100644 index 0000000..0e3776a --- /dev/null +++ b/templates/git_push.sh.mustache @@ -0,0 +1,57 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 +git_host=$4 + +if [ "$git_host" = "" ]; then + git_host="{{{gitHost}}}" + echo "[INFO] No command line input provided. Set \$git_host to $git_host" +fi + +if [ "$git_user_id" = "" ]; then + git_user_id="{{{gitUserId}}}" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="{{{gitRepoId}}}" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="{{{releaseNote}}}" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=$(git remote) +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' diff --git a/templates/gitignore.mustache b/templates/gitignore.mustache new file mode 100644 index 0000000..6aa1064 --- /dev/null +++ b/templates/gitignore.mustache @@ -0,0 +1,3 @@ +/target/ +**/*.rs.bk +Cargo.lock diff --git a/templates/hyper/api.mustache b/templates/hyper/api.mustache new file mode 100644 index 0000000..9c0f7fd --- /dev/null +++ b/templates/hyper/api.mustache @@ -0,0 +1,182 @@ +{{>partial_header}} +use std::sync::Arc; +use std::borrow::Borrow; +use std::pin::Pin; +#[allow(unused_imports)] +use std::option::Option; + +use hyper; +use hyper_util::client::legacy::connect::Connect; +use futures::Future; + +use crate::models; +use super::{Error, configuration}; +use super::request as __internal_request; + +pub struct {{{classname}}}Client + where C: Clone + std::marker::Send + Sync + 'static { + configuration: Arc>, +} + +impl {{{classname}}}Client + where C: Clone + std::marker::Send + Sync { + pub fn new(configuration: Arc>) -> {{{classname}}}Client { + {{{classname}}}Client { + configuration, + } + } +} + +pub trait {{{classname}}}: Send + Sync { +{{#operations}} +{{#operation}} + fn {{{operationId}}}(&self, {{#allParams}}{{{paramName}}}: {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{#isString}}{{^isUuid}}&str{{/isUuid}}{{/isString}}{{#isUuid}}&str{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Pin> + Send>>; +{{/operation}} +{{/operations}} +} + +impl{{{classname}}} for {{{classname}}}Client + where C: Clone + std::marker::Send + Sync { + {{#operations}} + {{#operation}} + #[allow(unused_mut)] + fn {{{operationId}}}(&self, {{#allParams}}{{{paramName}}}: {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{#isString}}{{^isUuid}}&str{{/isUuid}}{{/isString}}{{#isUuid}}&str{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Pin> + Send>> { + let mut req = __internal_request::Request::new(hyper::Method::{{{httpMethod.toUpperCase}}}, "{{{path}}}".to_string()) + {{#hasAuthMethods}} + {{#authMethods}} + {{#isApiKey}} + .with_auth(__internal_request::Auth::ApiKey(__internal_request::ApiKey{ + in_header: {{#isKeyInHeader}}true{{/isKeyInHeader}}{{^isKeyInHeader}}false{{/isKeyInHeader}}, + in_query: {{#isKeyInQuery}}true{{/isKeyInQuery}}{{^isKeyInQuery}}false{{/isKeyInQuery}}, + param_name: "{{{keyParamName}}}".to_owned(), + })) + {{/isApiKey}} + {{#isBasicBasic}} + .with_auth(__internal_request::Auth::Basic) + {{/isBasicBasic}} + {{#isOAuth}} + .with_auth(__internal_request::Auth::Oauth) + {{/isOAuth}} + {{/authMethods}} + {{/hasAuthMethods}} + ; + {{#queryParams}} + {{#required}} + {{^isNullable}} + req = req.with_query_param("{{{baseName}}}".to_string(), {{{paramName}}}{{#isArray}}.join(","){{/isArray}}.to_string()); + {{/isNullable}} + {{#isNullable}} + match {{{paramName}}} { + Some(param_value) => { req = req.with_query_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); }, + None => { req = req.with_query_param("{{{baseName}}}".to_string(), "".to_string()); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(ref s) = {{{paramName}}} { + {{#isArray}} + let query_value = s.iter().map(|s| s.to_string()).collect::>().join(","); + {{/isArray}} + {{^isArray}} + let query_value = match serde_json::to_string(s) { + Ok(value) => value, + Err(e) => return Box::pin(futures::future::err(Error::Serde(e))), + }; + {{/isArray}} + req = req.with_query_param("{{{baseName}}}".to_string(), query_value); + } + {{/required}} + {{/queryParams}} + {{#pathParams}} + {{#required}} + {{^isNullable}} + req = req.with_path_param("{{{baseName}}}".to_string(), {{{paramName}}}{{#isArray}}.join(","){{/isArray}}.to_string()); + {{/isNullable}} + {{#isNullable}} + match {{{paramName}}} { + Some(param_value) => { req = req.with_path_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); }, + None => { req = req.with_path_param("{{{baseName}}}".to_string(), "".to_string()); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(param_value) = {{{paramName}}} { + req = req.with_path_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); + } + {{/required}} + {{/pathParams}} + {{#hasHeaderParams}} + {{#headerParams}} + {{#required}} + {{^isNullable}} + req = req.with_header_param("{{{baseName}}}".to_string(), {{{paramName}}}{{#isArray}}.join(","){{/isArray}}.to_string()); + {{/isNullable}} + {{#isNullable}} + match {{{paramName}}} { + Some(param_value) => { req = req.with_header_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); }, + None => { req = req.with_header_param("{{{baseName}}}".to_string(), "".to_string()); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(param_value) = {{{paramName}}} { + req = req.with_header_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); + } + {{/required}} + {{/headerParams}} + {{/hasHeaderParams}} + {{#hasFormParams}} + {{#formParams}} + {{#isFile}} + {{#required}} + {{^isNullable}} + req = req.with_form_param("{{{baseName}}}".to_string(), unimplemented!()); + {{/isNullable}} + {{#isNullable}} + match {{{paramName}}} { + Some(param_value) => { req = req.with_form_param("{{{baseName}}}".to_string(), unimplemented!()); }, + None => { req = req.with_form_param("{{{baseName}}}".to_string(), unimplemented!()); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(param_value) = {{{paramName}}} { + req = req.with_form_param("{{{baseName}}}".to_string(), unimplemented!()); + } + {{/required}} + {{/isFile}} + {{^isFile}} + {{#required}} + {{^isNullable}} + req = req.with_form_param("{{{baseName}}}".to_string(), {{{paramName}}}{{#isArray}}.join(","){{/isArray}}.to_string()); + {{/isNullable}} + {{#isNullable}} + match {{{paramName}}} { + Some(param_value) => { req = req.with_form_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); }, + None => { req = req.with_form_param("{{{baseName}}}".to_string(), "".to_string()); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(param_value) = {{{paramName}}} { + req = req.with_form_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); + } + {{/required}} + {{/isFile}} + {{/formParams}} + {{/hasFormParams}} + {{#hasBodyParam}} + {{#bodyParams}} + req = req.with_body_param({{{paramName}}}); + {{/bodyParams}} + {{/hasBodyParam}} + {{^returnType}} + req = req.returns_nothing(); + {{/returnType}} + + req.execute(self.configuration.borrow()) + } + +{{/operation}} +{{/operations}} +} diff --git a/templates/hyper/api_mod.mustache b/templates/hyper/api_mod.mustache new file mode 100644 index 0000000..67baa44 --- /dev/null +++ b/templates/hyper/api_mod.mustache @@ -0,0 +1,83 @@ +use std::fmt; +use std::fmt::Debug; + +use hyper; +use hyper::http; +use hyper_util::client::legacy::connect::Connect; +use serde_json; + +#[derive(Debug)] +pub enum Error { + Api(ApiError), + Header(http::header::InvalidHeaderValue), + Http(http::Error), + Hyper(hyper::Error), + HyperClient(hyper_util::client::legacy::Error), + Serde(serde_json::Error), + UriError(http::uri::InvalidUri), +} + +pub struct ApiError { + pub code: hyper::StatusCode, + pub body: hyper::body::Incoming, +} + +impl Debug for ApiError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ApiError") + .field("code", &self.code) + .field("body", &"hyper::body::Incoming") + .finish() + } +} + +impl From<(hyper::StatusCode, hyper::body::Incoming)> for Error { + fn from(e: (hyper::StatusCode, hyper::body::Incoming)) -> Self { + Error::Api(ApiError { + code: e.0, + body: e.1, + }) + } +} + +impl From for Error { + fn from(e: http::Error) -> Self { + Error::Http(e) + } +} + +impl From for Error { + fn from(e: hyper_util::client::legacy::Error) -> Self { + Error::HyperClient(e) + } +} + +impl From for Error { + fn from(e: hyper::Error) -> Self { + Error::Hyper(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Serde(e) + } +} + +mod request; + +{{#apiInfo}} +{{#apis}} +mod {{{classFilename}}}; +{{#operations}} +{{#operation}} +{{#-last}} +pub use self::{{{classFilename}}}::{ {{{classname}}}, {{{classname}}}Client }; +{{/-last}} +{{/operation}} +{{/operations}} +{{/apis}} +{{/apiInfo}} + +pub mod configuration; +pub mod client; diff --git a/templates/hyper/client.mustache b/templates/hyper/client.mustache new file mode 100644 index 0000000..d54e334 --- /dev/null +++ b/templates/hyper/client.mustache @@ -0,0 +1,55 @@ +use std::sync::Arc; + +use hyper; +use hyper_util::client::legacy::connect::Connect; +use super::configuration::Configuration; + +pub struct APIClient { +{{#apiInfo}} +{{#apis}} +{{#operations}} +{{#operation}} + {{#-last}} + {{{classFilename}}}: Box, + {{/-last}} +{{/operation}} +{{/operations}} +{{/apis}} +{{/apiInfo}} +} + +impl APIClient { + pub fn new(configuration: Configuration) -> APIClient + where C: Clone + std::marker::Send + Sync + 'static { + let rc = Arc::new(configuration); + + APIClient { +{{#apiInfo}} +{{#apis}} +{{#operations}} +{{#operation}} + {{#-last}} + {{{classFilename}}}: Box::new(crate::apis::{{{classname}}}Client::new(rc.clone())), + {{/-last}} +{{/operation}} +{{/operations}} +{{/apis}} +{{/apiInfo}} + } + } + +{{#apiInfo}} +{{#apis}} +{{#operations}} +{{#operation}} +{{#-last}} + pub fn {{{classFilename}}}(&self) -> &dyn crate::apis::{{{classname}}}{ + self.{{{classFilename}}}.as_ref() + } + +{{/-last}} +{{/operation}} +{{/operations}} +{{/apis}} +{{/apiInfo}} +} diff --git a/templates/hyper/configuration.mustache b/templates/hyper/configuration.mustache new file mode 100644 index 0000000..43a48ff --- /dev/null +++ b/templates/hyper/configuration.mustache @@ -0,0 +1,83 @@ +{{>partial_header}} +use hyper; +use hyper_util::client::legacy::Client; +use hyper_util::client::legacy::connect::Connect; +use hyper_util::client::legacy::connect::HttpConnector; +use hyper_util::rt::TokioExecutor; + +pub struct Configuration + where C: Clone + std::marker::Send + Sync + 'static { + pub base_path: String, + pub user_agent: Option, + pub client: Client, + pub basic_auth: Option, + pub oauth_access_token: Option, + pub api_key: Option, + // TODO: take an oauth2 token source, similar to the go one +} + +pub type BasicAuth = (String, Option); + +pub struct ApiKey { + pub prefix: Option, + pub key: String, +} + +impl Configuration { + /// Construct a default [`Configuration`](Self) with a hyper client using a default + /// [`HttpConnector`](hyper_util::client::legacy::connect::HttpConnector). + /// + /// Use [`with_client`](Configuration::with_client) to construct a Configuration with a + /// custom hyper client. + /// + /// # Example + /// + /// ``` + /// # use {{externCrateName}}::apis::configuration::Configuration; + /// let api_config = Configuration { + /// basic_auth: Some(("user".into(), None)), + /// ..Configuration::new() + /// }; + /// ``` + pub fn new() -> Configuration { + Configuration::default() + } +} + +impl Configuration + where C: Clone + std::marker::Send + Sync { + + /// Construct a new Configuration with a custom hyper client. + /// + /// # Example + /// + /// ``` + /// # use core::time::Duration; + /// # use {{externCrateName}}::apis::configuration::Configuration; + /// use hyper_util::client::legacy::Client; + /// use hyper_util::rt::TokioExecutor; + /// + /// let client = Client::builder(TokioExecutor::new()) + /// .pool_idle_timeout(Duration::from_secs(30)) + /// .build_http(); + /// + /// let api_config = Configuration::with_client(client); + /// ``` + pub fn with_client(client: Client) -> Configuration { + Configuration { + base_path: "{{{basePath}}}".to_owned(), + user_agent: {{#httpUserAgent}}Some("{{{.}}}".to_owned()){{/httpUserAgent}}{{^httpUserAgent}}Some("OpenAPI-Generator/{{{version}}}/rust".to_owned()){{/httpUserAgent}}, + client, + basic_auth: None, + oauth_access_token: None, + api_key: None, + } + } +} + +impl Default for Configuration { + fn default() -> Self { + let client = Client::builder(TokioExecutor::new()).build_http(); + Configuration::with_client(client) + } +} diff --git a/templates/hyper0x/api.mustache b/templates/hyper0x/api.mustache new file mode 100644 index 0000000..fe1a65c --- /dev/null +++ b/templates/hyper0x/api.mustache @@ -0,0 +1,181 @@ +{{>partial_header}} +use std::rc::Rc; +use std::borrow::Borrow; +use std::pin::Pin; +#[allow(unused_imports)] +use std::option::Option; + +use hyper; +use futures::Future; + +use crate::models; +use super::{Error, configuration}; +use super::request as __internal_request; + +pub struct {{{classname}}}Client + where C: Clone + std::marker::Send + Sync + 'static { + configuration: Rc>, +} + +impl {{{classname}}}Client + where C: Clone + std::marker::Send + Sync { + pub fn new(configuration: Rc>) -> {{{classname}}}Client { + {{{classname}}}Client { + configuration, + } + } +} + +pub trait {{{classname}}} { +{{#operations}} +{{#operation}} + fn {{{operationId}}}(&self, {{#allParams}}{{{paramName}}}: {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{#isString}}{{^isUuid}}&str{{/isUuid}}{{/isString}}{{#isUuid}}&str{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Pin>>>; +{{/operation}} +{{/operations}} +} + +impl{{{classname}}} for {{{classname}}}Client + where C: Clone + std::marker::Send + Sync { + {{#operations}} + {{#operation}} + #[allow(unused_mut)] + fn {{{operationId}}}(&self, {{#allParams}}{{{paramName}}}: {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{#isString}}{{^isUuid}}&str{{/isUuid}}{{/isString}}{{#isUuid}}&str{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Pin>>> { + let mut req = __internal_request::Request::new(hyper::Method::{{{httpMethod.toUpperCase}}}, "{{{path}}}".to_string()) + {{#hasAuthMethods}} + {{#authMethods}} + {{#isApiKey}} + .with_auth(__internal_request::Auth::ApiKey(__internal_request::ApiKey{ + in_header: {{#isKeyInHeader}}true{{/isKeyInHeader}}{{^isKeyInHeader}}false{{/isKeyInHeader}}, + in_query: {{#isKeyInQuery}}true{{/isKeyInQuery}}{{^isKeyInQuery}}false{{/isKeyInQuery}}, + param_name: "{{{keyParamName}}}".to_owned(), + })) + {{/isApiKey}} + {{#isBasicBasic}} + .with_auth(__internal_request::Auth::Basic) + {{/isBasicBasic}} + {{#isOAuth}} + .with_auth(__internal_request::Auth::Oauth) + {{/isOAuth}} + {{/authMethods}} + {{/hasAuthMethods}} + ; + {{#queryParams}} + {{#required}} + {{^isNullable}} + req = req.with_query_param("{{{baseName}}}".to_string(), {{{paramName}}}{{#isArray}}.join(","){{/isArray}}.to_string()); + {{/isNullable}} + {{#isNullable}} + match {{{paramName}}} { + Some(param_value) => { req = req.with_query_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); }, + None => { req = req.with_query_param("{{{baseName}}}".to_string(), "".to_string()); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(ref s) = {{{paramName}}} { + {{#isArray}} + let query_value = s.iter().map(|s| s.to_string()).collect::>().join(","); + {{/isArray}} + {{^isArray}} + let query_value = match serde_json::to_string(s) { + Ok(value) => value, + Err(e) => return Box::pin(futures::future::err(Error::Serde(e))), + }; + {{/isArray}} + req = req.with_query_param("{{{baseName}}}".to_string(), query_value); + } + {{/required}} + {{/queryParams}} + {{#pathParams}} + {{#required}} + {{^isNullable}} + req = req.with_path_param("{{{baseName}}}".to_string(), {{{paramName}}}{{#isArray}}.join(","){{/isArray}}.to_string()); + {{/isNullable}} + {{#isNullable}} + match {{{paramName}}} { + Some(param_value) => { req = req.with_path_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); }, + None => { req = req.with_path_param("{{{baseName}}}".to_string(), "".to_string()); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(param_value) = {{{paramName}}} { + req = req.with_path_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); + } + {{/required}} + {{/pathParams}} + {{#hasHeaderParams}} + {{#headerParams}} + {{#required}} + {{^isNullable}} + req = req.with_header_param("{{{baseName}}}".to_string(), {{{paramName}}}{{#isArray}}.join(","){{/isArray}}.to_string()); + {{/isNullable}} + {{#isNullable}} + match {{{paramName}}} { + Some(param_value) => { req = req.with_header_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); }, + None => { req = req.with_header_param("{{{baseName}}}".to_string(), "".to_string()); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(param_value) = {{{paramName}}} { + req = req.with_header_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); + } + {{/required}} + {{/headerParams}} + {{/hasHeaderParams}} + {{#hasFormParams}} + {{#formParams}} + {{#isFile}} + {{#required}} + {{^isNullable}} + req = req.with_form_param("{{{baseName}}}".to_string(), unimplemented!()); + {{/isNullable}} + {{#isNullable}} + match {{{paramName}}} { + Some(param_value) => { req = req.with_form_param("{{{baseName}}}".to_string(), unimplemented!()); }, + None => { req = req.with_form_param("{{{baseName}}}".to_string(), unimplemented!()); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(param_value) = {{{paramName}}} { + req = req.with_form_param("{{{baseName}}}".to_string(), unimplemented!()); + } + {{/required}} + {{/isFile}} + {{^isFile}} + {{#required}} + {{^isNullable}} + req = req.with_form_param("{{{baseName}}}".to_string(), {{{paramName}}}{{#isArray}}.join(","){{/isArray}}.to_string()); + {{/isNullable}} + {{#isNullable}} + match {{{paramName}}} { + Some(param_value) => { req = req.with_form_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); }, + None => { req = req.with_form_param("{{{baseName}}}".to_string(), "".to_string()); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(param_value) = {{{paramName}}} { + req = req.with_form_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); + } + {{/required}} + {{/isFile}} + {{/formParams}} + {{/hasFormParams}} + {{#hasBodyParam}} + {{#bodyParams}} + req = req.with_body_param({{{paramName}}}); + {{/bodyParams}} + {{/hasBodyParam}} + {{^returnType}} + req = req.returns_nothing(); + {{/returnType}} + + req.execute(self.configuration.borrow()) + } + +{{/operation}} +{{/operations}} +} diff --git a/templates/hyper0x/api_mod.mustache b/templates/hyper0x/api_mod.mustache new file mode 100644 index 0000000..51a42ef --- /dev/null +++ b/templates/hyper0x/api_mod.mustache @@ -0,0 +1,64 @@ +use http; +use hyper; +use serde_json; + +#[derive(Debug)] +pub enum Error { + Api(ApiError), + Header(hyper::http::header::InvalidHeaderValue), + Http(http::Error), + Hyper(hyper::Error), + Serde(serde_json::Error), + UriError(http::uri::InvalidUri), +} + +#[derive(Debug)] +pub struct ApiError { + pub code: hyper::StatusCode, + pub body: hyper::body::Body, +} + +impl From<(hyper::StatusCode, hyper::body::Body)> for Error { + fn from(e: (hyper::StatusCode, hyper::body::Body)) -> Self { + Error::Api(ApiError { + code: e.0, + body: e.1, + }) + } +} + +impl From for Error { + fn from(e: http::Error) -> Self { + Error::Http(e) + } +} + +impl From for Error { + fn from(e: hyper::Error) -> Self { + Error::Hyper(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Serde(e) + } +} + +mod request; + +{{#apiInfo}} +{{#apis}} +mod {{{classFilename}}}; +{{#operations}} +{{#operation}} +{{#-last}} +pub use self::{{{classFilename}}}::{ {{{classname}}}, {{{classname}}}Client }; +{{/-last}} +{{/operation}} +{{/operations}} +{{/apis}} +{{/apiInfo}} + +pub mod configuration; +pub mod client; diff --git a/templates/hyper0x/client.mustache b/templates/hyper0x/client.mustache new file mode 100644 index 0000000..25124d9 --- /dev/null +++ b/templates/hyper0x/client.mustache @@ -0,0 +1,54 @@ +use std::rc::Rc; + +use hyper; +use super::configuration::Configuration; + +pub struct APIClient { +{{#apiInfo}} +{{#apis}} +{{#operations}} +{{#operation}} + {{#-last}} + {{{classFilename}}}: Box, + {{/-last}} +{{/operation}} +{{/operations}} +{{/apis}} +{{/apiInfo}} +} + +impl APIClient { + pub fn new(configuration: Configuration) -> APIClient + where C: Clone + std::marker::Send + Sync + 'static { + let rc = Rc::new(configuration); + + APIClient { +{{#apiInfo}} +{{#apis}} +{{#operations}} +{{#operation}} + {{#-last}} + {{{classFilename}}}: Box::new(crate::apis::{{{classname}}}Client::new(rc.clone())), + {{/-last}} +{{/operation}} +{{/operations}} +{{/apis}} +{{/apiInfo}} + } + } + +{{#apiInfo}} +{{#apis}} +{{#operations}} +{{#operation}} +{{#-last}} + pub fn {{{classFilename}}}(&self) -> &dyn crate::apis::{{{classname}}}{ + self.{{{classFilename}}}.as_ref() + } + +{{/-last}} +{{/operation}} +{{/operations}} +{{/apis}} +{{/apiInfo}} +} diff --git a/templates/hyper0x/configuration.mustache b/templates/hyper0x/configuration.mustache new file mode 100644 index 0000000..ee6f407 --- /dev/null +++ b/templates/hyper0x/configuration.mustache @@ -0,0 +1,34 @@ +{{>partial_header}} +use hyper; + +pub struct Configuration + where C: Clone + std::marker::Send + Sync + 'static { + pub base_path: String, + pub user_agent: Option, + pub client: hyper::client::Client, + pub basic_auth: Option, + pub oauth_access_token: Option, + pub api_key: Option, + // TODO: take an oauth2 token source, similar to the go one +} + +pub type BasicAuth = (String, Option); + +pub struct ApiKey { + pub prefix: Option, + pub key: String, +} + +impl Configuration + where C: Clone + std::marker::Send + Sync { + pub fn new(client: hyper::client::Client) -> Configuration { + Configuration { + base_path: "{{{basePath}}}".to_owned(), + user_agent: {{#httpUserAgent}}Some("{{{.}}}".to_owned()){{/httpUserAgent}}{{^httpUserAgent}}Some("OpenAPI-Generator/{{{version}}}/rust".to_owned()){{/httpUserAgent}}, + client, + basic_auth: None, + oauth_access_token: None, + api_key: None, + } + } +} diff --git a/templates/lib.mustache b/templates/lib.mustache new file mode 100644 index 0000000..b51df2b --- /dev/null +++ b/templates/lib.mustache @@ -0,0 +1,17 @@ +#![allow(unused_imports)] +#![allow(clippy::too_many_arguments)] + +extern crate serde_repr; +extern crate serde; +extern crate serde_json; +extern crate url; +{{#hyper}} +extern crate hyper; +extern crate futures; +{{/hyper}} +{{#reqwest}} +extern crate reqwest; +{{/reqwest}} + +pub mod apis; +pub mod models; diff --git a/templates/model.mustache b/templates/model.mustache new file mode 100644 index 0000000..34bba92 --- /dev/null +++ b/templates/model.mustache @@ -0,0 +1,229 @@ +{{>partial_header}} +use crate::models; +use serde::{Deserialize, Serialize}; +{{#models}} +{{#model}} +{{^isEnum}}{{#vendorExtensions.x-rust-has-byte-array}} +use serde_with::serde_as; +{{/vendorExtensions.x-rust-has-byte-array}}{{/isEnum}} +{{#isEnum}} +{{#isInteger}} +use serde_repr::{Serialize_repr,Deserialize_repr}; +{{/isInteger}} +{{/isEnum}} +{{#description}} +/// {{{classname}}} : {{{description}}} +{{/description}} +{{!-- for repr(int) enum schemas --}} +{{#isEnum}} +{{#isInteger}} +{{#description}} +/// {{{description}}} +{{/description}} +#[repr(i64)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr)] +pub enum {{{classname}}} { +{{#allowableValues}} +{{#enumVars}} + {{{name}}} = {{{value}}}, +{{/enumVars}}{{/allowableValues}} +} + +impl std::fmt::Display for {{{classname}}} { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", match self { + {{#allowableValues}} + {{#enumVars}} + Self::{{{name}}} => "{{{value}}}", + {{/enumVars}} + {{/allowableValues}} + }) + } +} +{{/isInteger}} +{{/isEnum}} +{{!-- for enum schemas --}} +{{#isEnum}} +{{^isInteger}} +{{#description}} +/// {{{description}}} +{{/description}} +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub enum {{{classname}}} { +{{#allowableValues}} +{{#enumVars}} + #[serde(rename = "{{{value}}}")] + {{{name}}}, +{{/enumVars}}{{/allowableValues}} +} + +impl std::fmt::Display for {{{classname}}} { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + {{#allowableValues}} + {{#enumVars}} + Self::{{{name}}} => write!(f, "{{{value}}}"), + {{/enumVars}} + {{/allowableValues}} + } + } +} + +{{/isInteger}} +impl Default for {{{classname}}} { + fn default() -> {{{classname}}} { + {{#allowableValues}} + Self::{{ enumVars.0.name }} + {{/allowableValues}} + } +} +{{/isEnum}} +{{!-- for schemas that have a discriminator --}} +{{#discriminator}} +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(tag = "{{{propertyBaseName}}}")] +pub enum {{{classname}}} { + {{^oneOf}} + {{#mappedModels}} + #[serde(rename="{{mappingName}}")] + {{{modelName}}} { + {{#vars}} + {{#description}} + /// {{{.}}} + {{/description}} + #[serde(rename = "{{{baseName}}}"{{^required}}, skip_serializing_if = "Option::is_none"{{/required}})] + {{{name}}}: {{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{^required}}Option<{{/required}}{{#isEnum}}{{{enumName}}}{{/isEnum}}{{^isEnum}}{{#isModel}}{{^avoidBoxedModels}}Box<{{/avoidBoxedModels}}{{{dataType}}}{{^avoidBoxedModels}}>{{/avoidBoxedModels}}{{/isModel}}{{^isModel}}{{{dataType}}}{{/isModel}}{{/isEnum}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{^required}}>{{/required}}, + {{/vars}} + }, + {{/mappedModels}} + {{/oneOf}} + {{^oneOf.isEmpty}} + {{#composedSchemas.oneOf}} + {{#description}} + /// {{{.}}} + {{/description}} + {{#baseName}} + #[serde(rename="{{{.}}}")] + {{/baseName}} + {{{name}}}({{#isModel}}{{^avoidBoxedModels}}Box<{{/avoidBoxedModels}}{{/isModel}}{{{dataType}}}{{#isModel}}{{^avoidBoxedModels}}>{{/avoidBoxedModels}}{{/isModel}}), + {{/composedSchemas.oneOf}} + {{/oneOf.isEmpty}} +} + +impl Default for {{classname}} { + fn default() -> Self { + {{^oneOf}}{{#mappedModels}}{{#-first}}Self::{{modelName}} { + {{#vars}} + {{{name}}}: Default::default(), + {{/vars}} + }{{/-first}}{{/mappedModels}} + {{/oneOf}}{{^oneOf.isEmpty}}{{#composedSchemas.oneOf}}{{#-first}}Self::{{{name}}}(Default::default()){{/-first}}{{/composedSchemas.oneOf}}{{/oneOf.isEmpty}} + } +} + +{{/discriminator}} +{{!-- for non-enum schemas --}} +{{^isEnum}} +{{^discriminator}} +{{#vendorExtensions.x-rust-has-byte-array}}#[serde_as] +{{/vendorExtensions.x-rust-has-byte-array}}{{#oneOf.isEmpty}}#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct {{{classname}}} { +{{#vars}} + {{#description}} + /// {{{.}}} + {{/description}} + {{#isByteArray}} + {{#vendorExtensions.isMandatory}}#[serde_as(as = "serde_with::base64::Base64")]{{/vendorExtensions.isMandatory}}{{^vendorExtensions.isMandatory}}#[serde_as(as = "{{^serdeAsDoubleOption}}Option{{/serdeAsDoubleOption}}{{#serdeAsDoubleOption}}super::DoubleOption{{/serdeAsDoubleOption}}")]{{/vendorExtensions.isMandatory}} + {{/isByteArray}} + #[serde(rename = "{{{baseName}}}"{{^required}}{{#isNullable}}, default{{^isByteArray}}, with = "::serde_with::rust::double_option"{{/isByteArray}}{{/isNullable}}{{/required}}{{^required}}, skip_serializing_if = "Option::is_none"{{/required}}{{#required}}{{#isNullable}}, deserialize_with = "Option::deserialize"{{/isNullable}}{{/required}})] + pub {{{name}}}: {{! + ### Option Start + }}{{#isNullable}}Option<{{/isNullable}}{{^required}}Option<{{/required}}{{! + ### Enums + }}{{#isEnum}}{{#isArray}}{{#uniqueItems}}std::collections::HashSet<{{/uniqueItems}}{{^uniqueItems}}Vec<{{/uniqueItems}}{{/isArray}}{{{enumName}}}{{#isArray}}>{{/isArray}}{{/isEnum}}{{! + ### Non-Enums Start + }}{{^isEnum}}{{! + ### Models + }}{{#isModel}}{{^avoidBoxedModels}}Box<{{/avoidBoxedModels}}{{{dataType}}}{{^avoidBoxedModels}}>{{/avoidBoxedModels}}{{/isModel}}{{! + ### Primative datatypes + }}{{^isModel}}{{#isByteArray}}Vec{{/isByteArray}}{{^isByteArray}}{{{dataType}}}{{/isByteArray}}{{/isModel}}{{! + ### Non-Enums End + }}{{/isEnum}}{{! + ### Option End (and trailing comma) + }}{{#isNullable}}>{{/isNullable}}{{^required}}>{{/required}}, +{{/vars}} +} + +impl {{{classname}}} { + {{#description}} + /// {{{.}}} + {{/description}} + pub fn new({{#requiredVars}}{{{name}}}: {{! + ### Option Start + }}{{#isNullable}}Option<{{/isNullable}}{{! + ### Enums + }}{{#isEnum}}{{#isArray}}{{#uniqueItems}}std::collections::HashSet<{{/uniqueItems}}{{^uniqueItems}}Vec<{{/uniqueItems}}{{/isArray}}{{{enumName}}}{{#isArray}}>{{/isArray}}{{/isEnum}}{{! + ### Non-Enums + }}{{^isEnum}}{{#isByteArray}}Vec{{/isByteArray}}{{^isByteArray}}{{{dataType}}}{{/isByteArray}}{{/isEnum}}{{! + ### Option End + }}{{#isNullable}}>{{/isNullable}}{{! + ### Comma for next arguement + }}{{^-last}}, {{/-last}}{{/requiredVars}}) -> {{{classname}}} { + {{{classname}}} { + {{#vars}} + {{{name}}}{{^required}}: None{{/required}}{{#required}}{{#isModel}}{{^avoidBoxedModels}}: {{^isNullable}}Box::new({{{name}}}){{/isNullable}}{{#isNullable}}if let Some(x) = {{{name}}} {Some(Box::new(x))} else {None}{{/isNullable}}{{/avoidBoxedModels}}{{/isModel}}{{/required}}, + {{/vars}} + } + } +} +{{/oneOf.isEmpty}} +{{^oneOf.isEmpty}} +{{! TODO: add other vars that are not part of the oneOf}} +{{#description}} +/// {{{.}}} +{{/description}} +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum {{classname}} { +{{#composedSchemas.oneOf}} + {{#description}} + /// {{{.}}} + {{/description}} + {{{name}}}({{#isModel}}{{^avoidBoxedModels}}Box<{{/avoidBoxedModels}}{{/isModel}}{{{dataType}}}{{#isModel}}{{^avoidBoxedModels}}>{{/avoidBoxedModels}}{{/isModel}}), +{{/composedSchemas.oneOf}} +} + +impl Default for {{classname}} { + fn default() -> Self { + {{#composedSchemas.oneOf}}{{#-first}}Self::{{{name}}}(Default::default()){{/-first}}{{/composedSchemas.oneOf}} + } +} +{{/oneOf.isEmpty}} +{{/discriminator}} +{{/isEnum}} +{{!-- for properties that are of enum type --}} +{{#vars}} +{{#isEnum}} +/// {{{description}}} +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub enum {{{enumName}}} { +{{#allowableValues}} +{{#enumVars}} + #[serde(rename = "{{{value}}}")] + {{{name}}}, +{{/enumVars}} +{{/allowableValues}} +} + +impl Default for {{{enumName}}} { + fn default() -> {{{enumName}}} { + {{#allowableValues}} + Self::{{ enumVars.0.name }} + {{/allowableValues}} + } +} +{{/isEnum}} +{{/vars}} + +{{/model}} +{{/models}} diff --git a/templates/model_doc.mustache b/templates/model_doc.mustache new file mode 100644 index 0000000..2aba759 --- /dev/null +++ b/templates/model_doc.mustache @@ -0,0 +1,54 @@ +{{#models}}{{#model}}# {{{classname}}} +{{#isEnum}} + +## Enum Variants + +| Name | Value | +|---- | -----|{{#allowableValues}}{{#enumVars}} +| {{name}} | {{value}} |{{/enumVars}}{{/allowableValues}} + +{{/isEnum}} +{{#discriminator}} + +## Enum Variants + +| Name | Value | +|---- | -----|{{#vendorExtensions}}{{#x-mapped-models}} +| {{modelName}} | {{mappingName}} |{{/x-mapped-models}}{{/vendorExtensions}} +{{#vendorExtensions}} +{{#x-mapped-models}} + +## {{modelName}} + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +{{#vars}}**{{{name}}}** | {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{#isPrimitiveType}}**{{{dataType}}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{{dataType}}}**]({{{complexType}}}.md){{/isPrimitiveType}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}} | {{{description}}} | {{^required}}[optional]{{/required}}{{#isReadOnly}}[readonly]{{/isReadOnly}}{{#defaultValue}}[default to {{{.}}}]{{/defaultValue}} +{{/vars}} +{{/x-mapped-models}} +{{/vendorExtensions}} +{{/discriminator}} +{{^discriminator}} +{{^isEnum}} +{{#oneOf.isEmpty}} + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +{{#vars}}**{{{name}}}** | {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{#isPrimitiveType}}**{{{dataType}}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{{dataType}}}**]({{{complexType}}}.md){{/isPrimitiveType}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}} | {{{description}}} | {{^required}}[optional]{{/required}}{{#isReadOnly}}[readonly]{{/isReadOnly}}{{#defaultValue}}[default to {{{.}}}]{{/defaultValue}} +{{/vars}} +{{/oneOf.isEmpty}} +{{^oneOf.isEmpty}} + +## Enum Variants + +| Name | Description | +|---- | -----|{{#oneOf}} +| {{{.}}} | {{description}} |{{/oneOf}} +{{/oneOf.isEmpty}} +{{/isEnum}} +{{/discriminator}} + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + +{{/model}}{{/models}} diff --git a/templates/model_mod.mustache b/templates/model_mod.mustache new file mode 100644 index 0000000..bdb080f --- /dev/null +++ b/templates/model_mod.mustache @@ -0,0 +1,45 @@ +{{#models}} +{{#model}} +pub mod {{{classFilename}}}; +pub use self::{{{classFilename}}}::{{{classname}}}; +{{/model}} +{{/models}} +{{#serdeAsDoubleOption}} +use serde::{Deserialize, Deserializer, Serializer}; +use serde_with::{de::DeserializeAsWrap, ser::SerializeAsWrap, DeserializeAs, SerializeAs}; +use std::marker::PhantomData; + +pub(crate) struct DoubleOption(PhantomData); + +impl SerializeAs>> for DoubleOption +where + TAs: SerializeAs, +{ + fn serialize_as(values: &Option>, serializer: S) -> Result + where + S: Serializer, + { + match values { + None => serializer.serialize_unit(), + Some(None) => serializer.serialize_none(), + Some(Some(v)) => serializer.serialize_some(&SerializeAsWrap::::new(v)), + } + } +} + +impl<'de, T, TAs> DeserializeAs<'de, Option>> for DoubleOption +where + TAs: DeserializeAs<'de, T>, + T: std::fmt::Debug, +{ + fn deserialize_as(deserializer: D) -> Result>, D::Error> + where + D: Deserializer<'de>, + { + Ok(Some( + DeserializeAsWrap::, Option>::deserialize(deserializer)? + .into_inner(), + )) + } +} +{{/serdeAsDoubleOption}} \ No newline at end of file diff --git a/templates/partial_header.mustache b/templates/partial_header.mustache new file mode 100644 index 0000000..408d841 --- /dev/null +++ b/templates/partial_header.mustache @@ -0,0 +1,13 @@ +/* + {{#appName}} + * {{{.}}} + * + {{/appName}} + {{#appDescription}} + * {{{.}}} + * + {{/appDescription}} + * {{#version}}The version of the OpenAPI document: {{{.}}}{{/version}} + * {{#infoEmail}}Contact: {{{.}}}{{/infoEmail}} + * Generated by: https://openapi-generator.tech + */ diff --git a/templates/request.rs b/templates/request.rs new file mode 100644 index 0000000..a6f7b74 --- /dev/null +++ b/templates/request.rs @@ -0,0 +1,247 @@ +use std::collections::HashMap; +use std::pin::Pin; + +use futures; +use futures::Future; +use futures::future::*; +use http_body_util::BodyExt; +use hyper; +use hyper_util::client::legacy::connect::Connect; +use hyper::header::{AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE, HeaderValue, USER_AGENT}; +use serde; +use serde_json; + +use super::{configuration, Error}; + +pub(crate) struct ApiKey { + pub in_header: bool, + pub in_query: bool, + pub param_name: String, +} + +impl ApiKey { + fn key(&self, prefix: &Option, key: &str) -> String { + match prefix { + None => key.to_owned(), + Some(ref prefix) => format!("{} {}", prefix, key), + } + } +} + +#[allow(dead_code)] +pub(crate) enum Auth { + None, + ApiKey(ApiKey), + Basic, + Oauth, +} + +/// If the authorization type is unspecified then it will be automatically detected based +/// on the configuration. This functionality is useful when the OpenAPI definition does not +/// include an authorization scheme. +pub(crate) struct Request { + auth: Option, + method: hyper::Method, + path: String, + query_params: HashMap, + no_return_type: bool, + path_params: HashMap, + form_params: HashMap, + header_params: HashMap, + // TODO: multiple body params are possible technically, but not supported here. + serialized_body: Option, +} + +#[allow(dead_code)] +impl Request { + pub fn new(method: hyper::Method, path: String) -> Self { + Request { + auth: None, + method, + path, + query_params: HashMap::new(), + path_params: HashMap::new(), + form_params: HashMap::new(), + header_params: HashMap::new(), + serialized_body: None, + no_return_type: false, + } + } + + pub fn with_body_param(mut self, param: T) -> Self { + self.serialized_body = Some(serde_json::to_string(¶m).unwrap()); + self + } + + pub fn with_header_param(mut self, basename: String, param: String) -> Self { + self.header_params.insert(basename, param); + self + } + + #[allow(unused)] + pub fn with_query_param(mut self, basename: String, param: String) -> Self { + self.query_params.insert(basename, param); + self + } + + #[allow(unused)] + pub fn with_path_param(mut self, basename: String, param: String) -> Self { + self.path_params.insert(basename, param); + self + } + + #[allow(unused)] + pub fn with_form_param(mut self, basename: String, param: String) -> Self { + self.form_params.insert(basename, param); + self + } + + pub fn returns_nothing(mut self) -> Self { + self.no_return_type = true; + self + } + + pub fn with_auth(mut self, auth: Auth) -> Self { + self.auth = Some(auth); + self + } + + pub fn execute<'a, C, U>( + self, + conf: &configuration::Configuration, + ) -> Pin> + 'a + Send>> + where + C: Connect + Clone + std::marker::Send + Sync, + U: Sized + std::marker::Send + 'a, + for<'de> U: serde::Deserialize<'de>, + { + let mut query_string = ::url::form_urlencoded::Serializer::new("".to_owned()); + + let mut path = self.path; + for (k, v) in self.path_params { + // replace {id} with the value of the id path param + path = path.replace(&format!("{{{}}}", k), &v); + } + + for (key, val) in self.query_params { + query_string.append_pair(&key, &val); + } + + let mut uri_str = format!("{}{}", conf.base_path, path); + + let query_string_str = query_string.finish(); + if !query_string_str.is_empty() { + uri_str += "?"; + uri_str += &query_string_str; + } + let uri: hyper::Uri = match uri_str.parse() { + Err(e) => return Box::pin(futures::future::err(Error::UriError(e))), + Ok(u) => u, + }; + + let mut req_builder = hyper::Request::builder() + .uri(uri) + .method(self.method); + + // Detect the authorization type if it hasn't been set. + let auth = self.auth.unwrap_or_else(|| + if conf.api_key.is_some() { + panic!("Cannot automatically set the API key from the configuration, it must be specified in the OpenAPI definition") + } else if conf.oauth_access_token.is_some() { + Auth::Oauth + } else if conf.basic_auth.is_some() { + Auth::Basic + } else { + Auth::None + } + ); + match auth { + Auth::ApiKey(apikey) => { + if let Some(ref key) = conf.api_key { + let val = apikey.key(&key.prefix, &key.key); + if apikey.in_query { + query_string.append_pair(&apikey.param_name, &val); + } + if apikey.in_header { + req_builder = req_builder.header(&apikey.param_name, val); + } + } + } + Auth::Basic => { + if let Some(ref auth_conf) = conf.basic_auth { + let mut text = auth_conf.0.clone(); + text.push(':'); + if let Some(ref pass) = auth_conf.1 { + text.push_str(&pass[..]); + } + let encoded = base64::encode(&text); + req_builder = req_builder.header(AUTHORIZATION, encoded); + } + } + Auth::Oauth => { + if let Some(ref token) = conf.oauth_access_token { + let text = "Bearer ".to_owned() + token; + req_builder = req_builder.header(AUTHORIZATION, text); + } + } + Auth::None => {} + } + + if let Some(ref user_agent) = conf.user_agent { + req_builder = req_builder.header(USER_AGENT, match HeaderValue::from_str(user_agent) { + Ok(header_value) => header_value, + Err(e) => return Box::pin(futures::future::err(super::Error::Header(e))) + }); + } + + for (k, v) in self.header_params { + req_builder = req_builder.header(&k, v); + } + + let req_headers = req_builder.headers_mut().unwrap(); + let request_result = if self.form_params.len() > 0 { + req_headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/x-www-form-urlencoded")); + let mut enc = ::url::form_urlencoded::Serializer::new("".to_owned()); + for (k, v) in self.form_params { + enc.append_pair(&k, &v); + } + req_builder.body(enc.finish()) + } else if let Some(body) = self.serialized_body { + req_headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + req_headers.insert(CONTENT_LENGTH, body.len().into()); + req_builder.body(body) + } else { + req_builder.body(String::new()) + }; + let request = match request_result { + Ok(request) => request, + Err(e) => return Box::pin(futures::future::err(Error::from(e))) + }; + + let no_return_type = self.no_return_type; + Box::pin(conf.client + .request(request) + .map_err(|e| Error::from(e)) + .and_then(move |response| { + let status = response.status(); + if !status.is_success() { + futures::future::err::(Error::from((status, response.into_body()))).boxed() + } else if no_return_type { + // This is a hack; if there's no_ret_type, U is (), but serde_json gives an + // error when deserializing "" into (), so deserialize 'null' into it + // instead. + // An alternate option would be to require U: Default, and then return + // U::default() here instead since () implements that, but then we'd + // need to impl default for all models. + futures::future::ok::(serde_json::from_str("null").expect("serde null value")).boxed() + } else { + let collect = response.into_body().collect().map_err(Error::from); + collect.map(|collected| { + collected.and_then(|collected| { + serde_json::from_slice(&collected.to_bytes()).map_err(Error::from) + }) + }).boxed() + } + })) + } +} diff --git a/templates/reqwest-trait/api.mustache b/templates/reqwest-trait/api.mustache new file mode 100644 index 0000000..3b10fb3 --- /dev/null +++ b/templates/reqwest-trait/api.mustache @@ -0,0 +1,566 @@ +{{>partial_header}} + +use async_trait::async_trait; +{{#mockall}} +#[cfg(feature = "mockall")] +use mockall::automock; +{{/mockall}} +use reqwest; +use std::sync::Arc; +use serde::{Deserialize, Serialize, de::Error as _}; +use crate::{apis::ResponseContent, models}; +use super::{Error, configuration}; +use crate::apis::ContentType; + +{{#mockall}} +#[cfg_attr(feature = "mockall", automock)] +{{/mockall}} +#[async_trait] +pub trait {{{classname}}}: Send + Sync { +{{#operations}} +{{#operation}} + + /// {{{httpMethod}}} {{{path}}} + {{^notes.empty}} + /// + /// {{{notes}}} + {{/notes.empty}} +{{#vendorExtensions.x-group-parameters}} + async fn {{{operationId}}}(&self, {{#allParams}}{{#-first}} params: {{{operationIdCamelCase}}}Params {{/-first}}{{/allParams}}{{! + ### Function return type + }}) -> Result<{{! + ### Multi response support + }}{{#supportMultipleResponses}}ResponseContent<{{{operationIdCamelCase}}}Success>{{/supportMultipleResponses}}{{! + ### Regular return type + }}{{^supportMultipleResponses}}{{^returnType}}(){{/returnType}}{{{returnType}}}{{/supportMultipleResponses}}{{! + ### Error Type + }}, Error<{{{operationIdCamelCase}}}Error>>; +{{/vendorExtensions.x-group-parameters}} +{{^vendorExtensions.x-group-parameters}} + async fn {{{operationId}}}{{! + ### Lifetimes + }}<{{#allParams}}'{{#lambda.lifetimeName}}{{{paramName}}}{{/lambda.lifetimeName}}{{^-last}}, {{/-last}}{{/allParams}}>{{! + ### Function parameter names + }}(&self, {{#allParams}}{{{paramName}}}: {{! + ### Option Start + }}{{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{! + ### &str and Vec<&str> + }}{{#isString}}{{#isArray}}Vec<{{/isArray}}{{^isUuid}}&'{{#lambda.lifetimeName}}{{{paramName}}}{{/lambda.lifetimeName}} str{{/isUuid}}{{#isArray}}>{{/isArray}}{{/isString}}{{! + ### UUIDs + }}{{#isUuid}}{{#isArray}}Vec<{{/isArray}}&str{{#isArray}}>{{/isArray}}{{/isUuid}}{{! + ### Models and primative types + }}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{! + ### Option End + }}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{! + ### Comma for next arguement + }}{{^-last}}, {{/-last}}{{/allParams}}{{! + ### Function return type + }}) -> Result<{{! + ### Multi response support + }}{{#supportMultipleResponses}}ResponseContent<{{{operationIdCamelCase}}}Success>{{/supportMultipleResponses}}{{! + ### Regular return type + }}{{^supportMultipleResponses}}{{^returnType}}(){{/returnType}}{{{returnType}}}{{/supportMultipleResponses}}{{! + ### Error Type + }}, Error<{{{operationIdCamelCase}}}Error>>; +{{/vendorExtensions.x-group-parameters}} +{{/operation}} +{{/operations}} +} + +pub struct {{{classname}}}Client { + configuration: Arc +} + +impl {{classname}}Client { + pub fn new(configuration: Arc) -> Self { + Self { configuration } + } +} + + +{{#operations}} +{{#operation}} +{{#vendorExtensions.x-group-parameters}} +{{#allParams}} +{{#-first}} +/// struct for passing parameters to the method [`{{{classname}}}::{{operationId}}`] +#[derive(Clone, Debug)] +{{#useBonBuilder}} +#[cfg_attr(feature = "bon", derive(::bon::Builder))] +{{/useBonBuilder}} +pub struct {{{operationIdCamelCase}}}Params { +{{/-first}} + {{#description}} + /// {{{.}}} + {{/description}} + pub {{{paramName}}}: {{! + ### Option Start + }}{{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{! + ### &str and Vec<&str> + }}{{^isUuid}}{{#isString}}{{#isArray}}Vec<{{/isArray}}String{{#isArray}}>{{/isArray}}{{/isString}}{{/isUuid}}{{! + ### UUIDs + }}{{#isUuid}}{{#isArray}}Vec<{{/isArray}}String{{#isArray}}>{{/isArray}}{{/isUuid}}{{! + ### Models and primative types + }}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{! + ### Option End + }}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{! + ### Comma for next field + }}{{^-last}},{{/-last}} +{{#-last}} +} + +{{/-last}} +{{/allParams}} +{{/vendorExtensions.x-group-parameters}} +{{/operation}} +{{/operations}} + +#[async_trait] +impl {{classname}} for {{classname}}Client { + {{#operations}} + {{#operation}} + {{#description}} + /// {{{.}}} + {{/description}} + {{#notes}} + /// {{{.}}} + {{/notes}} + {{#vendorExtensions.x-group-parameters}} + async fn {{{operationId}}}(&self, {{#allParams}}{{#-first}} params: {{{operationIdCamelCase}}}Params {{/-first}}{{/allParams}}{{! + ### Function return type + }}) -> Result<{{! + ### Multi response support + }}{{#supportMultipleResponses}}ResponseContent<{{{operationIdCamelCase}}}Success>{{/supportMultipleResponses}}{{! + ### Regular return type + }}{{^supportMultipleResponses}}{{^returnType}}(){{/returnType}}{{{returnType}}}{{/supportMultipleResponses}}{{! + ### Error Type + }}, Error<{{{operationIdCamelCase}}}Error>> { + {{#allParams}}{{#-first}} + let {{{operationIdCamelCase}}}Params { + {{#allParams}} + {{{paramName}}}, + {{/allParams}} + } = params; + {{/-first}}{{/allParams}} + + {{/vendorExtensions.x-group-parameters}} + {{^vendorExtensions.x-group-parameters}} + async fn {{{operationId}}}{{! + ### Lifetimes + }}<{{#allParams}}'{{#lambda.lifetimeName}}{{{paramName}}}{{/lambda.lifetimeName}}{{^-last}}, {{/-last}}{{/allParams}}>{{! + ### Function parameter names + }}(&self, {{#allParams}}{{{paramName}}}: {{! + ### Option Start + }}{{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{! + ### &str and Vec<&str> + }}{{#isString}}{{#isArray}}Vec<{{/isArray}}{{^isUuid}}&'{{#lambda.lifetimeName}}{{{paramName}}}{{/lambda.lifetimeName}} str{{/isUuid}}{{#isArray}}>{{/isArray}}{{/isString}}{{! + ### UUIDs + }}{{#isUuid}}{{#isArray}}Vec<{{/isArray}}&str{{#isArray}}>{{/isArray}}{{/isUuid}}{{! + ### Models and primative types + }}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{! + ### Option End + }}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{! + ### Comma for next arguement + }}{{^-last}}, {{/-last}}{{/allParams}}{{! + ### Function return type + }}) -> Result<{{! + ### Multi response support + }}{{#supportMultipleResponses}}ResponseContent<{{{operationIdCamelCase}}}Success>{{/supportMultipleResponses}}{{! + ### Regular return type + }}{{^supportMultipleResponses}}{{^returnType}}(){{/returnType}}{{{returnType}}}{{/supportMultipleResponses}}{{! + ### Regular return type + }}, Error<{{{operationIdCamelCase}}}Error>> { + {{/vendorExtensions.x-group-parameters}} + let local_var_configuration = &self.configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}{{{path}}}", local_var_configuration.base_path{{#pathParams}}, {{{baseName}}}={{#isString}}crate::apis::urlencode({{/isString}}{{{paramName}}}{{^required}}.unwrap(){{/required}}{{#required}}{{#isNullable}}.unwrap(){{/isNullable}}{{/required}}{{#isArray}}.join(",").as_ref(){{/isArray}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}.to_string(){{/isContainer}}{{/isPrimitiveType}}{{/isUuid}}{{/isString}}{{#isString}}){{/isString}}{{/pathParams}}); + let mut local_var_req_builder = local_var_client.request(reqwest::Method::{{{httpMethod}}}, local_var_uri_str.as_str()); + + {{#queryParams}} + {{#required}} + {{#isArray}} + local_var_req_builder = match "{{collectionFormat}}" { + "multi" => local_var_req_builder.query(&{{{paramName}}}.into_iter().map(|p| ("{{{baseName}}}".to_owned(), p.to_string())).collect::>()), + _ => local_var_req_builder.query(&[("{{{baseName}}}", &{{{paramName}}}.into_iter().map(|p| p.to_string()).collect::>().join(",").to_string())]), + }; + {{/isArray}} + {{^isArray}} + {{^isNullable}} + local_var_req_builder = local_var_req_builder.query(&[("{{{baseName}}}", &{{{paramName}}}.to_string())]); + {{/isNullable}} + {{#isNullable}} + {{#isDeepObject}} + {{^isExplode}} + if let Some(ref param_value) = {{{paramName}}} { + let params = crate::apis::parse_deep_object("{{{baseName}}}", &serde_json::to_value(param_value)?); + local_var_req_builder = local_var_req_builder.query(¶ms); + } + {{/isExplode}} + {{#isExplode}} + {{#isModel}} + if let Some(ref param_value) = {{{paramName}}} { + local_var_req_builder = local_var_req_builder.query(¶m_value); + } + {{/isModel}} + {{#isMap}} + if let Some(ref param_value) = {{{paramName}}} { + let mut query_params = Vec::with_capacity(param_value.len()); + for (key, value) in param_value.iter() { + query_params.push((key.to_string(), serde_json::to_string(value)?)); + } + local_var_req_builder = local_var_req_builder.query(&query_params); + } + {{/isMap}} + {{/isExplode}} + {{/isDeepObject}} + {{^isDeepObject}} + {{#isObject}} + if let Some(ref param_value) = {{{paramName}}} { + local_var_req_builder = local_var_req_builder.query(&[("{{{baseName}}}", &serde_json::to_value(param_value)?)]); + }; + {{/isObject}} + {{#isModel}} + if let Some(ref param_value) = {{{paramName}}} { + local_var_req_builder = local_var_req_builder.query(&[("{{{baseName}}}", &serde_json::to_value(param_value)?)]); + }; + {{/isModel}} + {{^isObject}} + {{^isModel}} + if let Some(ref param_value) = {{{paramName}}} { + local_var_req_builder = local_var_req_builder.query(&[("{{{baseName}}}", ¶m_value.to_string())]); + }; + {{/isModel}} + {{/isObject}} + {{/isDeepObject}} + {{/isNullable}} + {{/isArray}} + {{/required}} + {{^required}} + if let Some(ref param_value) = {{{paramName}}} { + {{#isArray}} + local_var_req_builder = match "{{collectionFormat}}" { + "multi" => local_var_req_builder.query(¶m_value.into_iter().map(|p| ("{{{baseName}}}".to_owned(), p.to_string())).collect::>()), + _ => local_var_req_builder.query(&[("{{{baseName}}}", ¶m_value.into_iter().map(|p| p.to_string()).collect::>().join(",").to_string())]), + }; + {{/isArray}} + {{^isArray}} + {{#isDeepObject}} + {{^isExplode}} + let params = crate::apis::parse_deep_object("{{{baseName}}}", &serde_json::to_value(param_value)?); + local_var_req_builder = local_var_req_builder.query(¶ms); + {{/isExplode}} + {{#isExplode}} + {{#isModel}} + local_var_req_builder = local_var_req_builder.query(¶m_value); + {{/isModel}} + {{#isMap}} + let mut query_params = Vec::with_capacity(param_value.len()); + for (key, value) in param_value.iter() { + query_params.push((key.to_string(), serde_json::to_string(value)?)); + } + local_var_req_builder = local_var_req_builder.query(&query_params); + {{/isMap}} + {{/isExplode}} + {{/isDeepObject}} + {{^isDeepObject}} + {{#isObject}} + local_var_req_builder = local_var_req_builder.query(&[("{{{baseName}}}", &serde_json::to_value(param_value)?)]); + {{/isObject}} + {{#isModel}} + local_var_req_builder = local_var_req_builder.query(&[("{{{baseName}}}", &serde_json::to_value(param_value)?)]); + {{/isModel}} + {{^isObject}} + {{^isModel}} + local_var_req_builder = local_var_req_builder.query(&[("{{{baseName}}}", ¶m_value.to_string())]); + {{/isModel}} + {{/isObject}} + {{/isDeepObject}} + {{/isArray}} + } + {{/required}} + {{/queryParams}} + {{#hasAuthMethods}} + {{#authMethods}} + {{#isApiKey}} + {{#isKeyInQuery}} + if let Some(ref local_var_apikey) = local_var_configuration.api_key { + let local_var_key = local_var_apikey.key.clone(); + let local_var_value = match local_var_apikey.prefix { + Some(ref local_var_prefix) => format!("{} {}", local_var_prefix, local_var_key), + None => local_var_key, + }; + local_var_req_builder = local_var_req_builder.query(&[("{{{keyParamName}}}", local_var_value)]); + } + {{/isKeyInQuery}} + {{/isApiKey}} + {{/authMethods}} + {{/hasAuthMethods}} + {{#hasAuthMethods}} + {{#withAWSV4Signature}} + if let Some(ref local_var_aws_v4_key) = local_var_configuration.aws_v4_key { + let local_var_new_headers = match local_var_aws_v4_key.sign( + &local_var_uri_str, + "{{{httpMethod}}}", + {{#hasBodyParam}} + {{#bodyParams}} + &serde_json::to_string(&{{{paramName}}}).expect("param should serialize to string"), + {{/bodyParams}} + {{/hasBodyParam}} + {{^hasBodyParam}} + "", + {{/hasBodyParam}} + ) { + Ok(new_headers) => new_headers, + Err(err) => return Err(Error::AWSV4SignatureError(err)), + }; + for (local_var_name, local_var_value) in local_var_new_headers.iter() { + local_var_req_builder = local_var_req_builder.header(local_var_name.as_str(), local_var_value.as_str()); + } + } + {{/withAWSV4Signature}} + {{/hasAuthMethods}} + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + {{#hasHeaderParams}} + {{#headerParams}} + {{#required}} + {{^isNullable}} + local_var_req_builder = local_var_req_builder.header("{{{baseName}}}", {{{paramName}}}{{#isArray}}.join(","){{/isArray}}.to_string()); + {{/isNullable}} + {{#isNullable}} + match {{{paramName}}} { + Some(local_var_param_value) => { local_var_req_builder = local_var_req_builder.header("{{{baseName}}}", local_var_param_value{{#isArray}}.join(","){{/isArray}}.to_string()); }, + None => { local_var_req_builder = local_var_req_builder.header("{{{baseName}}}", ""); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(local_var_param_value) = {{{paramName}}} { + local_var_req_builder = local_var_req_builder.header("{{{baseName}}}", local_var_param_value{{#isArray}}.join(","){{/isArray}}.to_string()); + } + {{/required}} + {{/headerParams}} + {{/hasHeaderParams}} + {{#hasAuthMethods}} + {{#authMethods}} + {{#supportTokenSource}} + // Obtain a token from source provider. + // Tokens can be Id or access tokens depending on the provider type and configuration. + let token = local_var_configuration.token_source.token().await.map_err(Error::TokenSource)?; + // The token format is the responsibility of the provider, thus we just set the authorization header with whatever is given. + local_var_req_builder = local_var_req_builder.header(reqwest::header::AUTHORIZATION, token); + {{/supportTokenSource}} + {{^supportTokenSource}} + {{#isApiKey}} + {{#isKeyInHeader}} + if let Some(ref local_var_apikey) = local_var_configuration.api_key { + let local_var_key = local_var_apikey.key.clone(); + let local_var_value = match local_var_apikey.prefix { + Some(ref local_var_prefix) => format!("{} {}", local_var_prefix, local_var_key), + None => local_var_key, + }; + local_var_req_builder = local_var_req_builder.header("{{{keyParamName}}}", local_var_value); + }; + {{/isKeyInHeader}} + {{/isApiKey}} + {{#isBasic}} + {{#isBasicBasic}} + if let Some(ref local_var_auth_conf) = local_var_configuration.basic_auth { + local_var_req_builder = local_var_req_builder.basic_auth(local_var_auth_conf.0.to_owned(), local_var_auth_conf.1.to_owned()); + }; + {{/isBasicBasic}} + {{#isBasicBearer}} + if let Some(ref local_var_token) = local_var_configuration.bearer_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + {{/isBasicBearer}} + {{/isBasic}} + {{#isOAuth}} + if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + {{/isOAuth}} + {{/supportTokenSource}} + {{/authMethods}} + {{/hasAuthMethods}} + {{#isMultipart}} + {{#hasFormParams}} + let mut local_var_form = reqwest::multipart::Form::new(); + {{#formParams}} + {{#isFile}} + // TODO: support file upload for '{{{baseName}}}' parameter + {{/isFile}} + {{^isFile}} + {{#required}} + {{^isNullable}} + local_var_form = local_var_form.text("{{{baseName}}}", {{{paramName}}}{{#isArray}}.into_iter().map(|p| p.to_string()).collect::>().join(","){{/isArray}}.to_string()); + {{/isNullable}} + {{#isNullable}} + match {{{paramName}}} { + Some(local_var_param_value) => { local_var_form = local_var_form.text("{{{baseName}}}", local_var_param_value{{#isArray}}.into_iter().map(|p| p.to_string()).collect::>().join(","){{/isArray}}.to_string()); }, + None => { local_var_form = local_var_form.text("{{{baseName}}}", ""); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(local_var_param_value) = {{{paramName}}} { + local_var_form = local_var_form.text("{{{baseName}}}", local_var_param_value{{#isArray}}.into_iter().map(|p| p.to_string()).collect::>().join(","){{/isArray}}.to_string()); + } + {{/required}} + {{/isFile}} + {{/formParams}} + local_var_req_builder = local_var_req_builder.multipart(local_var_form); + {{/hasFormParams}} + {{/isMultipart}} + {{^isMultipart}} + {{#hasFormParams}} + let mut local_var_form_params = std::collections::HashMap::new(); + {{#formParams}} + {{#isFile}} + {{#required}} + {{^isNullable}} + local_var_form_params.insert("{{{baseName}}}", unimplemented!("File form param not supported with x-www-form-urlencoded content")); + {{/isNullable}} + {{#isNullable}} + match {{{paramName}}} { + Some(local_var_param_value) => { local_var_form_params.insert("{{{baseName}}}", unimplemented!("File form param not supported with x-www-form-urlencoded content")); }, + None => { unimplemented!("Required nullable file form param not supported with x-www-form-urlencoded content"); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(local_var_param_value) = {{{paramName}}} { + local_var_form_params.insert("{{{baseName}}}", unimplemented!("File form param not supported with x-www-form-urlencoded content")); + } + {{/required}} + {{/isFile}} + {{^isFile}} + {{#required}} + {{^isNullable}} + local_var_form_params.insert("{{{baseName}}}", {{{paramName}}}{{#isArray}}.into_iter().map(|p| p.to_string()).collect::>().join(","){{/isArray}}.to_string()); + {{/isNullable}} + {{#isNullable}} + match {{{paramName}}} { + Some(local_var_param_value) => { local_var_form_params.insert("{{{baseName}}}", local_var_param_value{{#isArray}}.into_iter().map(|p| p.to_string()).collect::>().join(","){{/isArray}}.to_string()); }, + None => { local_var_form_params.insert("{{{baseName}}}", ""); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(local_var_param_value) = {{{paramName}}} { + local_var_form_params.insert("{{{baseName}}}", local_var_param_value{{#isArray}}.into_iter().map(|p| p.to_string()).collect::>().join(","){{/isArray}}.to_string()); + } + {{/required}} + {{/isFile}} + {{/formParams}} + local_var_req_builder = local_var_req_builder.form(&local_var_form_params); + {{/hasFormParams}} + {{/isMultipart}} + {{#hasBodyParam}} + {{#bodyParams}} + local_var_req_builder = local_var_req_builder.json(&{{{paramName}}}); + {{/bodyParams}} + {{/hasBodyParam}} + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + {{^supportMultipleResponses}} + {{#returnType}} + let local_var_content_type = local_var_resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let local_var_content_type = super::ContentType::from(local_var_content_type); + {{/returnType}} + {{/supportMultipleResponses}} + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + {{^supportMultipleResponses}} + {{^returnType}} + Ok(()) + {{/returnType}} + {{#returnType}} + match local_var_content_type { + ContentType::Json => serde_json::from_str(&local_var_content).map_err(Error::from), + {{#vendorExtensions.x-supports-plain-text}} + ContentType::Text => Ok(local_var_content), + {{/vendorExtensions.x-supports-plain-text}} + {{^vendorExtensions.x-supports-plain-text}} + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `{{returnType}}`", + ))), + {{/vendorExtensions.x-supports-plain-text}} + ContentType::Unsupported(local_var_unknown_type) => Err(Error::from( + serde_json::Error::custom(format!( + "Received `{local_var_unknown_type}` content type response that cannot be converted to `{{returnType}}`" + )), + )), + } + {{/returnType}} + {{/supportMultipleResponses}} + {{#supportMultipleResponses}} + let local_var_entity: Option<{{{operationIdCamelCase}}}Success> = serde_json::from_str(&local_var_content).ok(); + let local_var_result = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity }; + Ok(local_var_result) + {{/supportMultipleResponses}} + } else { + let local_var_entity: Option<{{{operationIdCamelCase}}}Error> = serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity }; + Err(Error::ResponseError(local_var_error)) + } + } + + {{/operation}} + {{/operations}} +} + +{{#supportMultipleResponses}} +{{#operations}} +{{#operation}} +/// struct for typed successes of method [`{{{classname}}}::{{operationId}}`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum {{{operationIdCamelCase}}}Success { + {{#responses}} + {{#is2xx}} + Status{{code}}({{#isEnum}}{{{enumName}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}), + {{/is2xx}} + {{#is3xx}} + Status{{code}}({{#isEnum}}{{{enumName}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}), + {{/is3xx}} + {{/responses}} + UnknownValue(serde_json::Value), +} + +{{/operation}} +{{/operations}} +{{/supportMultipleResponses}} +{{#operations}} +{{#operation}} +/// struct for typed errors of method [`{{{classname}}}::{{operationId}}`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum {{{operationIdCamelCase}}}Error { + {{#responses}} + {{#is4xx}} + Status{{code}}({{#isEnum}}{{{enumName}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}), + {{/is4xx}} + {{#is5xx}} + Status{{code}}({{#isEnum}}{{{enumName}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}), + {{/is5xx}} + {{#isDefault}} + DefaultResponse({{#isEnum}}{{{enumName}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}), + {{/isDefault}} + {{/responses}} + UnknownValue(serde_json::Value), +} + +{{/operation}} +{{/operations}} diff --git a/templates/reqwest-trait/api_mod.mustache b/templates/reqwest-trait/api_mod.mustache new file mode 100644 index 0000000..b856934 --- /dev/null +++ b/templates/reqwest-trait/api_mod.mustache @@ -0,0 +1,236 @@ +use std::error; +use std::fmt; +{{#withAWSV4Signature}} +use aws_sigv4; +{{/withAWSV4Signature}} + +#[derive(Debug, Clone)] +pub struct ResponseContent { + pub status: reqwest::StatusCode, + pub content: String, + pub entity: Option, +} + +#[derive(Debug)] +pub enum Error { + Reqwest(reqwest::Error), + {{#supportMiddleware}} + ReqwestMiddleware(reqwest_middleware::Error), + {{/supportMiddleware}} + Serde(serde_json::Error), + Io(std::io::Error), + ResponseError(ResponseContent), + {{#withAWSV4Signature}} + AWSV4SignatureError(aws_sigv4::http_request::Error), + {{/withAWSV4Signature}} + {{#supportTokenSource}} + TokenSource(Box), + {{/supportTokenSource}} +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (module, e) = match self { + Error::Reqwest(e) => ("reqwest", e.to_string()), + {{#supportMiddleware}} + Error::ReqwestMiddleware(e) => ("reqwest-middleware", e.to_string()), + {{/supportMiddleware}} + Error::Serde(e) => ("serde", e.to_string()), + Error::Io(e) => ("IO", e.to_string()), + Error::ResponseError(e) => ("response", format!("status code {}", e.status)), + {{#withAWSV4Signature}} + Error::AWSV4SignatureError(e) => ("aws v4 signature", e.to_string()), + {{/withAWSV4Signature}} + {{#supportTokenSource}} + Error::TokenSource(e) => ("token source failure", e.to_string()), + {{/supportTokenSource}} + }; + write!(f, "error in {}: {}", module, e) + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + Some(match self { + Error::Reqwest(e) => e, + {{#supportMiddleware}} + Error::ReqwestMiddleware(e) => e, + {{/supportMiddleware}} + Error::Serde(e) => e, + Error::Io(e) => e, + Error::ResponseError(_) => return None, + {{#withAWSV4Signature}} + Error::AWSV4SignatureError(_) => return None, + {{/withAWSV4Signature}} + {{#supportTokenSource}} + Error::TokenSource(e) => &**e, + {{/supportTokenSource}} + }) + } +} + +impl From for Error { + fn from(e: reqwest::Error) -> Self { + Error::Reqwest(e) + } +} + +{{#supportMiddleware}} +impl From for Error { + fn from(e: reqwest_middleware::Error) -> Self { + Error::ReqwestMiddleware(e) + } +} + +{{/supportMiddleware}} +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Serde(e) + } +} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + Error::Io(e) + } +} + +pub fn urlencode>(s: T) -> String { + ::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect() +} + +pub fn parse_deep_object(prefix: &str, value: &serde_json::Value) -> Vec<(String, String)> { + if let serde_json::Value::Object(object) = value { + let mut params = vec![]; + + for (key, value) in object { + match value { + serde_json::Value::Object(_) => params.append(&mut parse_deep_object( + &format!("{}[{}]", prefix, key), + value, + )), + serde_json::Value::Array(array) => { + for (i, value) in array.iter().enumerate() { + params.append(&mut parse_deep_object( + &format!("{}[{}][{}]", prefix, key, i), + value, + )); + } + }, + serde_json::Value::String(s) => params.push((format!("{}[{}]", prefix, key), s.clone())), + _ => params.push((format!("{}[{}]", prefix, key), value.to_string())), + } + } + + return params; + } + + unimplemented!("Only objects are supported with style=deepObject") +} + +/// Internal use only +/// A content type supported by this client. +#[allow(dead_code)] +enum ContentType { + Json, + Text, + Unsupported(String) +} + +impl From<&str> for ContentType { + fn from(content_type: &str) -> Self { + if content_type.starts_with("application") && content_type.contains("json") { + Self::Json + } else if content_type.starts_with("text/plain") { + Self::Text + } else { + Self::Unsupported(content_type.to_string()) + } + } +} + +{{#apiInfo}} +{{#apis}} +pub mod {{{classFilename}}}; +{{/apis}} +{{/apiInfo}} + +pub mod configuration; + +{{#topLevelApiClient}} +use std::sync::Arc; + +pub trait Api { + {{#apiInfo}} + {{#apis}} + fn {{{classFilename}}}(&self) -> &dyn {{{classFilename}}}::{{classname}}; + {{/apis}} + {{/apiInfo}} +} + +pub struct ApiClient { + {{#apiInfo}} + {{#apis}} + {{{classFilename}}}: Box, + {{/apis}} + {{/apiInfo}} +} + +impl ApiClient { + pub fn new(configuration: Arc) -> Self { + Self { + {{#apiInfo}} + {{#apis}} + {{{classFilename}}}: Box::new({{{classFilename}}}::{{classname}}Client::new(configuration.clone())), + {{/apis}} + {{/apiInfo}} + } + } +} + +impl Api for ApiClient { + {{#apiInfo}} + {{#apis}} + fn {{{classFilename}}}(&self) -> &dyn {{{classFilename}}}::{{classname}} { + self.{{{classFilename}}}.as_ref() + } + {{/apis}} + {{/apiInfo}} +} + +{{#mockall}} +#[cfg(feature = "mockall")] +pub struct MockApiClient { + {{#apiInfo}} + {{#apis}} + pub {{{classFilename}}}_mock: {{{classFilename}}}::Mock{{classname}}, + {{/apis}} + {{/apiInfo}} +} + +#[cfg(feature = "mockall")] +impl MockApiClient { + pub fn new() -> Self { + Self { + {{#apiInfo}} + {{#apis}} + {{{classFilename}}}_mock: {{{classFilename}}}::Mock{{classname}}::new(), + {{/apis}} + {{/apiInfo}} + } + } +} + +#[cfg(feature = "mockall")] +impl Api for MockApiClient { + {{#apiInfo}} + {{#apis}} + fn {{{classFilename}}}(&self) -> &dyn {{{classFilename}}}::{{classname}} { + &self.{{{classFilename}}}_mock + } + {{/apis}} + {{/apiInfo}} +} +{{/mockall}} + +{{/topLevelApiClient}} diff --git a/templates/reqwest-trait/configuration.mustache b/templates/reqwest-trait/configuration.mustache new file mode 100644 index 0000000..2536069 --- /dev/null +++ b/templates/reqwest-trait/configuration.mustache @@ -0,0 +1,120 @@ +{{>partial_header}} + +{{#withAWSV4Signature}} +use std::time::SystemTime; +use aws_sigv4::http_request::{sign, SigningSettings, SigningParams, SignableRequest}; +use http; +use secrecy::{SecretString, ExposeSecret}; +{{/withAWSV4Signature}} +{{#supportTokenSource}} +use std::sync::Arc; +use google_cloud_token::TokenSource; +use async_trait::async_trait; +{{/supportTokenSource}} + +#[derive(Debug, Clone)] +pub struct Configuration { + pub base_path: String, + pub user_agent: Option, + pub client: {{#supportMiddleware}}reqwest_middleware::ClientWithMiddleware{{/supportMiddleware}}{{^supportMiddleware}}reqwest::Client{{/supportMiddleware}}, + {{^supportTokenSource}} + pub basic_auth: Option, + pub oauth_access_token: Option, + pub bearer_access_token: Option, + pub api_key: Option, + {{/supportTokenSource}} + {{#withAWSV4Signature}} + pub aws_v4_key: Option, + {{/withAWSV4Signature}} + {{#supportTokenSource}} + pub token_source: Arc, + {{/supportTokenSource}} +} +{{^supportTokenSource}} + +pub type BasicAuth = (String, Option); + +#[derive(Debug, Clone)] +pub struct ApiKey { + pub prefix: Option, + pub key: String, +} +{{/supportTokenSource}} + +{{#withAWSV4Signature}} +#[derive(Debug, Clone)] +pub struct AWSv4Key { + pub access_key: String, + pub secret_key: SecretString, + pub region: String, + pub service: String, +} + +impl AWSv4Key { + pub fn sign(&self, uri: &str, method: &str, body: &str) -> Result, aws_sigv4::http_request::Error> { + let request = http::Request::builder() + .uri(uri) + .method(method) + .body(body).unwrap(); + let signing_settings = SigningSettings::default(); + let signing_params = SigningParams::builder() + .access_key(self.access_key.as_str()) + .secret_key(self.secret_key.expose_secret().as_str()) + .region(self.region.as_str()) + .service_name(self.service.as_str()) + .time(SystemTime::now()) + .settings(signing_settings) + .build() + .unwrap(); + let signable_request = SignableRequest::from(&request); + let (mut signing_instructions, _signature) = sign(signable_request, &signing_params)?.into_parts(); + let mut additional_headers = Vec::<(String, String)>::new(); + if let Some(new_headers) = signing_instructions.take_headers() { + for (name, value) in new_headers.into_iter() { + additional_headers.push((name.expect("header should have name").to_string(), + value.to_str().expect("header value should be a string").to_string())); + } + } + Ok(additional_headers) + } +} +{{/withAWSV4Signature}} + +impl Configuration { + pub fn new() -> Configuration { + Configuration::default() + } +} + +impl Default for Configuration { + fn default() -> Self { + Configuration { + base_path: "{{{basePath}}}".to_owned(), + user_agent: {{#httpUserAgent}}Some("{{{.}}}".to_owned()){{/httpUserAgent}}{{^httpUserAgent}}Some("OpenAPI-Generator/{{{version}}}/rust".to_owned()){{/httpUserAgent}}, + client: {{#supportMiddleware}}reqwest_middleware::ClientBuilder::new(reqwest::Client::new()).build(){{/supportMiddleware}}{{^supportMiddleware}}reqwest::Client::new(){{/supportMiddleware}}, + {{^supportTokenSource}} + basic_auth: None, + oauth_access_token: None, + bearer_access_token: None, + api_key: None, + {{/supportTokenSource}} + {{#withAWSV4Signature}} + aws_v4_key: None, + {{/withAWSV4Signature}} + {{#supportTokenSource}} + token_source: Arc::new(NoopTokenSource{}), + {{/supportTokenSource}} + } + } +} +{{#supportTokenSource}} +#[derive(Debug)] +struct NoopTokenSource{} + +#[async_trait] +impl TokenSource for NoopTokenSource { + async fn token(&self) -> Result> { + panic!("This is dummy token source. You can use TokenSourceProvider from 'google_cloud_auth' crate, or any other compatible crate.") + } +} +{{/supportTokenSource}} diff --git a/templates/reqwest/api.mustache b/templates/reqwest/api.mustache new file mode 100644 index 0000000..e59d9bd --- /dev/null +++ b/templates/reqwest/api.mustache @@ -0,0 +1,545 @@ +{{>partial_header}} + +use reqwest; +use serde::{Deserialize, Serialize, de::Error as _}; +use crate::{apis::ResponseContent, models}; +use super::{Error, configuration, ContentType}; +{{#operations}} +{{#operation}} +{{#vendorExtensions.useAsyncFileStream}} +use tokio::fs::File as TokioFile; +use tokio_util::codec::{BytesCodec, FramedRead}; +{{/vendorExtensions.useAsyncFileStream}} +{{/operation}} +{{/operations}} + +{{#operations}} +{{#operation}} +{{#vendorExtensions.x-group-parameters}} +{{#allParams}} +{{#-first}} +/// struct for passing parameters to the method [`{{operationId}}`] +#[derive(Clone, Debug)] +pub struct {{{operationIdCamelCase}}}Params { +{{/-first}} + {{#description}} + /// {{{.}}} + {{/description}} + pub {{{paramName}}}: {{! + ### Option Start + }}{{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{! + ### &str and Vec<&str> + }}{{^isUuid}}{{#isString}}{{#isArray}}Vec<{{/isArray}}String{{#isArray}}>{{/isArray}}{{/isString}}{{/isUuid}}{{! + ### UUIDs + }}{{#isUuid}}{{#isArray}}Vec<{{/isArray}}String{{#isArray}}>{{/isArray}}{{/isUuid}}{{! + ### Models and primative types + }}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{! + ### Option End + }}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{! + ### Comma for next arguement + }}{{^-last}},{{/-last}} +{{#-last}} +} + +{{/-last}} +{{/allParams}} +{{/vendorExtensions.x-group-parameters}} +{{/operation}} +{{/operations}} + +{{#supportMultipleResponses}} +{{#operations}} +{{#operation}} +/// struct for typed successes of method [`{{operationId}}`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum {{{operationIdCamelCase}}}Success { + {{#responses}} + {{#is2xx}} + Status{{code}}({{#isEnum}}{{{enumName}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}), + {{/is2xx}} + {{#is3xx}} + Status{{code}}({{#isEnum}}{{{enumName}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}), + {{/is3xx}} + {{/responses}} + UnknownValue(serde_json::Value), +} + +{{/operation}} +{{/operations}} +{{/supportMultipleResponses}} +{{#operations}} +{{#operation}} +/// struct for typed errors of method [`{{operationId}}`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum {{{operationIdCamelCase}}}Error { + {{#responses}} + {{#is4xx}} + Status{{code}}({{#isEnum}}{{{enumName}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}), + {{/is4xx}} + {{#is5xx}} + Status{{code}}({{#isEnum}}{{{enumName}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}), + {{/is5xx}} + {{#isDefault}} + DefaultResponse({{#isEnum}}{{{enumName}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}), + {{/isDefault}} + {{/responses}} + UnknownValue(serde_json::Value), +} + +{{/operation}} +{{/operations}} + +{{#operations}} +{{#operation}} +{{#description}} +/// {{{.}}} +{{/description}} +{{#notes}} +/// {{{.}}} +{{/notes}} +{{#vendorExtensions.x-group-parameters}} +pub {{#supportAsync}}async {{/supportAsync}}fn {{{operationId}}}(configuration: &configuration::Configuration{{#allParams}}{{#-first}}, {{! +### Params +}}params: {{{operationIdCamelCase}}}Params{{/-first}}{{/allParams}}{{! +### Function return type +}}) -> Result<{{! +### Response File Support +}}{{#isResponseFile}}{{#supportAsync}}reqwest::Response{{/supportAsync}}{{^supportAsync}}reqwest::blocking::Response{{/supportAsync}}{{/isResponseFile}}{{! +### Regular Responses +}}{{^isResponseFile}}{{! +### Multi response support +}}{{#supportMultipleResponses}}ResponseContent<{{{operationIdCamelCase}}}Success>{{/supportMultipleResponses}}{{! +### Regular return type +}}{{^supportMultipleResponses}}{{^returnType}}(){{/returnType}}{{{returnType}}}{{/supportMultipleResponses}}{{/isResponseFile}}{{! +### Error Type +}}, Error<{{{operationIdCamelCase}}}Error>> { +{{/vendorExtensions.x-group-parameters}} +{{^vendorExtensions.x-group-parameters}} +pub {{#supportAsync}}async {{/supportAsync}}fn {{{operationId}}}(configuration: &configuration::Configuration, {{#allParams}}{{{paramName}}}: {{! +### Option Start +}}{{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{! +### &str and Vec<&str> +}}{{#isString}}{{#isArray}}Vec<{{/isArray}}{{^isUuid}}&str{{/isUuid}}{{#isArray}}>{{/isArray}}{{/isString}}{{! +### UUIDs +}}{{#isUuid}}{{#isArray}}Vec<{{/isArray}}&str{{#isArray}}>{{/isArray}}{{/isUuid}}{{! +### Models and primative types +}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{! +### Option End +}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{! +### Comma for next arguement +}}{{^-last}}, {{/-last}}{{/allParams}}{{! +### Function return type +}}) -> Result<{{! +### Response File Support +}}{{#isResponseFile}}{{#supportAsync}}reqwest::Response{{/supportAsync}}{{^supportAsync}}reqwest::blocking::Response{{/supportAsync}}{{/isResponseFile}}{{! +### Regular Responses +}}{{^isResponseFile}}{{! +### Multi response support +}}{{#supportMultipleResponses}}ResponseContent<{{{operationIdCamelCase}}}Success>{{/supportMultipleResponses}}{{! +### Regular return type +}}{{^supportMultipleResponses}}{{^returnType}}(){{/returnType}}{{{returnType}}}{{/supportMultipleResponses}}{{/isResponseFile}}{{! +### Error Type +}}, Error<{{{operationIdCamelCase}}}Error>> { + {{#allParams.0}} + // add a prefix to parameters to efficiently prevent name collisions + {{/allParams.0}} + {{#allParams}} + let {{{vendorExtensions.x-rust-param-identifier}}} = {{{paramName}}}; + {{/allParams}} +{{/vendorExtensions.x-group-parameters}} + + let uri_str = format!("{}{{{path}}}", configuration.base_path{{#pathParams}}, {{{baseName}}}={{#isString}}crate::apis::urlencode({{/isString}}{{{vendorExtensions.x-rust-param-identifier}}}{{^required}}.unwrap(){{/required}}{{#required}}{{#isNullable}}.unwrap(){{/isNullable}}{{/required}}{{#isArray}}.join(",").as_ref(){{/isArray}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}.to_string(){{/isContainer}}{{/isPrimitiveType}}{{/isUuid}}{{/isString}}{{#isString}}){{/isString}}{{/pathParams}}); + let mut req_builder = configuration.client.request(reqwest::Method::{{{httpMethod}}}, &uri_str); + + {{#queryParams}} + {{#required}} + {{#isArray}} + req_builder = match "{{collectionFormat}}" { + "multi" => req_builder.query(&{{{vendorExtensions.x-rust-param-identifier}}}.into_iter().map(|p| ("{{{baseName}}}".to_owned(), p.to_string())).collect::>()), + _ => req_builder.query(&[("{{{baseName}}}", &{{{vendorExtensions.x-rust-param-identifier}}}.into_iter().map(|p| p.to_string()).collect::>().join(",").to_string())]), + }; + {{/isArray}} + {{^isArray}} + {{^isNullable}} + req_builder = req_builder.query(&[("{{{baseName}}}", &{{{vendorExtensions.x-rust-param-identifier}}}.to_string())]); + {{/isNullable}} + {{#isNullable}} + {{#isDeepObject}} + if let Some(ref param_value) = {{{vendorExtensions.x-rust-param-identifier}}} { + {{^isExplode}} + let params = crate::apis::parse_deep_object("{{{baseName}}}", &serde_json::to_value(param_value)?); + req_builder = req_builder.query(¶ms); + {{/isExplode}} + {{#isExplode}} + {{#isModel}} + req_builder = req_builder.query(¶m_value); + {{/isModel}} + {{#isMap}} + let mut query_params = Vec::with_capacity(param_value.len()); + for (key, value) in param_value.iter() { + query_params.push((key.to_string(), serde_json::to_string(value)?)); + } + req_builder = req_builder.query(&query_params); + {{/isMap}} + {{/isExplode}} + }; + {{/isDeepObject}} + {{^isDeepObject}} + {{#isObject}} + if let Some(ref param_value) = {{{vendorExtensions.x-rust-param-identifier}}} { + req_builder = req_builder.query(&[("{{{baseName}}}", &serde_json::to_string(param_value)?)]); + }; + {{/isObject}} + {{#isModel}} + if let Some(ref param_value) = {{{vendorExtensions.x-rust-param-identifier}}} { + req_builder = req_builder.query(&[("{{{baseName}}}", &serde_json::to_string(param_value)?)]); + }; + {{/isModel}} + {{/isDeepObject}} + {{^isObject}} + {{^isModel}} + if let Some(ref param_value) = {{{vendorExtensions.x-rust-param-identifier}}} { + req_builder = req_builder.query(&[("{{{baseName}}}", ¶m_value.to_string())]); + }; + {{/isModel}} + {{/isObject}} + {{/isNullable}} + {{/isArray}} + {{/required}} + {{^required}} + if let Some(ref param_value) = {{{vendorExtensions.x-rust-param-identifier}}} { + {{#isArray}} + req_builder = match "{{collectionFormat}}" { + "multi" => req_builder.query(¶m_value.into_iter().map(|p| ("{{{baseName}}}".to_owned(), p.to_string())).collect::>()), + _ => req_builder.query(&[("{{{baseName}}}", ¶m_value.into_iter().map(|p| p.to_string()).collect::>().join(",").to_string())]), + }; + {{/isArray}} + {{^isArray}} + {{#isDeepObject}} + {{^isExplode}} + let params = crate::apis::parse_deep_object("{{{baseName}}}", &serde_json::to_value(param_value)?); + req_builder = req_builder.query(¶ms); + {{/isExplode}} + {{#isExplode}} + {{#isModel}} + req_builder = req_builder.query(¶m_value); + {{/isModel}} + {{#isMap}} + let mut query_params = Vec::with_capacity(param_value.len()); + for (key, value) in param_value.iter() { + query_params.push((key.to_string(), serde_json::to_string(value)?)); + } + req_builder = req_builder.query(&query_params); + {{/isMap}} + {{/isExplode}} + {{/isDeepObject}} + {{^isDeepObject}} + {{#isObject}} + req_builder = req_builder.query(&[("{{{baseName}}}", &serde_json::to_string(param_value)?)]); + {{/isObject}} + {{#isModel}} + req_builder = req_builder.query(&[("{{{baseName}}}", &serde_json::to_string(param_value)?)]); + {{/isModel}} + {{^isObject}} + {{^isModel}} + req_builder = req_builder.query(&[("{{{baseName}}}", ¶m_value.to_string())]); + {{/isModel}} + {{/isObject}} + {{/isDeepObject}} + {{/isArray}} + } + {{/required}} + {{/queryParams}} + {{#hasAuthMethods}} + {{#authMethods}} + {{#isApiKey}} + {{#isKeyInQuery}} + if let Some(ref apikey) = configuration.api_key { + let key = apikey.key.clone(); + let value = match apikey.prefix { + Some(ref prefix) => format!("{} {}", prefix, key), + None => key, + }; + req_builder = req_builder.query(&[("{{{keyParamName}}}", value)]); + } + {{/isKeyInQuery}} + {{/isApiKey}} + {{/authMethods}} + {{/hasAuthMethods}} + {{#hasAuthMethods}} + {{#withAWSV4Signature}} + if let Some(ref aws_v4_key) = configuration.aws_v4_key { + let new_headers = match aws_v4_key.sign( + &uri_str, + "{{{httpMethod}}}", + {{#hasBodyParam}} + {{#bodyParams}} + &serde_json::to_string(&{{{vendorExtensions.x-rust-param-identifier}}}).expect("param should serialize to string"), + {{/bodyParams}} + {{/hasBodyParam}} + {{^hasBodyParam}} + "", + {{/hasBodyParam}} + ) { + Ok(new_headers) => new_headers, + Err(err) => return Err(Error::AWSV4SignatureError(err)), + }; + for (name, value) in new_headers.iter() { + req_builder = req_builder.header(name.as_str(), value.as_str()); + } + } + {{/withAWSV4Signature}} + {{/hasAuthMethods}} + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + {{#hasHeaderParams}} + {{#headerParams}} + {{#required}} + {{^isNullable}} + req_builder = req_builder.header("{{{baseName}}}", {{{vendorExtensions.x-rust-param-identifier}}}{{#isArray}}.join(","){{/isArray}}.to_string()); + {{/isNullable}} + {{#isNullable}} + match {{{vendorExtensions.x-rust-param-identifier}}} { + Some(param_value) => { req_builder = req_builder.header("{{{baseName}}}", param_value{{#isArray}}.join(","){{/isArray}}.to_string()); }, + None => { req_builder = req_builder.header("{{{baseName}}}", ""); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(param_value) = {{{vendorExtensions.x-rust-param-identifier}}} { + req_builder = req_builder.header("{{{baseName}}}", param_value{{#isArray}}.join(","){{/isArray}}.to_string()); + } + {{/required}} + {{/headerParams}} + {{/hasHeaderParams}} + {{#hasAuthMethods}} + {{#authMethods}} + {{#supportTokenSource}} + // Obtain a token from source provider. + // Tokens can be Id or access tokens depending on the provider type and configuration. + let token = configuration.token_source.token().await.map_err(Error::TokenSource)?; + // The token format is the responsibility of the provider, thus we just set the authorization header with whatever is given. + req_builder = req_builder.header(reqwest::header::AUTHORIZATION, token); + {{/supportTokenSource}} + {{^supportTokenSource}} + {{#isApiKey}} + {{#isKeyInHeader}} + if let Some(ref apikey) = configuration.api_key { + let key = apikey.key.clone(); + let value = match apikey.prefix { + Some(ref prefix) => format!("{} {}", prefix, key), + None => key, + }; + req_builder = req_builder.header("{{{keyParamName}}}", value); + }; + {{/isKeyInHeader}} + {{/isApiKey}} + {{#isBasic}} + {{#isBasicBasic}} + if let Some(ref auth_conf) = configuration.basic_auth { + req_builder = req_builder.basic_auth(auth_conf.0.to_owned(), auth_conf.1.to_owned()); + }; + {{/isBasicBasic}} + {{#isBasicBearer}} + if let Some(ref token) = configuration.bearer_access_token { + req_builder = req_builder.bearer_auth(token.to_owned()); + }; + {{/isBasicBearer}} + {{/isBasic}} + {{#isOAuth}} + if let Some(ref token) = configuration.oauth_access_token { + req_builder = req_builder.bearer_auth(token.to_owned()); + }; + {{/isOAuth}} + {{/supportTokenSource}} + {{/authMethods}} + {{/hasAuthMethods}} + {{#isMultipart}} + {{#hasFormParams}} + let mut multipart_form = reqwest{{^supportAsync}}::blocking{{/supportAsync}}::multipart::Form::new(); + {{#formParams}} + {{#isFile}} + {{^supportAsync}} + {{#required}} + {{^isNullable}} + multipart_form = multipart_form.file("{{{baseName}}}", {{{vendorExtensions.x-rust-param-identifier}}})?; + {{/isNullable}} + {{#isNullable}} + match {{{vendorExtensions.x-rust-param-identifier}}} { + Some(param_value) => { multipart_form = multipart_form.file("{{{baseName}}}", param_value)?; }, + None => { unimplemented!("Required nullable form file param not supported"); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(param_value) = {{{vendorExtensions.x-rust-param-identifier}}} { + multipart_form = multipart_form.file("{{{baseName}}}", param_value)?; + } + {{/required}} + {{/supportAsync}} + {{#supportAsync}} + // TODO: support file upload for '{{{baseName}}}' parameter + {{/supportAsync}} + {{/isFile}} + {{^isFile}} + {{#required}} + {{^isNullable}} + multipart_form = multipart_form.text("{{{baseName}}}", {{{vendorExtensions.x-rust-param-identifier}}}{{#isArray}}.into_iter().map(|p| p.to_string()).collect::>().join(","){{/isArray}}.to_string()); + {{/isNullable}} + {{#isNullable}} + match {{{vendorExtensions.x-rust-param-identifier}}} { + Some(param_value) => { multipart_form = multipart_form.text("{{{baseName}}}", param_value{{#isArray}}.into_iter().map(|p| p.to_string()).collect::>().join(","){{/isArray}}.to_string()); }, + None => { multipart_form = multipart_form.text("{{{baseName}}}", ""); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(param_value) = {{{vendorExtensions.x-rust-param-identifier}}} { + multipart_form = multipart_form.text("{{{baseName}}}", param_value{{#isArray}}.into_iter().map(|p| p.to_string()).collect::>().join(","){{/isArray}}.to_string()); + } + {{/required}} + {{/isFile}} + {{/formParams}} + req_builder = req_builder.multipart(multipart_form); + {{/hasFormParams}} + {{/isMultipart}} + {{^isMultipart}} + {{#hasFormParams}} + let mut multipart_form_params = std::collections::HashMap::new(); + {{#formParams}} + {{#isFile}} + {{#required}} + {{^isNullable}} + multipart_form_params.insert("{{{baseName}}}", unimplemented!("File form param not supported with x-www-form-urlencoded content")); + {{/isNullable}} + {{#isNullable}} + match {{{vendorExtensions.x-rust-param-identifier}}} { + Some(param_value) => { multipart_form_params.insert("{{{baseName}}}", unimplemented!("File form param not supported with x-www-form-urlencoded content")); }, + None => { unimplemented!("Required nullable file form param not supported with x-www-form-urlencoded content"); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(param_value) = {{{vendorExtensions.x-rust-param-identifier}}} { + multipart_form_params.insert("{{{baseName}}}", unimplemented!("File form param not supported with x-www-form-urlencoded content")); + } + {{/required}} + {{/isFile}} + {{^isFile}} + {{#required}} + {{^isNullable}} + multipart_form_params.insert("{{{baseName}}}", {{{vendorExtensions.x-rust-param-identifier}}}{{#isArray}}.into_iter().map(|p| p.to_string()).collect::>().join(","){{/isArray}}.to_string()); + {{/isNullable}} + {{#isNullable}} + match {{{vendorExtensions.x-rust-param-identifier}}} { + Some(param_value) => { multipart_form_params.insert("{{{baseName}}}", param_value{{#isArray}}.into_iter().map(|p| p.to_string()).collect::>().join(","){{/isArray}}.to_string()); }, + None => { multipart_form_params.insert("{{{baseName}}}", ""); }, + } + {{/isNullable}} + {{/required}} + {{^required}} + if let Some(param_value) = {{{vendorExtensions.x-rust-param-identifier}}} { + multipart_form_params.insert("{{{baseName}}}", param_value{{#isArray}}.into_iter().map(|p| p.to_string()).collect::>().join(","){{/isArray}}.to_string()); + } + {{/required}} + {{/isFile}} + {{/formParams}} + req_builder = req_builder.form(&multipart_form_params); + {{/hasFormParams}} + {{/isMultipart}} + {{#hasBodyParam}} + {{#bodyParams}} + {{#isFile}} + {{#supportAsync}} + {{#required}} + let file = TokioFile::open({{{vendorExtensions.x-rust-param-identifier}}}).await?; + let stream = FramedRead::new(file, BytesCodec::new()); + req_builder = req_builder.body(reqwest::Body::wrap_stream(stream)); + {{/required}} + {{^required}} + if let Some(param_value) = {{{vendorExtensions.x-rust-param-identifier}}} { + let file = TokioFile::open(param_value).await?; + let stream = FramedRead::new(file, BytesCodec::new()); + req_builder = req_builder.body(reqwest::Body::wrap_stream(stream)); + } + {{/required}} + {{/supportAsync}} + {{^supportAsync}} + req_builder = req_builder.body({{{vendorExtensions.x-rust-param-identifier}}}); + {{/supportAsync}} + {{/isFile}} + {{^isFile}} + req_builder = req_builder.json(&{{{vendorExtensions.x-rust-param-identifier}}}); + {{/isFile}} + {{/bodyParams}} + {{/hasBodyParam}} + + let req = req_builder.build()?; + let resp = configuration.client.execute(req){{#supportAsync}}.await{{/supportAsync}}?; + + let status = resp.status(); + {{^supportMultipleResponses}} + {{^isResponseFile}} + {{#returnType}} + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + {{/returnType}} + {{/isResponseFile}} + {{/supportMultipleResponses}} + + if !status.is_client_error() && !status.is_server_error() { + {{^supportMultipleResponses}} + {{#isResponseFile}} + Ok(resp) + {{/isResponseFile}} + {{^isResponseFile}} + {{^returnType}} + Ok(()) + {{/returnType}} + {{#returnType}} + let content = resp.text(){{#supportAsync}}.await{{/supportAsync}}?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + {{#vendorExtensions.x-supports-plain-text}} + ContentType::Text => Ok(content), + {{/vendorExtensions.x-supports-plain-text}} + {{^vendorExtensions.x-supports-plain-text}} + ContentType::Text => Err(Error::from(serde_json::Error::custom( + "Received `text/plain` content type response that cannot be converted to `{{returnType}}`", + ))), + {{/vendorExtensions.x-supports-plain-text}} + ContentType::Unsupported(unknown_type) => Err(Error::from( + serde_json::Error::custom(format!( + "Received `{unknown_type}` content type response that cannot be converted to `{{returnType}}`" + )), + )), + } + {{/returnType}} + {{/isResponseFile}} + {{/supportMultipleResponses}} + {{#supportMultipleResponses}} + {{#isResponseFile}} + Ok(resp) + {{/isResponseFile}} + {{^isResponseFile}} + let content = resp.text(){{#supportAsync}}.await{{/supportAsync}}?; + let entity: Option<{{{operationIdCamelCase}}}Success> = serde_json::from_str(&content).ok(); + Ok(ResponseContent { status, content, entity }) + {{/isResponseFile}} + {{/supportMultipleResponses}} + } else { + let content = resp.text(){{#supportAsync}}.await{{/supportAsync}}?; + let entity: Option<{{{operationIdCamelCase}}}Error> = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { status, content, entity })) + } +} + +{{/operation}} +{{/operations}} diff --git a/templates/reqwest/api_mod.mustache b/templates/reqwest/api_mod.mustache new file mode 100644 index 0000000..3033e4a --- /dev/null +++ b/templates/reqwest/api_mod.mustache @@ -0,0 +1,164 @@ +use std::error; +use std::fmt; +{{#withAWSV4Signature}} +use aws_sigv4; +{{/withAWSV4Signature}} + +#[derive(Debug, Clone)] +pub struct ResponseContent { + pub status: reqwest::StatusCode, + pub content: String, + pub entity: Option, +} + +#[derive(Debug)] +pub enum Error { + Reqwest(reqwest::Error), + {{#supportMiddleware}} + ReqwestMiddleware(reqwest_middleware::Error), + {{/supportMiddleware}} + Serde(serde_json::Error), + Io(std::io::Error), + ResponseError(ResponseContent), + {{#withAWSV4Signature}} + AWSV4SignatureError(aws_sigv4::http_request::Error), + {{/withAWSV4Signature}} + {{#supportAsync}} + {{#supportTokenSource}} + TokenSource(Box), + {{/supportTokenSource}} + {{/supportAsync}} +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (module, e) = match self { + Error::Reqwest(e) => ("reqwest", e.to_string()), + {{#supportMiddleware}} + Error::ReqwestMiddleware(e) => ("reqwest-middleware", e.to_string()), + {{/supportMiddleware}} + Error::Serde(e) => ("serde", e.to_string()), + Error::Io(e) => ("IO", e.to_string()), + Error::ResponseError(e) => ("response", format!("status code {}", e.status)), + {{#withAWSV4Signature}} + Error::AWSV4SignatureError(e) => ("aws v4 signature", e.to_string()), + {{/withAWSV4Signature}} + {{#supportAsync}} + {{#supportTokenSource}} + Error::TokenSource(e) => ("token source failure", e.to_string()), + {{/supportTokenSource}} + {{/supportAsync}} + }; + write!(f, "error in {}: {}", module, e) + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + Some(match self { + Error::Reqwest(e) => e, + {{#supportMiddleware}} + Error::ReqwestMiddleware(e) => e, + {{/supportMiddleware}} + Error::Serde(e) => e, + Error::Io(e) => e, + Error::ResponseError(_) => return None, + {{#withAWSV4Signature}} + Error::AWSV4SignatureError(_) => return None, + {{/withAWSV4Signature}} + {{#supportAsync}} + {{#supportTokenSource}} + Error::TokenSource(e) => &**e, + {{/supportTokenSource}} + {{/supportAsync}} + }) + } +} + +impl From for Error { + fn from(e: reqwest::Error) -> Self { + Error::Reqwest(e) + } +} + +{{#supportMiddleware}} +impl From for Error { + fn from(e: reqwest_middleware::Error) -> Self { + Error::ReqwestMiddleware(e) + } +} + +{{/supportMiddleware}} +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Serde(e) + } +} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + Error::Io(e) + } +} + +pub fn urlencode>(s: T) -> String { + ::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect() +} + +pub fn parse_deep_object(prefix: &str, value: &serde_json::Value) -> Vec<(String, String)> { + if let serde_json::Value::Object(object) = value { + let mut params = vec![]; + + for (key, value) in object { + match value { + serde_json::Value::Object(_) => params.append(&mut parse_deep_object( + &format!("{}[{}]", prefix, key), + value, + )), + serde_json::Value::Array(array) => { + for (i, value) in array.iter().enumerate() { + params.append(&mut parse_deep_object( + &format!("{}[{}][{}]", prefix, key, i), + value, + )); + } + }, + serde_json::Value::String(s) => params.push((format!("{}[{}]", prefix, key), s.clone())), + _ => params.push((format!("{}[{}]", prefix, key), value.to_string())), + } + } + + return params; + } + + unimplemented!("Only objects are supported with style=deepObject") +} + +/// Internal use only +/// A content type supported by this client. +#[allow(dead_code)] +enum ContentType { + Json, + Text, + Unsupported(String) +} + +impl From<&str> for ContentType { + fn from(content_type: &str) -> Self { + if content_type.starts_with("application") && content_type.contains("json") { + Self::Json + } else if content_type.starts_with("text/plain") { + Self::Text + } else { + Self::Unsupported(content_type.to_string()) + } + } +} + +{{#apiInfo}} +{{#apis}} +pub mod {{{classFilename}}}; +{{/apis}} +{{/apiInfo}} + +pub mod configuration; diff --git a/templates/reqwest/configuration.mustache b/templates/reqwest/configuration.mustache new file mode 100644 index 0000000..e29c73b --- /dev/null +++ b/templates/reqwest/configuration.mustache @@ -0,0 +1,122 @@ +{{>partial_header}} + +{{#withAWSV4Signature}} +use std::time::SystemTime; +use aws_sigv4::http_request::{sign, SigningSettings, SigningParams, SignableRequest}; +use http; +use secrecy::{SecretString, ExposeSecret}; +{{/withAWSV4Signature}} +{{#supportTokenSource}} +use std::sync::Arc; +use google_cloud_token::TokenSource; +use async_trait::async_trait; +{{/supportTokenSource}} + +#[derive(Debug, Clone)] +pub struct Configuration { + pub base_path: String, + pub user_agent: Option, + pub client: {{#supportMiddleware}}reqwest_middleware::ClientWithMiddleware{{/supportMiddleware}}{{^supportMiddleware}}reqwest{{^supportAsync}}::blocking{{/supportAsync}}::Client{{/supportMiddleware}}, + {{^supportTokenSource}} + pub basic_auth: Option, + pub oauth_access_token: Option, + pub bearer_access_token: Option, + pub api_key: Option, + {{/supportTokenSource}} + {{#withAWSV4Signature}} + pub aws_v4_key: Option, + {{/withAWSV4Signature}} + {{#supportAsync}} + {{#supportTokenSource}} + pub token_source: Arc, + {{/supportTokenSource}} + {{/supportAsync}} +} +{{^supportTokenSource}} + +pub type BasicAuth = (String, Option); + +#[derive(Debug, Clone)] +pub struct ApiKey { + pub prefix: Option, + pub key: String, +} +{{/supportTokenSource}} + +{{#withAWSV4Signature}} +#[derive(Debug, Clone)] +pub struct AWSv4Key { + pub access_key: String, + pub secret_key: SecretString, + pub region: String, + pub service: String, +} + +impl AWSv4Key { + pub fn sign(&self, uri: &str, method: &str, body: &str) -> Result, aws_sigv4::http_request::Error> { + let request = http::Request::builder() + .uri(uri) + .method(method) + .body(body).unwrap(); + let signing_settings = SigningSettings::default(); + let signing_params = SigningParams::builder() + .access_key(self.access_key.as_str()) + .secret_key(self.secret_key.expose_secret().as_str()) + .region(self.region.as_str()) + .service_name(self.service.as_str()) + .time(SystemTime::now()) + .settings(signing_settings) + .build() + .unwrap(); + let signable_request = SignableRequest::from(&request); + let (mut signing_instructions, _signature) = sign(signable_request, &signing_params)?.into_parts(); + let mut additional_headers = Vec::<(String, String)>::new(); + if let Some(new_headers) = signing_instructions.take_headers() { + for (name, value) in new_headers.into_iter() { + additional_headers.push((name.expect("header should have name").to_string(), + value.to_str().expect("header value should be a string").to_string())); + } + } + Ok(additional_headers) + } +} +{{/withAWSV4Signature}} + +impl Configuration { + pub fn new() -> Configuration { + Configuration::default() + } +} + +impl Default for Configuration { + fn default() -> Self { + Configuration { + base_path: "{{{basePath}}}".to_owned(), + user_agent: {{#httpUserAgent}}Some("{{{.}}}".to_owned()){{/httpUserAgent}}{{^httpUserAgent}}Some("OpenAPI-Generator/{{{version}}}/rust".to_owned()){{/httpUserAgent}}, + client: {{#supportMiddleware}}reqwest_middleware::ClientBuilder::new(reqwest{{^supportAsync}}::blocking{{/supportAsync}}::Client::new()).build(){{/supportMiddleware}}{{^supportMiddleware}}reqwest{{^supportAsync}}::blocking{{/supportAsync}}::Client::new(){{/supportMiddleware}}, + {{^supportTokenSource}} + basic_auth: None, + oauth_access_token: None, + bearer_access_token: None, + api_key: None, + {{/supportTokenSource}} + {{#withAWSV4Signature}} + aws_v4_key: None, + {{/withAWSV4Signature}} + {{#supportTokenSource}} + token_source: Arc::new(NoopTokenSource{}), + {{/supportTokenSource}} + } + } +} +{{#supportTokenSource}} +#[derive(Debug)] +struct NoopTokenSource{} + +#[async_trait] +impl TokenSource for NoopTokenSource { + async fn token(&self) -> Result> { + panic!("This is dummy token source. You can use TokenSourceProvider from 'google_cloud_auth' crate, or any other compatible crate.") + } +} +{{/supportTokenSource}} diff --git a/tests/default_api_health_test.rs b/tests/default_api_health_test.rs new file mode 100644 index 0000000..731acf5 --- /dev/null +++ b/tests/default_api_health_test.rs @@ -0,0 +1,28 @@ +use crawl4ai_client::apis::{configuration::Configuration, default_api}; +use httpmock::prelude::*; +use serde_json::json; + +// Ensures the generated client hits the expected health endpoint and parses JSON. +#[tokio::test] +async fn health_endpoint_returns_ok() { + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(GET).path("/health"); + then.status(200) + .header("content-type", "application/json") + .json_body(json!({ "status": "ok" })); + }); + + let config = Configuration { + base_path: server.base_url(), + ..Configuration::default() + }; + + let response = default_api::health_health_get(&config) + .await + .expect("health request should succeed"); + + mock.assert(); + assert_eq!(response, json!({ "status": "ok" })); +} From f93432937e9883d0019dd5945e5b2e2224fe35f8 Mon Sep 17 00:00:00 2001 From: jasonaix Date: Thu, 11 Dec 2025 18:53:57 +0800 Subject: [PATCH 2/6] fix review by gemini --- Cargo.toml | 4 ++-- scripts/check_dirty_files.sh | 2 ++ scripts/generate_api_sdk.sh | 2 ++ scripts/generate_templates.sh | 3 ++- src/generated/models/filter_type.rs | 11 ++++------- templates/model.mustache | 27 +++++++++------------------ 6 files changed, 21 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 06f75ec..792edc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,9 +11,9 @@ rustls-tls = ["reqwest/rustls-tls"] [dependencies] serde = { version = "1.0.228", features = ["derive"] } serde_json = { version = "1.0.145" } -serde_with = { version = "3.15.1", default-features = false, features = ["base64", "std", "macros"] } +serde_with = { version = "3.16.1", default-features = false, features = ["base64", "std", "macros"] } url = { version = "2.5.7" } -reqwest = { version = "0.12.24", default-features = false, features = ["json", "multipart", "rustls-tls"] } +reqwest = { version = "0.12.25", default-features = false, features = ["json", "multipart", "rustls-tls"] } [dev-dependencies] tokio = { version = "1.47.1", features = ["full"] } diff --git a/scripts/check_dirty_files.sh b/scripts/check_dirty_files.sh index c328601..d38e797 100755 --- a/scripts/check_dirty_files.sh +++ b/scripts/check_dirty_files.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -e + ./scripts/generate_api_sdk.sh if git status --porcelain | grep -q "M"; then diff --git a/scripts/generate_api_sdk.sh b/scripts/generate_api_sdk.sh index 13af191..977b31c 100755 --- a/scripts/generate_api_sdk.sh +++ b/scripts/generate_api_sdk.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -e + openapi-generator-cli generate \ -i ./api/crawl4ai-openapi.json \ -g rust \ diff --git a/scripts/generate_templates.sh b/scripts/generate_templates.sh index a8247e1..e9ed203 100755 --- a/scripts/generate_templates.sh +++ b/scripts/generate_templates.sh @@ -1,7 +1,8 @@ #!/bin/bash - # Script to extract default Rust templates from OpenAPI Generator +set -e + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" diff --git a/src/generated/models/filter_type.rs b/src/generated/models/filter_type.rs index cbf7113..5802ed4 100644 --- a/src/generated/models/filter_type.rs +++ b/src/generated/models/filter_type.rs @@ -11,9 +11,12 @@ use crate::models; use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +#[derive( + Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, Default, +)] pub enum FilterType { #[serde(rename = "raw")] + #[default] Raw, #[serde(rename = "fit")] Fit, @@ -33,9 +36,3 @@ impl std::fmt::Display for FilterType { } } } - -impl Default for FilterType { - fn default() -> FilterType { - Self::Raw - } -} diff --git a/templates/model.mustache b/templates/model.mustache index 34bba92..c18601a 100644 --- a/templates/model.mustache +++ b/templates/model.mustache @@ -21,10 +21,12 @@ use serde_repr::{Serialize_repr,Deserialize_repr}; /// {{{description}}} {{/description}} #[repr(i64)] -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, Default)] pub enum {{{classname}}} { {{#allowableValues}} {{#enumVars}} + {{#-first}}#[default] + {{/-first}} {{{name}}} = {{{value}}}, {{/enumVars}}{{/allowableValues}} } @@ -48,11 +50,13 @@ impl std::fmt::Display for {{{classname}}} { {{#description}} /// {{{description}}} {{/description}} -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, Default)] pub enum {{{classname}}} { {{#allowableValues}} {{#enumVars}} #[serde(rename = "{{{value}}}")] + {{#-first}}#[default] + {{/-first}} {{{name}}}, {{/enumVars}}{{/allowableValues}} } @@ -70,13 +74,6 @@ impl std::fmt::Display for {{{classname}}} { } {{/isInteger}} -impl Default for {{{classname}}} { - fn default() -> {{{classname}}} { - {{#allowableValues}} - Self::{{ enumVars.0.name }} - {{/allowableValues}} - } -} {{/isEnum}} {{!-- for schemas that have a discriminator --}} {{#discriminator}} @@ -205,23 +202,17 @@ impl Default for {{classname}} { {{#vars}} {{#isEnum}} /// {{{description}}} -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, Default)] pub enum {{{enumName}}} { {{#allowableValues}} {{#enumVars}} #[serde(rename = "{{{value}}}")] + {{#-first}}#[default] + {{/-first}} {{{name}}}, {{/enumVars}} {{/allowableValues}} } - -impl Default for {{{enumName}}} { - fn default() -> {{{enumName}}} { - {{#allowableValues}} - Self::{{ enumVars.0.name }} - {{/allowableValues}} - } -} {{/isEnum}} {{/vars}} From dbe1522d45d7d1ae130532ad569397464acec6fc Mon Sep 17 00:00:00 2001 From: jasonaix Date: Thu, 11 Dec 2025 19:13:29 +0800 Subject: [PATCH 3/6] fix review by gemini --- .github/workflows/build.yml | 5 +++++ Cargo.toml | 8 ++++++++ README.md | 6 +++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f2d387d..3c7fc45 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,6 +19,11 @@ jobs: toolchain: stable components: rustfmt, clippy, llvm-tools-preview + - name: OpenAPI Generator CLI + uses: openapitools/openapi-generator-cli@v2 + with: + version: 7.16.0 + - name: Build env: RUSTFLAGS: "-D warnings" diff --git a/Cargo.toml b/Cargo.toml index 792edc7..bcddcff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,14 @@ name = "crawl4ai-client" version = "0.1.0" edition = "2024" +description = "Rust SDK for the Crawl4AI API" +license = "MIT" +repository = "https://github.com/nurokhq/crawl4ai-client-rust" +homepage = "https://github.com/nurokhq/crawl4ai-client-rust" +documentation = "https://docs.crawl4ai.com" +authors = ["Rui Zhang ", "Jian Fang "] +keywords = ["api", "client", "crawl4ai", "sdk"] +categories = ["api-bindings"] [features] default = ["rustls-tls"] diff --git a/README.md b/README.md index 1e00480..8551c87 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # crawl4ai-client-rust [![Build](https://github.com/nurokhq/crawl4ai-client-rust/actions/workflows/build.yml/badge.svg)](https://github.com/nurokhq/crawl4ai-client-rust/actions/workflows/build.yml) -[![Rust](https://img.shields.io/badge/rust-1.70+-blue.svg)](https://www.rust-lang.org/) +[![Rust](https://img.shields.io/badge/rust-1.85+-blue.svg)](https://www.rust-lang.org/) [![Code style: rustfmt](https://img.shields.io/badge/code%20style-rustfmt-000000.svg)](https://github.com/rust-lang/rustfmt) [![Linter: clippy](https://img.shields.io/badge/linter-clippy-blue.svg)](https://github.com/rust-lang/rust-clippy) -Rust SDK for the [Crawl4AI API](https://github.com/unclecode/crawl4ai), providing type-safe bindings to interact with Crawl4AI. +Rust API Client SDK for [Crawl4AI API](https://github.com/unclecode/crawl4ai), providing type-safe bindings to interact with Crawl4AI. ## Overview @@ -103,7 +103,7 @@ This script: ## Requirements -- Rust 1.70 or later +- Rust 1.85 or later - OpenAPI Generator CLI (for regeneration) ## License From e2bd7bb5a23907d127433f56bbe8265838a621b7 Mon Sep 17 00:00:00 2001 From: jasonaix Date: Thu, 11 Dec 2025 19:21:39 +0800 Subject: [PATCH 4/6] fix workflow build error --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3c7fc45..96587fb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,7 @@ jobs: components: rustfmt, clippy, llvm-tools-preview - name: OpenAPI Generator CLI - uses: openapitools/openapi-generator-cli@v2 + uses: openapitools/openapi-generator-cli@v1 with: version: 7.16.0 From 6b04d4025e439846ee9c4b8f2501d31200eb396e Mon Sep 17 00:00:00 2001 From: jasonaix Date: Thu, 11 Dec 2025 19:22:57 +0800 Subject: [PATCH 5/6] fix workflow build error --- .github/workflows/build.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 96587fb..022ce8e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,10 +19,15 @@ jobs: toolchain: stable components: rustfmt, clippy, llvm-tools-preview - - name: OpenAPI Generator CLI - uses: openapitools/openapi-generator-cli@v1 + - name: Setup Node + uses: actions/setup-node@v4 with: - version: 7.16.0 + node-version: 20 + + - name: Install OpenAPI Generator CLI + run: | + npm install -g @openapitools/openapi-generator-cli@7.16.0 + openapi-generator-cli version-manager set 7.16.0 - name: Build env: From 9cb8a453e9b72c7fda1134de44560e3f2c34a936 Mon Sep 17 00:00:00 2001 From: jasonaix Date: Thu, 11 Dec 2025 19:28:54 +0800 Subject: [PATCH 6/6] fix workflow build error --- .github/workflows/build.yml | 2 +- scripts/generate_api_sdk.sh | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 022ce8e..16f4e8b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,7 +26,7 @@ jobs: - name: Install OpenAPI Generator CLI run: | - npm install -g @openapitools/openapi-generator-cli@7.16.0 + npm install -g @openapitools/openapi-generator-cli openapi-generator-cli version-manager set 7.16.0 - name: Build diff --git a/scripts/generate_api_sdk.sh b/scripts/generate_api_sdk.sh index 977b31c..75d530f 100755 --- a/scripts/generate_api_sdk.sh +++ b/scripts/generate_api_sdk.sh @@ -8,10 +8,6 @@ openapi-generator-cli generate \ -o ./api/crawl4ai-client \ -t ./templates -# find ./api/crawl4ai-client -type f -name "*.rs" -exec sed -i '' 's/use crate::models;/use crate::generated::models;/g' {} \; -# find ./api/crawl4ai-client -type f -name "*.rs" -exec sed -i '' 's/crate::apis/crate::generated::apis/g' {} \; -# find ./api/crawl4ai-client -type f -name "*.rs" -exec sed -i '' 's/use crate::{apis::ResponseContent, models};/use crate::generated::{apis::ResponseContent, models};/g' {} \; - rm -rf ./src/generated/apis rm -rf ./src/generated/models cp -r ./api/crawl4ai-client/src/apis ./src/generated/apis