astral-reqwest-retry-0.9.1/.cargo_vcs_info.json0000644000000001531046102023000152050ustar { "git": { "sha1": "9d6740936fd3378eb891b51bda12ff28f998642c" }, "path_in_vcs": "reqwest-retry" }astral-reqwest-retry-0.9.1/.gitignore000064400000000000000000000000231046102023000157370ustar 00000000000000/target Cargo.lock astral-reqwest-retry-0.9.1/CHANGELOG.md000064400000000000000000000047171046102023000155760ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.9.1](https://github.com/TrueLayer/reqwest-middleware/compare/reqwest-retry-v0.9.0...reqwest-retry-v0.9.1) - 2026-02-05 ### Fixed - *(reqwest-retry)* drop instant by upgrading wasmtimer ([#254](https://github.com/TrueLayer/reqwest-middleware/pull/254)) ### Other - Set changelog version for last release ([#268](https://github.com/TrueLayer/reqwest-middleware/pull/268)) ## [0.9.0] - 2026-01-07 ### Changed - Updated `reqwest` to `0.13` ## [0.8.0] - 2025-11-26 ### Breaking Changes - Updated `retry-policies` (re-exported as `reqwest_retry::policies`) to 0.5. ### Added - Report retry count on `Ok` results that underwent retries through a `RetryCount` response extension. ### Changed - Updated `thiserror` to `2.0` ## [0.7.0] - 2024-11-08 ### Breaking changes - Errors are now reported as `RetryError` that adds the number of retries to the error chain if there were any. This changes the returned error types. ### Added - Added support reqwest-middleware `0.4` next to `0.3` ## [0.6.1] - 2024-08-08 ### Added - Removed dependency on `chrono` ([#170](https://github.com/TrueLayer/reqwest-middleware/pull/170)) ## [0.6.0] - 2024-06-28 ### Added - Added `with_retry_log_level` to `RetryTransientMiddleware` ### Changed - Upgraded `retry-policies` to `0.4.0`. ## [0.5.0] - 2024-04-10 ### Breaking changes - Upgraded `reqwest-middleware` to `0.3.0`. ## [0.3.0] - 2023-09-07 ### Changed - `retry-policies` upgraded to 0.2.0 ## [0.2.3] - 2023-08-30 ### Added - `RetryableStrategy` which allows for custom retry decisions based on the response that a request got ## [0.2.1] - 2022-12-01 ### Changed - Classify `io::Error`s and `hyper::Error(Canceled)` as transient ## [0.2.0] - 2022-11-15 ### Changed - Updated `reqwest-middleware` to `0.2.0` ## [0.1.4] - 2022-02-21 ### Changed - Updated `reqwest-middleware` to `0.1.5` ## [0.1.3] - 2022-01-24 ### Changed - Updated `reqwest-middleware` to `0.1.4` ## [0.1.2] - 2021-09-28 ### Added - Re-export `RetryPolicy` from the crate root. ### Changed - Disabled default features on `reqwest` - Replaced `truelayer-extensions` with `task-local-extensions` - Updated `reqwest-middleware` to `0.1.2` ## [0.1.1] - 2021-09-15 ### Changed - Updated `reqwest-middleware` dependency to `0.1.1`. astral-reqwest-retry-0.9.1/Cargo.lock0000644000001064031046102023000131650ustar # 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 = "anyhow" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[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 = "astral-reqwest-middleware" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98e1c6be25cfbf1bb4fea1a9da51bc05d3259a9062df4e53f54e5607895e33c9" dependencies = [ "anyhow", "async-trait", "http", "reqwest", "thiserror", "tower-service", ] [[package]] name = "astral-reqwest-retry" version = "0.9.1" dependencies = [ "anyhow", "astral-reqwest-middleware", "async-trait", "futures", "getrandom 0.2.17", "http", "hyper", "paste", "reqwest", "retry-policies", "thiserror", "tokio", "tracing", "wasmtimer", "wiremock", ] [[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", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bumpalo" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "deadpool" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b" dependencies = [ "deadpool-runtime", "lazy_static", "num_cpus", "tokio", ] [[package]] name = "deadpool-runtime" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "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 = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[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" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[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", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[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-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "getrandom" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" 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", "libc", "r-efi", "wasip2", ] [[package]] name = "h2" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[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 = "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 = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", "http", "http-body", "pin-project-lite", ] [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "h2", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "pin-utils", "smallvec", "tokio", "want", ] [[package]] name = "hyper-util" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64", "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", "socket2", "tokio", "tower-service", "tracing", ] [[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 = "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 = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown", ] [[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.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", ] [[package]] name = "itoa" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[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" [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[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 = "num_cpus" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[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 = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[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 = "potential_utf" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" 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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" 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 = "regex" version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "reqwest" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ "base64", "bytes", "futures-core", "http", "http-body", "http-body-util", "hyper", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", "sync_wrapper", "tokio", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", ] [[package]] name = "retry-policies" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46a4bd6027df676bcb752d3724db0ea3c0c5fc1dd0376fec51ac7dcaf9cc69be" dependencies = [ "rand", ] [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.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", ] [[package]] name = "serde_json" version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", "serde", "serde_core", "zmij", ] [[package]] name = "signal-hook-registry" version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ "errno", "libc", ] [[package]] name = "slab" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" 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 = "syn" version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" 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", ] [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tinystr" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tokio" version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "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", ] [[package]] name = "tokio-util" version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "tower" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" 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", "http-body", "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.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tracing-core" version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" 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 = "unicode-ident" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" [[package]] name = "url" version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" 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 = "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.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", "futures-util", "js-sys", "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] [[package]] name = "wasmtimer" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" dependencies = [ "futures", "js-sys", "parking_lot", "pin-utils", "slab", "wasm-bindgen", ] [[package]] name = "web-sys" version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets", ] [[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.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[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.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[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.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[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.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "wiremock" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08db1edfb05d9b3c1542e521aea074442088292f00b5f28e435c714a98f85031" dependencies = [ "assert-json-diff", "base64", "deadpool", "futures", "http", "http-body-util", "hyper", "hyper-util", "log", "once_cell", "regex", "serde", "serde_json", "tokio", "url", ] [[package]] name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[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", "synstructure", ] [[package]] name = "zerocopy" version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "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", ] [[package]] name = "zmij" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" astral-reqwest-retry-0.9.1/Cargo.toml0000644000000042451046102023000132110ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "astral-reqwest-retry" version = "0.9.1" authors = ["Rodrigo Gryzinski "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Retry middleware for reqwest." readme = "README.md" keywords = [ "reqwest", "http", "middleware", "retry", ] categories = ["web-programming::http-client"] license = "MIT OR Apache-2.0" repository = "https://github.com/astral-sh/reqwest-middleware" [features] default = ["tracing"] tracing = ["dep:tracing"] [lib] name = "reqwest_retry" path = "src/lib.rs" [[test]] name = "all" path = "tests/all/main.rs" [dependencies.anyhow] version = "1.0.0" [dependencies.async-trait] version = "0.1.51" [dependencies.futures] version = "0.3.0" [dependencies.http] version = "1.0" [dependencies.reqwest] version = "0.13.1" default-features = false [dependencies.reqwest-middleware] version = "0.5" package = "astral-reqwest-middleware" [dependencies.retry-policies] version = "0.5" [dependencies.thiserror] version = "2.0" [dependencies.tracing] version = "0.1.26" optional = true [dev-dependencies.futures] version = "0.3.0" [dev-dependencies.paste] version = "1.0.0" [dev-dependencies.tokio] version = "1.0.0" features = ["full"] [dev-dependencies.wiremock] version = "0.6.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies.hyper] version = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio] version = "1.6.0" features = ["time"] default-features = false [target.'cfg(target_arch = "wasm32")'.dependencies.getrandom] version = "0.2.0" features = ["js"] [target.'cfg(target_arch = "wasm32")'.dependencies.wasmtimer] version = "0.4.3" astral-reqwest-retry-0.9.1/Cargo.toml.orig000064400000000000000000000023031046102023000166410ustar 00000000000000[package] name = "astral-reqwest-retry" version = "0.9.1" authors = ["Rodrigo Gryzinski "] edition = "2018" description = "Retry middleware for reqwest." repository = "https://github.com/astral-sh/reqwest-middleware" license = "MIT OR Apache-2.0" keywords = ["reqwest", "http", "middleware", "retry"] categories = ["web-programming::http-client"] [lib] name = "reqwest_retry" [features] default = ["tracing"] tracing = ["dep:tracing"] [dependencies] reqwest-middleware = { version = "0.5", path = "../reqwest-middleware", package = "astral-reqwest-middleware" } anyhow = "1.0.0" async-trait = "0.1.51" futures = "0.3.0" http = "1.0" reqwest = { version = "0.13.1", default-features = false } retry-policies = "0.5" thiserror = "2.0" tracing = { version = "0.1.26", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] hyper = "1.0" tokio = { version = "1.6.0", default-features = false, features = ["time"] } [target.'cfg(target_arch = "wasm32")'.dependencies] wasmtimer = "0.4.3" getrandom = { version = "0.2.0", features = ["js"] } [dev-dependencies] paste = "1.0.0" tokio = { version = "1.0.0", features = ["full"] } wiremock = "0.6.0" futures = "0.3.0" astral-reqwest-retry-0.9.1/LICENSE-APACHE000064400000000000000000000227731046102023000157130ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS astral-reqwest-retry-0.9.1/LICENSE-MIT000064400000000000000000000020521046102023000154070ustar 00000000000000MIT License Copyright (c) 2021 TrueLayer 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. astral-reqwest-retry-0.9.1/README.md000064400000000000000000000025701046102023000152370ustar 00000000000000# reqwest-retry Retry middleware implementation for [`reqwest-middleware`](https://crates.io/crates/reqwest-middleware). [![Crates.io](https://img.shields.io/crates/v/reqwest-retry.svg)](https://crates.io/crates/reqwest-retry) [![Docs.rs](https://docs.rs/reqwest-retry/badge.svg)](https://docs.rs/reqwest-retry) [![CI](https://github.com/TrueLayer/reqwest-middleware/workflows/CI/badge.svg)](https://github.com/TrueLayer/reqwest-middleware/actions) [![Coverage Status](https://coveralls.io/repos/github/TrueLayer/reqwest-middleware/badge.svg?branch=main&t=UWgSpm)](https://coveralls.io/github/TrueLayer/reqwest-middleware?branch=main) ## Overview Build `RetryTransientMiddleware` from a `RetryPolicy`, then attach it to a `reqwest_middleware::ClientBuilder`. [`retry-policies::policies`](https://crates.io/crates/retry-policies) is reexported under `reqwest_retry::policies` for convenience. See [`reqwest_middleware`](https://docs.rs/reqwest_middleware) for usage with reqwest. #### License Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. astral-reqwest-retry-0.9.1/src/lib.rs000064400000000000000000000032041046102023000156560ustar 00000000000000//! Middleware to retry failed HTTP requests built on [`reqwest_middleware`]. //! //! Use [`RetryTransientMiddleware`] to retry failed HTTP requests. Retry control flow is managed //! by a [`RetryPolicy`]. //! //! ## Example //! //! ``` //! use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; //! use reqwest_retry::{RetryTransientMiddleware, policies::ExponentialBackoff}; //! //! async fn run_retries() { //! // Retry up to 3 times with increasing intervals between attempts. //! let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3); //! let client = ClientBuilder::new(reqwest::Client::new()) //! .with(RetryTransientMiddleware::new_with_policy(retry_policy)) //! .build(); //! //! client //! .get("https://truelayer.com") //! .header("foo", "bar") //! .send() //! .await //! .unwrap(); //! } //! ``` mod middleware; mod retryable; mod retryable_strategy; pub use retry_policies::{policies, Jitter, RetryDecision, RetryPolicy}; use thiserror::Error; pub use middleware::{RetryCount, RetryTransientMiddleware}; pub use retryable::Retryable; pub use retryable_strategy::{ default_on_request_error, default_on_request_failure, default_on_request_success, DefaultRetryableStrategy, RetryableStrategy, }; /// Custom error type to attach the number of retries to the error message. #[derive(Debug, Error)] pub enum RetryError { #[error("Request failed after {retries} retries")] WithRetries { retries: u32, #[source] err: reqwest_middleware::Error, }, #[error(transparent)] Error(reqwest_middleware::Error), } astral-reqwest-retry-0.9.1/src/middleware.rs000064400000000000000000000222331046102023000172300ustar 00000000000000//! `RetryTransientMiddleware` implements retrying requests on transient errors. use std::time::{Duration, SystemTime}; use crate::retryable_strategy::RetryableStrategy; use crate::{retryable::Retryable, retryable_strategy::DefaultRetryableStrategy, RetryError}; use anyhow::anyhow; use http::Extensions; use reqwest::{Request, Response}; use reqwest_middleware::{Error, Middleware, Next, Result}; use retry_policies::RetryPolicy; #[doc(hidden)] // We need this macro because tracing expects the level to be const: // https://github.com/tokio-rs/tracing/issues/2730 #[cfg(feature = "tracing")] macro_rules! log_retry { ($level:expr, $($args:tt)*) => {{ match $level { ::tracing::Level::TRACE => ::tracing::trace!($($args)*), ::tracing::Level::DEBUG => ::tracing::debug!($($args)*), ::tracing::Level::INFO => ::tracing::info!($($args)*), ::tracing::Level::WARN => ::tracing::warn!($($args)*), ::tracing::Level::ERROR => ::tracing::error!($($args)*), } }}; } /// `RetryTransientMiddleware` offers retry logic for requests that fail in a transient manner /// and can be safely executed again. /// /// Currently, it allows setting a [RetryPolicy] algorithm for calculating the __wait_time__ /// between each request retry. Sleeping on non-`wasm32` archs is performed using /// [`tokio::time::sleep`], therefore it will respect pauses/auto-advance if run under a /// runtime that supports them. /// ///```rust /// use std::time::Duration; /// use reqwest_middleware::ClientBuilder; /// use retry_policies::{RetryDecision, RetryPolicy, Jitter}; /// use retry_policies::policies::ExponentialBackoff; /// use reqwest_retry::RetryTransientMiddleware; /// use reqwest::Client; /// /// // We create a ExponentialBackoff retry policy which implements `RetryPolicy`. /// let retry_policy = ExponentialBackoff::builder() /// .retry_bounds(Duration::from_secs(1), Duration::from_secs(60)) /// .jitter(Jitter::Bounded) /// .base(2) /// .build_with_total_retry_duration(Duration::from_secs(24 * 60 * 60)); /// /// let retry_transient_middleware = RetryTransientMiddleware::new_with_policy(retry_policy); /// let client = ClientBuilder::new(Client::new()).with(retry_transient_middleware).build(); ///``` /// /// # Note /// /// This middleware always errors when given requests with streaming bodies, before even executing /// the request. When this happens you'll get an [`Error::Middleware`] with the message /// 'Request object is not cloneable. Are you passing a streaming body?'. /// /// Some workaround suggestions: /// * If you can fit the data in memory, you can instead build static request bodies e.g. with /// `Body`'s `From` or `From` implementations. /// * You can wrap this middleware in a custom one which skips retries for streaming requests. /// * You can write a custom retry middleware that builds new streaming requests from the data /// source directly, avoiding the issue of streaming requests not being cloneable. pub struct RetryTransientMiddleware< T: RetryPolicy + Send + Sync + 'static, R: RetryableStrategy + Send + Sync + 'static = DefaultRetryableStrategy, > { retry_policy: T, retryable_strategy: R, #[cfg(feature = "tracing")] retry_log_level: tracing::Level, } impl RetryTransientMiddleware { /// Construct `RetryTransientMiddleware` with a [retry_policy][RetryPolicy]. pub fn new_with_policy(retry_policy: T) -> Self { Self::new_with_policy_and_strategy(retry_policy, DefaultRetryableStrategy) } /// Set the log [level][tracing::Level] for retry events. /// The default is [`WARN`][tracing::Level::WARN]. #[cfg(feature = "tracing")] pub fn with_retry_log_level(mut self, level: tracing::Level) -> Self { self.retry_log_level = level; self } } impl RetryTransientMiddleware where T: RetryPolicy + Send + Sync, R: RetryableStrategy + Send + Sync, { /// Construct `RetryTransientMiddleware` with a [retry_policy][RetryPolicy] and [retryable_strategy](RetryableStrategy). pub fn new_with_policy_and_strategy(retry_policy: T, retryable_strategy: R) -> Self { Self { retry_policy, retryable_strategy, #[cfg(feature = "tracing")] retry_log_level: tracing::Level::WARN, } } } #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] impl Middleware for RetryTransientMiddleware where T: RetryPolicy + Send + Sync, R: RetryableStrategy + Send + Sync + 'static, { async fn handle( &self, req: Request, extensions: &mut Extensions, next: Next<'_>, ) -> Result { // TODO: Ideally we should create a new instance of the `Extensions` map to pass // downstream. This will guard against previous retries polluting `Extensions`. // That is, we only return what's populated in the typemap for the last retry attempt // and copy those into the the `global` Extensions map. self.execute_with_retry(req, next, extensions).await } } impl RetryTransientMiddleware where T: RetryPolicy + Send + Sync, R: RetryableStrategy + Send + Sync, { /// This function will try to execute the request, if it fails /// with an error classified as transient it will call itself /// to retry the request. async fn execute_with_retry<'a>( &'a self, req: Request, next: Next<'a>, ext: &'a mut Extensions, ) -> Result { let mut n_past_retries = 0; let start_time = SystemTime::now(); loop { // Cloning the request object before-the-fact is not ideal.. // However, if the body of the request is not static, e.g of type `Bytes`, // the Clone operation should be of constant complexity and not O(N) // since the byte abstraction is a shared pointer over a buffer. let duplicate_request = req.try_clone().ok_or_else(|| { Error::Middleware(anyhow!( "Request object is not cloneable. Are you passing a streaming body?" .to_string() )) })?; let result = next.clone().run(duplicate_request, ext).await; // We classify the response which will return None if not // errors were returned. if let Some(Retryable::Transient) = self.retryable_strategy.handle(&result) { // If the response failed and the error type was transient // we can safely try to retry the request. let retry_decision = self.retry_policy.should_retry(start_time, n_past_retries); if let retry_policies::RetryDecision::Retry { execute_after } = retry_decision { let duration = execute_after .duration_since(SystemTime::now()) .unwrap_or_else(|_| Duration::default()); // Sleep the requested amount before we try again. #[cfg(feature = "tracing")] log_retry!( self.retry_log_level, "Retry attempt #{}. Sleeping {:?} before the next attempt", n_past_retries, duration ); #[cfg(not(target_arch = "wasm32"))] tokio::time::sleep(duration).await; #[cfg(target_arch = "wasm32")] wasmtimer::tokio::sleep(duration).await; n_past_retries += 1; continue; } }; break if n_past_retries > 0 { // Both `Ok` results (e.g. status code errors) and `Err` results (e.g. an // `io::Error` for from connection reset), and we want to inform the user about the // retries in both cases. match result { Ok(mut response) => { response .extensions_mut() .insert(RetryCount::new(n_past_retries)); Ok(response) } Err(err) => Err(Error::Middleware( RetryError::WithRetries { retries: n_past_retries, err, } .into(), )), } } else { result.map_err(|err| Error::Middleware(RetryError::Error(err).into())) }; } } } /// Extension type to store retry count in a response. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct RetryCount(u32); impl RetryCount { /// Create a new retry count. pub fn new(count: u32) -> Self { Self(count) } pub fn value(self) -> u32 { self.0 } } impl std::ops::Deref for RetryCount { type Target = u32; fn deref(&self) -> &Self::Target { &self.0 } } astral-reqwest-retry-0.9.1/src/retryable.rs000064400000000000000000000014271046102023000171060ustar 00000000000000use crate::retryable_strategy::{DefaultRetryableStrategy, RetryableStrategy}; use reqwest_middleware::Error; /// Classification of an error/status returned by request. #[derive(PartialEq, Eq)] pub enum Retryable { /// The failure was due to something that might resolve in the future. Transient, /// Unresolvable error. Fatal, } impl Retryable { /// Try to map a `reqwest` response into `Retryable`. /// /// Returns `None` if the response object does not contain any errors. /// pub fn from_reqwest_response(res: &Result) -> Option { DefaultRetryableStrategy.handle(res) } } impl From<&reqwest::Error> for Retryable { fn from(_status: &reqwest::Error) -> Retryable { Retryable::Transient } } astral-reqwest-retry-0.9.1/src/retryable_strategy.rs000064400000000000000000000177421046102023000210370ustar 00000000000000use crate::retryable::Retryable; use http::StatusCode; use reqwest_middleware::Error; /// A strategy to create a [`Retryable`] from a [`Result`] /// /// A [`RetryableStrategy`] has a single `handler` functions. /// The result of calling the request could be: /// - [`reqwest::Response`] In case the request has been sent and received correctly /// This could however still mean that the server responded with a erroneous response. /// For example a HTTP statuscode of 500 /// - [`reqwest_middleware::Error`] In this case the request actually failed. /// This could, for example, be caused by a timeout on the connection. /// /// Example: /// /// ``` /// use reqwest_retry::{default_on_request_failure, policies::ExponentialBackoff, Retryable, RetryableStrategy, RetryTransientMiddleware}; /// use reqwest::{Request, Response}; /// use reqwest_middleware::{ClientBuilder, Middleware, Next, Result}; /// use http::Extensions; /// /// // Log each request to show that the requests will be retried /// struct LoggingMiddleware; /// /// #[async_trait::async_trait] /// impl Middleware for LoggingMiddleware { /// async fn handle( /// &self, /// req: Request, /// extensions: &mut Extensions, /// next: Next<'_>, /// ) -> Result { /// println!("Request started {}", req.url()); /// let res = next.run(req, extensions).await; /// println!("Request finished"); /// res /// } /// } /// /// // Just a toy example, retry when the successful response code is 201, else do nothing. /// struct Retry201; /// impl RetryableStrategy for Retry201 { /// fn handle(&self, res: &Result) -> Option { /// match res { /// // retry if 201 /// Ok(success) if success.status() == 201 => Some(Retryable::Transient), /// // otherwise do not retry a successful request /// Ok(success) => None, /// // but maybe retry a request failure /// Err(error) => default_on_request_failure(error), /// } /// } /// } /// /// #[tokio::main] /// async fn main() { /// // Exponential backoff with max 2 retries /// let retry_policy = ExponentialBackoff::builder() /// .build_with_max_retries(2); /// /// // Create the actual middleware, with the exponential backoff and custom retry strategy. /// let ret_s = RetryTransientMiddleware::new_with_policy_and_strategy( /// retry_policy, /// Retry201, /// ); /// /// let client = ClientBuilder::new(reqwest::Client::new()) /// // Retry failed requests. /// .with(ret_s) /// // Log the requests /// .with(LoggingMiddleware) /// .build(); /// /// // Send request which should get a 201 response. So it will be retried /// let r = client /// .get("https://httpbin.org/status/201") /// .send() /// .await; /// println!("{:?}", r); /// /// // Send request which should get a 200 response. So it will not be retried /// let r = client /// .get("https://httpbin.org/status/200") /// .send() /// .await; /// println!("{:?}", r); /// } /// ``` pub trait RetryableStrategy { fn handle(&self, res: &Result) -> Option; } /// The default [`RetryableStrategy`] for [`RetryTransientMiddleware`](crate::RetryTransientMiddleware). pub struct DefaultRetryableStrategy; impl RetryableStrategy for DefaultRetryableStrategy { fn handle(&self, res: &Result) -> Option { match res { Ok(success) => default_on_request_success(success), Err(error) => default_on_request_failure(error), } } } /// Default request success retry strategy. /// /// Will only retry if: /// * The status was 5XX (server error) /// * The status was 408 (request timeout) or 429 (too many requests) /// /// Note that success here means that the request finished without interruption, not that it was logically OK. pub fn default_on_request_success(success: &reqwest::Response) -> Option { let status = success.status(); if status.is_server_error() { Some(Retryable::Transient) } else if status.is_client_error() && status != StatusCode::REQUEST_TIMEOUT && status != StatusCode::TOO_MANY_REQUESTS { Some(Retryable::Fatal) } else if status.is_success() { None } else if status == StatusCode::REQUEST_TIMEOUT || status == StatusCode::TOO_MANY_REQUESTS { Some(Retryable::Transient) } else { Some(Retryable::Fatal) } } /// Default request failure retry strategy for a [`reqwest_middleware::Error`]. /// /// Will only retry if the request failed due to a network error pub fn default_on_request_failure(error: &Error) -> Option { match error { // If something fails in the middleware we're screwed. Error::Middleware(_) => Some(Retryable::Fatal), Error::Reqwest(error) => default_on_request_error(error), } } /// Default request failure retry strategy for the [`reqwest::Error`] part of a /// [`reqwest_middleware::Error`]. /// /// Will only retry if the request failed due to a network error pub fn default_on_request_error(error: &reqwest::Error) -> Option { #[cfg(not(target_arch = "wasm32"))] let is_connect = error.is_connect(); #[cfg(target_arch = "wasm32")] let is_connect = false; if error.is_timeout() || is_connect { Some(Retryable::Transient) } else if error.is_body() || error.is_decode() || error.is_builder() || error.is_redirect() { Some(Retryable::Fatal) } else if error.is_request() { // It seems that hyper::Error(IncompleteMessage) is not correctly handled by reqwest. // Here we check if the Reqwest error was originated by hyper and map it consistently. #[cfg(not(target_arch = "wasm32"))] if let Some(hyper_error) = get_source_error_type::(&error) { // The hyper::Error(IncompleteMessage) is raised if the HTTP response is well formatted but does not contain all the bytes. // This can happen when the server has started sending back the response but the connection is cut halfway through. // We can safely retry the call, hence marking this error as [`Retryable::Transient`]. // Instead hyper::Error(Canceled) is raised when the connection is // gracefully closed on the server side. if hyper_error.is_incomplete_message() || hyper_error.is_canceled() { Some(Retryable::Transient) // Try and downcast the hyper error to io::Error if that is the // underlying error, and try and classify it. } else if let Some(io_error) = get_source_error_type::(hyper_error) { Some(classify_io_error(io_error)) } else { Some(Retryable::Fatal) } } else { Some(Retryable::Fatal) } #[cfg(target_arch = "wasm32")] Some(Retryable::Fatal) } else { // We omit checking if error.is_status() since we check that already. // However, if Response::error_for_status is used the status will still // remain in the response object. None } } #[cfg(not(target_arch = "wasm32"))] fn classify_io_error(error: &std::io::Error) -> Retryable { match error.kind() { std::io::ErrorKind::ConnectionReset | std::io::ErrorKind::ConnectionAborted => { Retryable::Transient } _ => Retryable::Fatal, } } /// Downcasts the given err source into T. #[cfg(not(target_arch = "wasm32"))] fn get_source_error_type( err: &dyn std::error::Error, ) -> Option<&T> { let mut source = err.source(); while let Some(err) = source { if let Some(err) = err.downcast_ref::() { return Some(err); } source = err.source(); } None } astral-reqwest-retry-0.9.1/tests/all/helpers/mod.rs000064400000000000000000000000711046102023000204530ustar 00000000000000mod simple_server; pub use simple_server::SimpleServer; astral-reqwest-retry-0.9.1/tests/all/helpers/simple_server.rs000064400000000000000000000122511046102023000225560ustar 00000000000000use futures::future::BoxFuture; use std::error::Error; use std::fmt; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream}; type CustomMessageHandler = Box< dyn Fn(TcpStream) -> BoxFuture<'static, Result<(), Box>> + Send + Sync, >; /// This is a simple server that returns the responses given at creation time: [`self.raw_http_responses`] following a round-robin mechanism. pub struct SimpleServer { listener: TcpListener, port: u16, host: String, raw_http_responses: Vec, calls_counter: usize, custom_handler: Option, } /// Request-Line = Method SP Request-URI SP HTTP-Version CRLF struct Request<'a> { method: &'a str, uri: &'a str, http_version: &'a str, } impl fmt::Display for Request<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} {} {}\r\n", self.method, self.uri, self.http_version) } } impl SimpleServer { /// Creates an instance of a [`SimpleServer`] /// If [`port`] is None os Some(0), it gets randomly chosen between the available ones. pub async fn new( host: &str, port: Option, raw_http_responses: Vec, ) -> Result { let port = port.unwrap_or(0); let listener = TcpListener::bind(format!("{}:{}", host, port)).await?; let port = listener.local_addr()?.port(); Ok(Self { listener, port, host: host.to_string(), raw_http_responses, calls_counter: 0, custom_handler: None, }) } pub fn set_custom_handler( &mut self, custom_handler: impl Fn(TcpStream) -> BoxFuture<'static, Result<(), Box>> + Send + Sync + 'static, ) -> &mut Self { self.custom_handler.replace(Box::new(custom_handler)); self } /// Returns the uri in which the server is listening to. pub fn uri(&self) -> String { format!("http://{}:{}", self.host, self.port) } /// Starts the TcpListener and handles the requests. pub async fn start(mut self) { loop { match self.listener.accept().await { Ok((stream, _)) => { match self.handle_connection(stream).await { Ok(_) => (), Err(e) => { println!("Error handling connection: {}", e); } } self.calls_counter += 1; } Err(e) => { println!("Connection failed: {}", e); } } } } /// Asyncrounously reads from the buffer and handle the request. /// It first checks that the format is correct, then returns the response. /// /// Returns a 400 if the request if formatted badly. async fn handle_connection(&self, mut stream: TcpStream) -> Result<(), Box> { if let Some(ref custom_handler) = self.custom_handler { return custom_handler(stream).await; } let mut buffer = vec![0; 1024]; let n = stream.read(&mut buffer).await.unwrap(); let request = String::from_utf8_lossy(&buffer[..n]); let request_line = request.lines().next().unwrap(); let response = match Self::parse_request_line(request_line) { Ok(request) => { println!("== Request == \n{}\n=============", request); self.get_response().clone() } Err(e) => { println!("++ Bad request: {} ++++++", e); self.get_bad_request_response() } }; println!("-- Response --\n{}\n--------------", response.clone()); stream.write_all(response.as_bytes()).await.unwrap(); stream.flush().await.unwrap(); Ok(()) } /// Parses the request line and checks that it contains the method, uri and http_version parts. /// It does not check if the content of the checked parts is correct. It just checks the format (it contains enough parts) of the request. fn parse_request_line(request: &str) -> Result, Box> { let mut parts = request.split_whitespace(); let method = parts.next().ok_or("Method not specified")?; let uri = parts.next().ok_or("URI not specified")?; let http_version = parts.next().ok_or("HTTP version not specified")?; Ok(Request { method, uri, http_version, }) } /// Returns the response to use based on the calls counter. /// It uses a round-robin mechanism. fn get_response(&self) -> String { let index = if self.calls_counter >= self.raw_http_responses.len() { self.raw_http_responses.len() % self.calls_counter } else { self.calls_counter }; self.raw_http_responses[index].clone() } /// Returns the raw HTTP response in case of a 400 Bad Request. fn get_bad_request_response(&self) -> String { "HTTP/1.1 400 Bad Request\r\n\r\n".to_string() } } astral-reqwest-retry-0.9.1/tests/all/main.rs000064400000000000000000000000301046102023000171510ustar 00000000000000mod helpers; mod retry; astral-reqwest-retry-0.9.1/tests/all/retry.rs000064400000000000000000000303051046102023000174020ustar 00000000000000use futures::FutureExt; use paste::paste; use reqwest::Client; use reqwest::StatusCode; use reqwest_middleware::ClientBuilder; use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; use std::sync::atomic::AtomicI8; use std::sync::{ atomic::{AtomicU32, Ordering}, Arc, }; use tokio::io::AsyncReadExt; use tokio::io::AsyncWriteExt; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, Respond, ResponseTemplate}; use crate::helpers::SimpleServer; pub struct RetryResponder(Arc, u32, u16); impl RetryResponder { fn new(retries: u32, status_code: u16) -> Self { Self(Arc::new(AtomicU32::new(0)), retries, status_code) } } impl Respond for RetryResponder { fn respond(&self, _request: &wiremock::Request) -> ResponseTemplate { let mut retries = self.0.load(Ordering::SeqCst); retries += 1; self.0.store(retries, Ordering::SeqCst); if retries + 1 >= self.1 { ResponseTemplate::new(200) } else { ResponseTemplate::new(self.2) } } } macro_rules! assert_retry_succeeds_inner { ($x:tt, $name:ident, $status:expr, $retry:tt, $exact:tt, $responder:expr) => { #[tokio::test] async fn $name() { let server = MockServer::start().await; let retry_amount: u32 = $retry; Mock::given(method("GET")) .and(path("/foo")) .respond_with($responder) .expect($exact) .mount(&server) .await; let reqwest_client = Client::builder().build().unwrap(); let client = ClientBuilder::new(reqwest_client) .with(RetryTransientMiddleware::new_with_policy( ExponentialBackoff::builder() .retry_bounds( std::time::Duration::from_millis(30), std::time::Duration::from_millis(100), ) .build_with_max_retries(retry_amount), )) .build(); let resp = client .get(&format!("{}/foo", server.uri())) .send() .await .expect("call failed"); assert_eq!(resp.status(), $status); } }; } macro_rules! assert_retry_succeeds { ($x:tt, $status:expr) => { paste! { assert_retry_succeeds_inner!($x, [], $status, 3, 2, RetryResponder::new(3 as u32, $x)); } }; } macro_rules! assert_no_retry { ($x:tt, $status:expr) => { paste! { assert_retry_succeeds_inner!($x, [], $status, 1, 1, ResponseTemplate::new($x)); } }; } // 2xx. assert_no_retry!(200, StatusCode::OK); assert_no_retry!(201, StatusCode::CREATED); assert_no_retry!(202, StatusCode::ACCEPTED); assert_no_retry!(203, StatusCode::NON_AUTHORITATIVE_INFORMATION); assert_no_retry!(204, StatusCode::NO_CONTENT); assert_no_retry!(205, StatusCode::RESET_CONTENT); assert_no_retry!(206, StatusCode::PARTIAL_CONTENT); assert_no_retry!(207, StatusCode::MULTI_STATUS); assert_no_retry!(226, StatusCode::IM_USED); // 3xx. assert_no_retry!(300, StatusCode::MULTIPLE_CHOICES); assert_no_retry!(301, StatusCode::MOVED_PERMANENTLY); assert_no_retry!(302, StatusCode::FOUND); assert_no_retry!(303, StatusCode::SEE_OTHER); assert_no_retry!(304, StatusCode::NOT_MODIFIED); assert_no_retry!(307, StatusCode::TEMPORARY_REDIRECT); assert_no_retry!(308, StatusCode::PERMANENT_REDIRECT); // 5xx. assert_retry_succeeds!(500, StatusCode::OK); assert_retry_succeeds!(501, StatusCode::OK); assert_retry_succeeds!(502, StatusCode::OK); assert_retry_succeeds!(503, StatusCode::OK); assert_retry_succeeds!(504, StatusCode::OK); assert_retry_succeeds!(505, StatusCode::OK); assert_retry_succeeds!(506, StatusCode::OK); assert_retry_succeeds!(507, StatusCode::OK); assert_retry_succeeds!(508, StatusCode::OK); assert_retry_succeeds!(510, StatusCode::OK); assert_retry_succeeds!(511, StatusCode::OK); // 4xx. assert_no_retry!(400, StatusCode::BAD_REQUEST); assert_no_retry!(401, StatusCode::UNAUTHORIZED); assert_no_retry!(402, StatusCode::PAYMENT_REQUIRED); assert_no_retry!(403, StatusCode::FORBIDDEN); assert_no_retry!(404, StatusCode::NOT_FOUND); assert_no_retry!(405, StatusCode::METHOD_NOT_ALLOWED); assert_no_retry!(406, StatusCode::NOT_ACCEPTABLE); assert_no_retry!(407, StatusCode::PROXY_AUTHENTICATION_REQUIRED); assert_retry_succeeds!(408, StatusCode::OK); assert_no_retry!(409, StatusCode::CONFLICT); assert_no_retry!(410, StatusCode::GONE); assert_no_retry!(411, StatusCode::LENGTH_REQUIRED); assert_no_retry!(412, StatusCode::PRECONDITION_FAILED); assert_no_retry!(413, StatusCode::PAYLOAD_TOO_LARGE); assert_no_retry!(414, StatusCode::URI_TOO_LONG); assert_no_retry!(415, StatusCode::UNSUPPORTED_MEDIA_TYPE); assert_no_retry!(416, StatusCode::RANGE_NOT_SATISFIABLE); assert_no_retry!(417, StatusCode::EXPECTATION_FAILED); assert_no_retry!(418, StatusCode::IM_A_TEAPOT); assert_no_retry!(421, StatusCode::MISDIRECTED_REQUEST); assert_no_retry!(422, StatusCode::UNPROCESSABLE_ENTITY); assert_no_retry!(423, StatusCode::LOCKED); assert_no_retry!(424, StatusCode::FAILED_DEPENDENCY); assert_no_retry!(426, StatusCode::UPGRADE_REQUIRED); assert_no_retry!(428, StatusCode::PRECONDITION_REQUIRED); assert_retry_succeeds!(429, StatusCode::OK); assert_no_retry!(431, StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE); assert_no_retry!(451, StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS); pub struct RetryTimeoutResponder(Arc, u32, std::time::Duration); impl RetryTimeoutResponder { fn new(retries: u32, initial_timeout: std::time::Duration) -> Self { Self(Arc::new(AtomicU32::new(0)), retries, initial_timeout) } } impl Respond for RetryTimeoutResponder { fn respond(&self, _request: &wiremock::Request) -> ResponseTemplate { let mut retries = self.0.load(Ordering::SeqCst); retries += 1; self.0.store(retries, Ordering::SeqCst); if retries + 1 >= self.1 { ResponseTemplate::new(200) } else { ResponseTemplate::new(500).set_delay(self.2) } } } #[tokio::test] async fn assert_retry_on_request_timeout() { let server = MockServer::start().await; Mock::given(method("GET")) .and(path("/foo")) .respond_with(RetryTimeoutResponder::new( 3, std::time::Duration::from_millis(1000), )) .expect(2) .mount(&server) .await; let reqwest_client = Client::builder().build().unwrap(); let client = ClientBuilder::new(reqwest_client) .with(RetryTransientMiddleware::new_with_policy( ExponentialBackoff::builder() .retry_bounds( std::time::Duration::from_millis(30), std::time::Duration::from_millis(100), ) .build_with_max_retries(3), )) .build(); let resp = client .get(format!("{}/foo", server.uri())) .timeout(std::time::Duration::from_millis(10)) .send() .await .expect("call failed"); assert_eq!(resp.status(), 200); } #[tokio::test] async fn assert_retry_on_incomplete_message() { // Following the HTTP/1.1 specification (https://en.wikipedia.org/wiki/HTTP_message_body) a valid response contains: // - status line // - headers // - empty line // - optional message body // // After a few tries we have noticed that: // - "message_that_makes_no_sense" triggers a hyper::ParseError because the format is completely wrong // - "HTTP/1.1" triggers a hyper::IncompleteMessage because the format is correct until that point but misses mandatory parts let incomplete_message = "HTTP/1.1"; let complete_message = "HTTP/1.1 200 OK\r\n\r\n"; // create a SimpleServer that returns the correct response after 3 attempts. // the first 3 attempts are incomplete http response and internally they result in a [`hyper::Error(IncompleteMessage)`] error. let simple_server = SimpleServer::new( "127.0.0.1", None, vec![ incomplete_message.to_string(), incomplete_message.to_string(), incomplete_message.to_string(), complete_message.to_string(), ], ) .await .expect("Error when creating a simple server"); let uri = simple_server.uri(); tokio::spawn(simple_server.start()); let reqwest_client = Client::builder().build().unwrap(); let client = ClientBuilder::new(reqwest_client) .with(RetryTransientMiddleware::new_with_policy( ExponentialBackoff::builder() .retry_bounds( std::time::Duration::from_millis(30), std::time::Duration::from_millis(100), ) .build_with_max_retries(3), )) .build(); let resp = client .get(format!("{}/foo", uri)) .timeout(std::time::Duration::from_millis(100)) .send() .await .expect("call failed"); assert_eq!(resp.status(), 200); } #[tokio::test] async fn assert_retry_on_hyper_canceled() { let counter = Arc::new(AtomicI8::new(0)); let mut simple_server = SimpleServer::new("127.0.0.1", None, vec![]) .await .expect("Error when creating a simple server"); simple_server.set_custom_handler(move |mut stream| { let counter = counter.clone(); async move { let mut buffer = Vec::new(); stream.read_buf(&mut buffer).await.unwrap(); if counter.fetch_add(1, Ordering::SeqCst) > 1 { // This triggers hyper:Error(Canceled). let _res = stream .into_std() .unwrap() .shutdown(std::net::Shutdown::Both); } else { let _res = stream.write("HTTP/1.1 200 OK\r\n\r\n".as_bytes()).await; } Ok(()) } .boxed() }); let uri = simple_server.uri(); tokio::spawn(simple_server.start()); let reqwest_client = Client::builder().build().unwrap(); let client = ClientBuilder::new(reqwest_client) .with(RetryTransientMiddleware::new_with_policy( ExponentialBackoff::builder() .retry_bounds( std::time::Duration::from_millis(30), std::time::Duration::from_millis(100), ) .build_with_max_retries(3), )) .build(); let resp = client .get(format!("{}/foo", uri)) .timeout(std::time::Duration::from_millis(100)) .send() .await .expect("call failed"); assert_eq!(resp.status(), 200); } #[tokio::test] async fn assert_retry_on_connection_reset_by_peer() { let counter = Arc::new(AtomicI8::new(0)); let mut simple_server = SimpleServer::new("127.0.0.1", None, vec![]) .await .expect("Error when creating a simple server"); simple_server.set_custom_handler(move |mut stream| { let counter = counter.clone(); async move { let mut buffer = Vec::new(); stream.read_buf(&mut buffer).await.unwrap(); if counter.fetch_add(1, Ordering::SeqCst) > 1 { // This triggers hyper:Error(Io, io::Error(ConnectionReset)). drop(stream); } else { let _res = stream.write("HTTP/1.1 200 OK\r\n\r\n".as_bytes()).await; } Ok(()) } .boxed() }); let uri = simple_server.uri(); tokio::spawn(simple_server.start()); let reqwest_client = Client::builder().build().unwrap(); let client = ClientBuilder::new(reqwest_client) .with(RetryTransientMiddleware::new_with_policy( ExponentialBackoff::builder() .retry_bounds( std::time::Duration::from_millis(30), std::time::Duration::from_millis(100), ) .build_with_max_retries(3), )) .build(); let resp = client .get(format!("{}/foo", uri)) .timeout(std::time::Duration::from_millis(100)) .send() .await .expect("call failed"); assert_eq!(resp.status(), 200); }