backon-1.6.0/.cargo_vcs_info.json0000644000000001440000000000100122740ustar { "git": { "sha1": "e24dc92742e201f7320868d46a12ff59eb82ef72" }, "path_in_vcs": "backon" }backon-1.6.0/Cargo.lock0000644000002060570000000000100102620ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anyhow" version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "atoi" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" dependencies = [ "num-traits", ] [[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 = "backon" version = "1.6.0" dependencies = [ "anyhow", "embassy-time", "fastrand", "futures-timer", "gloo-timers 0.3.0", "reqwest", "spin 0.10.0", "sqlx", "tokio", "wasm-bindgen-test", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bitflags" version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" dependencies = [ "serde", ] [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" version = "1.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" 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 = "concurrent-queue" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[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 = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "critical-section" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] name = "crossbeam-queue" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "der" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "pem-rfc7468", "zeroize", ] [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "const-oid", "crypto-common", "subtle", ] [[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 = "document-features" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" dependencies = [ "litrs", ] [[package]] name = "dotenvy" version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" dependencies = [ "serde", ] [[package]] name = "embassy-time" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65" dependencies = [ "cfg-if", "critical-section", "document-features", "embassy-time-driver", "embedded-hal 0.2.7", "embedded-hal 1.0.0", "embedded-hal-async", "futures-core", ] [[package]] name = "embassy-time-driver" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6" dependencies = [ "document-features", ] [[package]] name = "embedded-hal" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" dependencies = [ "nb 0.1.3", "void", ] [[package]] name = "embedded-hal" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" [[package]] name = "embedded-hal-async" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" dependencies = [ "embedded-hal 1.0.0", ] [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[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 = "etcetera" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ "cfg-if", "home", "windows-sys 0.48.0", ] [[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 = "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.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "flume" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", "spin 0.9.8", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[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", "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-intrusive" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", "parking_lot", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[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-timer" version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" dependencies = [ "gloo-timers 0.2.6", ] [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-io", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "generic-array" version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", "wasi", ] [[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 = "gloo-timers" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" dependencies = [ "futures-channel", "futures-core", "js-sys", "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 = "h2" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] [[package]] name = "hashbrown" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "hashlink" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ "hashbrown 0.15.5", ] [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ "digest", ] [[package]] name = "home" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "http" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.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 = "hyper" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "h2", "http", "http-body", "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", "hyper", "hyper-util", "rustls", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", ] [[package]] name = "hyper-tls" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", "hyper", "hyper-util", "native-tls", "tokio", "tokio-native-tls", "tower-service", ] [[package]] name = "hyper-util" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "base64", "bytes", "futures-channel", "futures-core", "futures-util", "http", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", "socket2", "system-configuration", "tokio", "tower-service", "tracing", "windows-registry", ] [[package]] name = "icu_collections" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locale_core" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_normalizer" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", "icu_locale_core", "stable_deref_trait", "tinystr", "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.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.0", ] [[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.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" dependencies = [ "memchr", "serde", ] [[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.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" 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" dependencies = [ "spin 0.9.8", ] [[package]] name = "libc" version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libm" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags", "libc", "redox_syscall", ] [[package]] name = "libsqlite3-sys" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", "vcpkg", ] [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "litrs" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[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.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "md-5" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", "digest", ] [[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 = "minicov" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" dependencies = [ "cc", "walkdir", ] [[package]] name = "mio" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" 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 = "nb" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" dependencies = [ "nb 1.1.0", ] [[package]] name = "nb" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" [[package]] name = "num-bigint-dig" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" dependencies = [ "byteorder", "lazy_static", "libm", "num-integer", "num-iter", "num-traits", "rand", "smallvec", "zeroize", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", ] [[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.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" 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", ] [[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.110" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" 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 0.2.1", ] [[package]] name = "pem-rfc7468" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ "base64ct", ] [[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 = "pkcs1" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ "der", "pkcs8", "spki", ] [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", "spki", ] [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "potential_utf" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" 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.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" 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.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.16", ] [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "reqwest" version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", "hyper-tls", "hyper-util", "js-sys", "log", "mime", "native-tls", "percent-encoding", "pin-project-lite", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", "tokio-native-tls", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", ] [[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 = "rsa" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" dependencies = [ "const-oid", "digest", "num-bigint-dig", "num-integer", "num-traits", "pkcs1", "pkcs8", "rand_core", "signature", "spki", "subtle", "zeroize", ] [[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.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c" dependencies = [ "once_cell", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pki-types" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" version = "0.103.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" 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 = "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", ] [[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_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 = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sha2" version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signature" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", "rand_core", ] [[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" dependencies = [ "serde", ] [[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 = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ "lock_api", ] [[package]] name = "spin" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" dependencies = [ "lock_api", ] [[package]] name = "spki" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", ] [[package]] name = "sqlx" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" dependencies = [ "sqlx-core", "sqlx-macros", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", ] [[package]] name = "sqlx-core" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ "base64", "bytes", "crc", "crossbeam-queue", "either", "event-listener", "futures-core", "futures-intrusive", "futures-io", "futures-util", "hashbrown 0.15.5", "hashlink", "indexmap", "log", "memchr", "once_cell", "percent-encoding", "serde", "serde_json", "sha2", "smallvec", "thiserror", "tokio", "tokio-stream", "tracing", "url", ] [[package]] name = "sqlx-macros" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", "syn", ] [[package]] name = "sqlx-macros-core" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "dotenvy", "either", "heck", "hex", "once_cell", "proc-macro2", "quote", "serde", "serde_json", "sha2", "sqlx-core", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "syn", "tokio", "url", ] [[package]] name = "sqlx-mysql" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", "bitflags", "byteorder", "bytes", "crc", "digest", "dotenvy", "either", "futures-channel", "futures-core", "futures-io", "futures-util", "generic-array", "hex", "hkdf", "hmac", "itoa", "log", "md-5", "memchr", "once_cell", "percent-encoding", "rand", "rsa", "serde", "sha1", "sha2", "smallvec", "sqlx-core", "stringprep", "thiserror", "tracing", "whoami", ] [[package]] name = "sqlx-postgres" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", "bitflags", "byteorder", "crc", "dotenvy", "etcetera", "futures-channel", "futures-core", "futures-util", "hex", "hkdf", "hmac", "home", "itoa", "log", "md-5", "memchr", "once_cell", "rand", "serde", "serde_json", "sha2", "smallvec", "sqlx-core", "stringprep", "thiserror", "tracing", "whoami", ] [[package]] name = "sqlx-sqlite" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ "atoi", "flume", "futures-channel", "futures-core", "futures-executor", "futures-intrusive", "futures-util", "libsqlite3-sys", "log", "percent-encoding", "serde", "serde_urlencoded", "sqlx-core", "thiserror", "tracing", "url", ] [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stringprep" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" dependencies = [ "unicode-bidi", "unicode-normalization", "unicode-properties", ] [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" 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 = "system-configuration" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "tempfile" version = "3.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 = "thiserror" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[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", ] [[package]] name = "tinystr" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" 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", "pin-project-lite", "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-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 = "tokio-stream" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", "tokio", ] [[package]] name = "tokio-util" version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" 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.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tracing-core" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 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 = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-bidi" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-normalization" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[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 = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[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 = "wasite" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" dependencies = [ "cfg-if", "js-sys", "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e381134e148c1062f965a42ed1f5ee933eef2927c3f70d1812158f711d39865" dependencies = [ "js-sys", "minicov", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", ] [[package]] name = "wasm-bindgen-test-macro" version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b673bca3298fe582aeef8352330ecbad91849f85090805582400850f8270a2e8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "web-sys" version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "whoami" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" dependencies = [ "libredox", "wasite", ] [[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 = "windows-link" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-registry" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ "windows-link 0.1.3", "windows-result", "windows-strings", ] [[package]] name = "windows-result" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link 0.1.3", ] [[package]] name = "windows-strings" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link 0.1.3", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[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.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 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 0.2.1", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[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 0.2.1", "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.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[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.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[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.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[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.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[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.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[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.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[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.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[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.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "yoke" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerocopy" version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" 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 = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" dependencies = [ "displaydoc", "yoke", "zerofrom", ] [[package]] name = "zerovec" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", "syn", ] backon-1.6.0/Cargo.toml0000644000000051000000000000100102670ustar # 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 = "2024" rust-version = "1.85" name = "backon" version = "1.6.0" build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Make retry like a built-in feature provided by Rust." documentation = "https://docs.rs/backon" readme = "README.md" license = "Apache-2.0" repository = "https://github.com/Xuanwo/backon" resolver = "2" [package.metadata.docs.rs] all-features = true targets = [ "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-pc-windows-msvc", "wasm32-unknown-unknown", ] [features] default = [ "std", "std-blocking-sleep", "tokio-sleep", "gloo-timers-sleep", ] embassy-sleep = ["embassy-time"] futures-timer-sleep = ["futures-timer"] gloo-timers-sleep = ["gloo-timers/futures"] std = ["fastrand/std"] std-blocking-sleep = [] tokio-sleep = ["tokio/time"] [lib] name = "backon" path = "src/lib.rs" [dependencies.embassy-time] version = "0.5" optional = true [dependencies.fastrand] version = "2" default-features = false [dev-dependencies.anyhow] version = "1" [dev-dependencies.reqwest] version = "0.12" [dev-dependencies.spin] version = "0.10.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies.futures-timer] version = "3.0.3" optional = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio] version = "1" optional = true [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.sqlx] version = "0.8.0" features = [ "runtime-tokio", "sqlite", ] [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.tokio] version = "1" features = [ "time", "rt", "macros", "sync", "rt-multi-thread", ] [target.'cfg(target_arch = "wasm32")'.dependencies.futures-timer] version = "3.0.3" features = ["gloo-timers"] optional = true [target.'cfg(target_arch = "wasm32")'.dependencies.gloo-timers] version = "0.3" optional = true [target.'cfg(target_arch = "wasm32")'.dev-dependencies.tokio] version = "1" features = [ "macros", "rt", "sync", ] default-features = false [target.'cfg(target_arch = "wasm32")'.dev-dependencies.wasm-bindgen-test] version = "0.3" backon-1.6.0/Cargo.toml.orig000064400000000000000000000032251046102023000137560ustar 00000000000000[package] description = "Make retry like a built-in feature provided by Rust." documentation = "https://docs.rs/backon" name = "backon" readme = "../README.md" rust-version = "1.85" version = "1.6.0" edition.workspace = true license.workspace = true repository.workspace = true [package.metadata.docs.rs] all-features = true targets = [ "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-pc-windows-msvc", "wasm32-unknown-unknown", ] [features] default = ["std", "std-blocking-sleep", "tokio-sleep", "gloo-timers-sleep"] embassy-sleep = ["embassy-time"] futures-timer-sleep = ["futures-timer"] gloo-timers-sleep = ["gloo-timers/futures"] std = ["fastrand/std"] std-blocking-sleep = [] tokio-sleep = ["tokio/time"] [dependencies] embassy-time = { version = "0.5", optional = true } fastrand = { version = "2", default-features = false } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] futures-timer = { version = "3.0.3", optional = true } tokio = { version = "1", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] futures-timer = { version = "3.0.3", optional = true, features = [ "gloo-timers", ] } gloo-timers = { version = "0.3", optional = true } [dev-dependencies] anyhow = "1" reqwest = "0.12" spin = "0.10.0" [target.'cfg(target_arch = "wasm32")'.dev-dependencies] tokio = { version = "1", features = [ "macros", "rt", "sync", ], default-features = false } wasm-bindgen-test = "0.3" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] sqlx = { version = "0.8.0", features = ["runtime-tokio", "sqlite"] } tokio = { version = "1", features = [ "time", "rt", "macros", "sync", "rt-multi-thread", ] } backon-1.6.0/LICENSE000064400000000000000000000261161046102023000121000ustar 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 APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2021 Datafuse Labs Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.backon-1.6.0/README.md000064400000000000000000000064501046102023000123510ustar 00000000000000# BackON   [![Build Status]][actions] [![Latest Version]][crates.io] [![](https://img.shields.io/discord/1111711408875393035?logo=discord&label=discord)](https://discord.gg/8ARnvtJePD) [Build Status]: https://img.shields.io/github/actions/workflow/status/Xuanwo/backon/ci.yml?branch=main [actions]: https://github.com/Xuanwo/backon/actions?query=branch%3Amain [Latest Version]: https://img.shields.io/crates/v/backon.svg [crates.io]: https://crates.io/crates/backon BackON Make **retry** like a built-in feature provided by Rust. - **Simple API**: Native feel: `your_fn.retry(ExponentialBuilder::default()).await`. - **Sync & Async**: Supports both blocking and async operations seamlessly. - **Precise Control**: Define when to retry and get notified via [`when`](https://docs.rs/backon/latest/backon/struct.Retry.html#method.when) and [`notify`](https://docs.rs/backon/latest/backon/struct.Retry.html#method.notify). - **Custom Strategies**: Use built-in backoff strategies (exponential, constant) or define custom ones. Also supports dynamic backoff, such as using the HTTP `Retry-After` header. - **Cross-Platform**: Works everywhere Rust does, including `wasm` & `no-std`. --- ## Quick Start For more examples, check out the [examples](https://docs.rs/backon/latest/backon/docs/examples/index.html). ### Retry an async function. ```rust use anyhow::Result; use backon::ExponentialBuilder; use backon::Retryable; async fn fetch() -> Result { Ok("hello, world!".to_string()) } #[tokio::main] async fn main() -> Result<()> { let content = fetch // Retry with exponential backoff .retry(ExponentialBuilder::default()) // Sleep implementation, required if no feature has been enabled .sleep(tokio::time::sleep) // When to retry .when(|e| e.to_string() == "EOF") // Notify when retrying .notify(|err: &anyhow::Error, dur: Duration| { println!("retrying {:?} after {:?}", err, dur); }) .await?; println!("fetch succeeded: {}", content); Ok(()) } ``` ### Retry a blocking function. ```rust use anyhow::Result; use backon::BlockingRetryable; use backon::ExponentialBuilder; fn fetch() -> Result { Ok("hello, world!".to_string()) } fn main() -> Result<()> { let content = fetch // Retry with exponential backoff .retry(ExponentialBuilder::default()) // Sleep implementation, required if no feature has been enabled .sleep(std::thread::sleep) // When to retry .when(|e| e.to_string() == "EOF") // Notify when retrying .notify(|err: &anyhow::Error, dur: Duration| { println!("retrying {:?} after {:?}", err, dur); }) .call()?; println!("fetch succeeded: {}", content); Ok(()) } ``` ## Contributing Check out the [CONTRIBUTING.md](./CONTRIBUTING.md) guide for more details on getting started with contributing to this project. ## Getting help Submit [issues](https://github.com/Xuanwo/backon/issues/new/choose) for bug report or asking questions in [discussion](https://github.com/Xuanwo/backon/discussions/new?category=q-a). ## License Licensed under Apache License, Version 2.0. backon-1.6.0/src/backoff/api.rs000064400000000000000000000027471046102023000144000ustar 00000000000000use core::time::Duration; /// Backoff is an [`Iterator`] that returns [`Duration`]. /// /// - `Some(Duration)` indicates the caller should `sleep(Duration)` and retry the request. /// - `None` indicates the limits have been reached, and the caller should return the current error instead. pub trait Backoff: Iterator + Send + Sync + Unpin {} impl Backoff for T where T: Iterator + Send + Sync + Unpin {} /// BackoffBuilder is utilized to construct a new backoff. pub trait BackoffBuilder: Send + Sync + Unpin { /// The associated backoff returned by this builder. type Backoff: Backoff; /// Construct a new backoff using the builder. fn build(self) -> Self::Backoff; } impl BackoffBuilder for B { type Backoff = B; fn build(self) -> Self::Backoff { self } } #[cfg(test)] mod tests { use super::*; use crate::ConstantBuilder; use crate::ExponentialBuilder; use crate::FibonacciBuilder; fn test_fn_builder(b: impl BackoffBuilder) { let _ = b.build(); } #[test] fn test_backoff_builder() { test_fn_builder([Duration::from_secs(1)].into_iter()); // Just for test if user can keep using &XxxBuilder. #[allow(clippy::needless_borrows_for_generic_args)] { test_fn_builder(&ConstantBuilder::default()); test_fn_builder(&FibonacciBuilder::default()); test_fn_builder(&ExponentialBuilder::default()); } } } backon-1.6.0/src/backoff/constant.rs000064400000000000000000000141561046102023000154550ustar 00000000000000use core::time::Duration; use crate::backoff::BackoffBuilder; /// ConstantBuilder is used to create a [`ConstantBackoff`], providing a steady delay with a fixed number of retries. /// /// # Default /// /// - delay: 1s /// - max_times: 3 /// /// # Examples /// /// ```no_run /// use anyhow::Result; /// use backon::ConstantBuilder; /// use backon::Retryable; /// /// async fn fetch() -> Result { /// Ok(reqwest::get("https://www.rust-lang.org") /// .await? /// .text() /// .await?) /// } /// /// #[tokio::main(flavor = "current_thread")] /// async fn main() -> Result<()> { /// let content = fetch.retry(ConstantBuilder::default()).await?; /// println!("fetch succeeded: {}", content); /// /// Ok(()) /// } /// ``` #[derive(Debug, Clone, Copy)] pub struct ConstantBuilder { delay: Duration, max_times: Option, jitter: bool, seed: Option, } impl Default for ConstantBuilder { fn default() -> Self { Self::new() } } impl ConstantBuilder { /// Create a new `ConstantBuilder` with default values. pub const fn new() -> Self { Self { delay: Duration::from_secs(1), max_times: Some(3), jitter: false, seed: None, } } /// Set the delay for the backoff. pub const fn with_delay(mut self, delay: Duration) -> Self { self.delay = delay; self } /// Set the maximum number of attempts to be made. pub const fn with_max_times(mut self, max_times: usize) -> Self { self.max_times = Some(max_times); self } /// Enable jitter for the backoff. /// /// Jitter is a random value added to the delay to prevent a thundering herd problem. pub const fn with_jitter(mut self) -> Self { self.jitter = true; self } /// Set the seed value for the jitter random number generator. If no seed is given, a random seed is used in std and default seed is used in no_std. pub fn with_jitter_seed(mut self, seed: u64) -> Self { self.seed = Some(seed); self } /// Set no max times for the backoff. /// /// The backoff will not stop by itself. /// /// _The backoff could stop reaching `usize::MAX` attempts but this is **unrealistic**._ pub const fn without_max_times(mut self) -> Self { self.max_times = None; self } } impl BackoffBuilder for ConstantBuilder { type Backoff = ConstantBackoff; fn build(self) -> Self::Backoff { ConstantBackoff { delay: self.delay, max_times: self.max_times, attempts: 0, jitter: self.jitter, rng: if let Some(seed) = self.seed { fastrand::Rng::with_seed(seed) } else { #[cfg(feature = "std")] let rng = fastrand::Rng::new(); #[cfg(not(feature = "std"))] let rng = fastrand::Rng::with_seed(super::RANDOM_SEED); rng }, } } } impl BackoffBuilder for &ConstantBuilder { type Backoff = ConstantBackoff; fn build(self) -> Self::Backoff { (*self).build() } } /// ConstantBackoff offers a consistent delay with a limited number of retries. /// /// This backoff strategy is constructed by [`ConstantBuilder`]. #[doc(hidden)] #[derive(Debug)] pub struct ConstantBackoff { delay: Duration, max_times: Option, attempts: usize, jitter: bool, rng: fastrand::Rng, } impl Iterator for ConstantBackoff { type Item = Duration; fn next(&mut self) -> Option { let mut delay = || match self.jitter { true => self.delay + self.delay.mul_f32(self.rng.f32()), false => self.delay, }; match self.max_times { None => Some(delay()), Some(max_times) => { if self.attempts >= max_times { None } else { self.attempts += 1; Some(delay()) } } } } } #[cfg(test)] mod tests { use core::time::Duration; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; const TEST_BUILDER: ConstantBuilder = ConstantBuilder::new() .with_delay(Duration::from_secs(2)) .with_max_times(5) .with_jitter(); #[test] fn test_constant_default() { let mut it = ConstantBuilder::default().build(); assert_eq!(Some(Duration::from_secs(1)), it.next()); assert_eq!(Some(Duration::from_secs(1)), it.next()); assert_eq!(Some(Duration::from_secs(1)), it.next()); assert_eq!(None, it.next()); } #[test] fn test_constant_with_delay() { let mut it = ConstantBuilder::default() .with_delay(Duration::from_secs(2)) .build(); assert_eq!(Some(Duration::from_secs(2)), it.next()); assert_eq!(Some(Duration::from_secs(2)), it.next()); assert_eq!(Some(Duration::from_secs(2)), it.next()); assert_eq!(None, it.next()); } #[test] fn test_constant_with_times() { let mut it = ConstantBuilder::default().with_max_times(1).build(); assert_eq!(Some(Duration::from_secs(1)), it.next()); assert_eq!(None, it.next()); } #[test] fn test_constant_with_jitter() { let mut it = ConstantBuilder::default().with_jitter().build(); let dur = it.next().unwrap(); fastrand::seed(7); assert!(dur > Duration::from_secs(1)); } #[test] fn test_constant_without_max_times() { let mut it = ConstantBuilder::default().without_max_times().build(); for _ in 0..10_000 { assert_eq!(Some(Duration::from_secs(1)), it.next()); } } // allow assertions on constants because they are not optimized out by unit tests #[allow(clippy::assertions_on_constants)] #[test] fn test_constant_const_builder() { assert_eq!(TEST_BUILDER.delay, Duration::from_secs(2)); assert_eq!(TEST_BUILDER.max_times, Some(5)); assert!(TEST_BUILDER.jitter); } } backon-1.6.0/src/backoff/exponential.rs000064400000000000000000000344311046102023000161500ustar 00000000000000use core::time::Duration; use crate::backoff::BackoffBuilder; /// ExponentialBuilder is used to construct an [`ExponentialBackoff`] that offers delays with exponential retries. /// /// # Default /// /// - jitter: false /// - factor: 2 /// - min_delay: 1s /// - max_delay: 60s /// - max_times: 3 /// /// # Examples /// /// ```no_run /// use anyhow::Result; /// use backon::ExponentialBuilder; /// use backon::Retryable; /// /// async fn fetch() -> Result { /// Ok(reqwest::get("https://www.rust-lang.org") /// .await? /// .text() /// .await?) /// } /// /// #[tokio::main(flavor = "current_thread")] /// async fn main() -> Result<()> { /// let content = fetch.retry(ExponentialBuilder::default()).await?; /// println!("fetch succeeded: {}", content); /// /// Ok(()) /// } /// ``` #[derive(Debug, Clone, Copy)] pub struct ExponentialBuilder { jitter: bool, factor: f32, min_delay: Duration, max_delay: Option, max_times: Option, total_delay: Option, seed: Option, } impl Default for ExponentialBuilder { fn default() -> Self { Self::new() } } impl ExponentialBuilder { /// Create a new `ExponentialBuilder` with default values. pub const fn new() -> Self { Self { jitter: false, factor: 2.0, min_delay: Duration::from_secs(1), max_delay: Some(Duration::from_secs(60)), max_times: Some(3), total_delay: None, seed: None, } } /// Enable jitter for the backoff. /// /// When jitter is enabled, [`ExponentialBackoff`] will add a random jitter within `(0, current_delay)` /// to the current delay. pub const fn with_jitter(mut self) -> Self { self.jitter = true; self } /// Set the seed value for the jitter random number generator. If no seed is given, a random seed is used in std and default seed is used in no_std. pub fn with_jitter_seed(mut self, seed: u64) -> Self { self.seed = Some(seed); self } /// Set the factor for the backoff. /// /// Note: Having a factor less than `1.0` does not make any sense as it would create a /// smaller negative backoff. pub const fn with_factor(mut self, factor: f32) -> Self { self.factor = factor; self } /// Set the minimum delay for the backoff. pub const fn with_min_delay(mut self, min_delay: Duration) -> Self { self.min_delay = min_delay; self } /// Set the maximum delay for the backoff. /// /// The delay will not increase if the current delay exceeds the maximum delay. pub const fn with_max_delay(mut self, max_delay: Duration) -> Self { self.max_delay = Some(max_delay); self } /// Set no maximum delay for the backoff. /// /// The delay will keep increasing. /// /// _The delay will saturate at `Duration::MAX` which is an **unrealistic** delay._ pub const fn without_max_delay(mut self) -> Self { self.max_delay = None; self } /// Set the maximum number of attempts for the current backoff. /// /// The backoff will stop if the maximum number of attempts is reached. pub const fn with_max_times(mut self, max_times: usize) -> Self { self.max_times = Some(max_times); self } /// Set no maximum number of attempts for the current backoff. /// /// The backoff will not stop by itself. /// /// _The backoff could stop reaching `usize::MAX` attempts but this is **unrealistic**._ pub const fn without_max_times(mut self) -> Self { self.max_times = None; self } /// Set the total delay for the backoff. /// /// The backoff will stop yielding sleep durations once the cumulative sleep time /// plus the next sleep duration would exceed `total_delay`. pub const fn with_total_delay(mut self, total_delay: Option) -> Self { self.total_delay = total_delay; self } } impl BackoffBuilder for ExponentialBuilder { type Backoff = ExponentialBackoff; fn build(self) -> Self::Backoff { ExponentialBackoff { jitter: self.jitter, rng: if let Some(seed) = self.seed { fastrand::Rng::with_seed(seed) } else { #[cfg(feature = "std")] let rng = fastrand::Rng::new(); #[cfg(not(feature = "std"))] let rng = fastrand::Rng::with_seed(super::RANDOM_SEED); rng }, factor: self.factor, min_delay: self.min_delay, max_delay: self.max_delay, max_times: self.max_times, current_delay: None, attempts: 0, cumulative_delay: Duration::ZERO, total_delay: self.total_delay, } } } impl BackoffBuilder for &ExponentialBuilder { type Backoff = ExponentialBackoff; fn build(self) -> Self::Backoff { (*self).build() } } /// ExponentialBackoff provides a delay with exponential retries. /// /// This backoff strategy is constructed by [`ExponentialBuilder`]. #[doc(hidden)] #[derive(Debug)] pub struct ExponentialBackoff { jitter: bool, rng: fastrand::Rng, factor: f32, min_delay: Duration, max_delay: Option, max_times: Option, total_delay: Option, current_delay: Option, cumulative_delay: Duration, attempts: usize, } impl Iterator for ExponentialBackoff { type Item = Duration; fn next(&mut self) -> Option { if self.attempts >= self.max_times.unwrap_or(usize::MAX) { return None; } self.attempts += 1; let mut tmp_cur = match self.current_delay { None => { // If current_delay is None, it's must be the first time to retry. self.min_delay } Some(mut cur) => { // If current delay larger than max delay, we should stop increment anymore. if let Some(max_delay) = self.max_delay { if cur < max_delay { cur = saturating_mul(cur, self.factor); } if cur > max_delay { cur = max_delay; } } else { cur = saturating_mul(cur, self.factor); } cur } }; let current_delay = tmp_cur; // If jitter is enabled, add random jitter based on min delay. if self.jitter { tmp_cur = tmp_cur.saturating_add(tmp_cur.mul_f32(self.rng.f32())); } // Check if adding the current delay would exceed the total delay limit. let total_delay_check = self .total_delay .is_none_or(|total| self.cumulative_delay + tmp_cur <= total); if !total_delay_check { return None; } if self.total_delay.is_some() { self.cumulative_delay = self.cumulative_delay.saturating_add(tmp_cur); } self.current_delay = Some(current_delay); Some(tmp_cur) } } #[inline] pub(crate) fn saturating_mul(d: Duration, rhs: f32) -> Duration { Duration::try_from_secs_f32(rhs * d.as_secs_f32()).unwrap_or(Duration::MAX) } #[cfg(test)] mod tests { use core::time::Duration; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::wasm_bindgen_test as test; use crate::BackoffBuilder; use crate::ExponentialBuilder; const TEST_BUILDER: ExponentialBuilder = ExponentialBuilder::new() .with_jitter() .with_factor(1.5) .with_min_delay(Duration::from_secs(2)) .with_max_delay(Duration::from_secs(30)) .with_max_times(5); #[test] fn test_exponential_default() { let mut exp = ExponentialBuilder::default().build(); assert_eq!(Some(Duration::from_secs(1)), exp.next()); assert_eq!(Some(Duration::from_secs(2)), exp.next()); assert_eq!(Some(Duration::from_secs(4)), exp.next()); assert_eq!(None, exp.next()); } #[test] fn test_exponential_factor() { let mut exp = ExponentialBuilder::default().with_factor(1.5).build(); assert_eq!(Some(Duration::from_secs_f32(1.0)), exp.next()); assert_eq!(Some(Duration::from_secs_f32(1.5)), exp.next()); assert_eq!(Some(Duration::from_secs_f32(2.25)), exp.next()); assert_eq!(None, exp.next()); } #[test] fn test_exponential_jitter() { let mut exp = ExponentialBuilder::default().with_jitter().build(); let v = exp.next().expect("value must valid"); assert!(v >= Duration::from_secs(1), "current: {v:?}"); assert!(v < Duration::from_secs(2), "current: {v:?}"); let v = exp.next().expect("value must valid"); assert!(v >= Duration::from_secs(2), "current: {v:?}"); assert!(v < Duration::from_secs(4), "current: {v:?}"); let v = exp.next().expect("value must valid"); assert!(v >= Duration::from_secs(4), "current: {v:?}"); assert!(v < Duration::from_secs(8), "current: {v:?}"); assert_eq!(None, exp.next()); } #[test] fn test_exponential_min_delay() { let mut exp = ExponentialBuilder::default() .with_min_delay(Duration::from_millis(500)) .build(); assert_eq!(Some(Duration::from_millis(500)), exp.next()); assert_eq!(Some(Duration::from_secs(1)), exp.next()); assert_eq!(Some(Duration::from_secs(2)), exp.next()); assert_eq!(None, exp.next()); } #[test] fn test_exponential_total_delay() { let mut exp = ExponentialBuilder::default() .with_min_delay(Duration::from_secs(1)) .with_factor(1.0) .with_total_delay(Some(Duration::from_secs(3))) .with_max_times(5) .build(); assert_eq!(Some(Duration::from_secs(1)), exp.next()); assert_eq!(Some(Duration::from_secs(1)), exp.next()); assert_eq!(Some(Duration::from_secs(1)), exp.next()); assert_eq!(None, exp.next()); } #[test] fn test_exponential_no_max_times_with_default() { let mut exp = ExponentialBuilder::default() .with_min_delay(Duration::from_secs(1)) .with_factor(1_f32) .without_max_times() .build(); // to fully test we would need to call this `usize::MAX` // which seems unreasonable for a test as it would take too long... for _ in 0..10_000 { assert_eq!(Some(Duration::from_secs(1)), exp.next()); } } #[test] fn test_exponential_max_delay_with_default() { let mut exp = ExponentialBuilder::default() .with_max_delay(Duration::from_secs(2)) .build(); assert_eq!(Some(Duration::from_secs(1)), exp.next()); assert_eq!(Some(Duration::from_secs(2)), exp.next()); assert_eq!(Some(Duration::from_secs(2)), exp.next()); assert_eq!(None, exp.next()); } #[test] fn test_exponential_no_max_delay_with_default() { let mut exp = ExponentialBuilder::default() .with_min_delay(Duration::from_secs(1)) .with_factor(10_000_000_000_f32) .without_max_delay() .with_max_times(4) .build(); assert_eq!(Some(Duration::from_secs(1)), exp.next()); assert_eq!(Some(Duration::from_secs(10_000_000_000)), exp.next()); assert_eq!(Some(Duration::MAX), exp.next()); assert_eq!(Some(Duration::MAX), exp.next()); assert_eq!(None, exp.next()); } #[test] fn test_exponential_max_delay_without_default_1() { let mut exp = ExponentialBuilder { jitter: false, seed: Some(0x2fdb0020ffc7722b), factor: 10_000_000_000_f32, min_delay: Duration::from_secs(1), max_delay: None, max_times: None, total_delay: None, } .build(); assert_eq!(Some(Duration::from_secs(1)), exp.next()); assert_eq!(Some(Duration::from_secs(10_000_000_000)), exp.next()); assert_eq!(Some(Duration::MAX), exp.next()); assert_eq!(Some(Duration::MAX), exp.next()); } #[test] fn test_exponential_max_delay_without_default_2() { let mut exp = ExponentialBuilder { jitter: true, seed: Some(0x2fdb0020ffc7722b), factor: 10_000_000_000_f32, min_delay: Duration::from_secs(10_000_000_000), max_delay: None, max_times: Some(2), total_delay: None, } .build(); let v = exp.next().expect("value must valid"); assert!(v >= Duration::from_secs(10_000_000_000), "current: {v:?}"); assert!(v < Duration::from_secs(20_000_000_000), "current: {v:?}"); assert_eq!(Some(Duration::MAX), exp.next()); assert_eq!(None, exp.next()); } #[test] fn test_exponential_max_delay_without_default_3() { let mut exp = ExponentialBuilder { jitter: false, seed: Some(0x2fdb0020ffc7722b), factor: 10_000_000_000_f32, min_delay: Duration::from_secs(10_000_000_000), max_delay: Some(Duration::from_secs(60_000_000_000)), max_times: Some(3), total_delay: None, } .build(); assert_eq!(Some(Duration::from_secs(10_000_000_000)), exp.next()); assert_eq!(Some(Duration::from_secs(60_000_000_000)), exp.next()); assert_eq!(Some(Duration::from_secs(60_000_000_000)), exp.next()); assert_eq!(None, exp.next()); } #[test] fn test_exponential_max_times() { let mut exp = ExponentialBuilder::default().with_max_times(1).build(); assert_eq!(Some(Duration::from_secs(1)), exp.next()); assert_eq!(None, exp.next()); } // allow assertions on constants because they are not optimized out by unit tests #[allow(clippy::assertions_on_constants)] #[test] fn test_exponential_const_builder() { assert!(TEST_BUILDER.jitter); assert_eq!(TEST_BUILDER.factor, 1.5); assert_eq!(TEST_BUILDER.min_delay, Duration::from_secs(2)); assert_eq!(TEST_BUILDER.max_delay, Some(Duration::from_secs(30))); assert_eq!(TEST_BUILDER.max_times, Some(5)); } } backon-1.6.0/src/backoff/fibonacci.rs000064400000000000000000000245011046102023000155340ustar 00000000000000use core::time::Duration; use crate::backoff::BackoffBuilder; /// FibonacciBuilder is used to build a [`FibonacciBackoff`] which offers a delay with Fibonacci-based retries. /// /// # Default /// /// - jitter: false /// - min_delay: 1s /// - max_delay: 60s /// - max_times: 3 /// /// # Examples /// /// ```no_run /// use anyhow::Result; /// use backon::FibonacciBuilder; /// use backon::Retryable; /// /// async fn fetch() -> Result { /// Ok(reqwest::get("https://www.rust-lang.org") /// .await? /// .text() /// .await?) /// } /// /// #[tokio::main(flavor = "current_thread")] /// async fn main() -> Result<()> { /// let content = fetch.retry(FibonacciBuilder::default()).await?; /// println!("fetch succeeded: {}", content); /// /// Ok(()) /// } /// ``` #[derive(Debug, Clone, Copy)] pub struct FibonacciBuilder { jitter: bool, seed: Option, min_delay: Duration, max_delay: Option, max_times: Option, } impl Default for FibonacciBuilder { fn default() -> Self { Self::new() } } impl FibonacciBuilder { /// Create a new `FibonacciBuilder` with default values. pub const fn new() -> Self { Self { jitter: false, seed: None, min_delay: Duration::from_secs(1), max_delay: Some(Duration::from_secs(60)), max_times: Some(3), } } /// Set the jitter for the backoff. /// /// When jitter is enabled, FibonacciBackoff will add a random jitter between `(0, current_delay)` to the delay. pub const fn with_jitter(mut self) -> Self { self.jitter = true; self } /// Set the seed value for the jitter random number generator. If no seed is given, a random seed is used in std and default seed is used in no_std. pub fn with_jitter_seed(mut self, seed: u64) -> Self { self.seed = Some(seed); self } /// Set the minimum delay for the backoff. pub const fn with_min_delay(mut self, min_delay: Duration) -> Self { self.min_delay = min_delay; self } /// Set the maximum delay for the current backoff. /// /// The delay will not increase if the current delay exceeds the maximum delay. pub const fn with_max_delay(mut self, max_delay: Duration) -> Self { self.max_delay = Some(max_delay); self } /// Set no maximum delay for the backoff. /// /// The delay will keep increasing. /// /// _The delay will saturate at `Duration::MAX` which is an **unrealistic** delay._ pub const fn without_max_delay(mut self) -> Self { self.max_delay = None; self } /// Set the maximum number of attempts for the current backoff. /// /// The backoff will stop if the maximum number of attempts is reached. pub const fn with_max_times(mut self, max_times: usize) -> Self { self.max_times = Some(max_times); self } /// Set no maximum number of attempts for the current backoff. /// /// The backoff will not stop by itself. /// /// _The backoff could stop reaching `usize::MAX` attempts but this is **unrealistic**._ pub const fn without_max_times(mut self) -> Self { self.max_times = None; self } } impl BackoffBuilder for FibonacciBuilder { type Backoff = FibonacciBackoff; fn build(self) -> Self::Backoff { FibonacciBackoff { jitter: self.jitter, rng: if let Some(seed) = self.seed { fastrand::Rng::with_seed(seed) } else { #[cfg(feature = "std")] let rng = fastrand::Rng::new(); #[cfg(not(feature = "std"))] let rng = fastrand::Rng::with_seed(super::RANDOM_SEED); rng }, min_delay: self.min_delay, max_delay: self.max_delay, max_times: self.max_times, previous_delay: None, current_delay: None, attempts: 0, } } } impl BackoffBuilder for &FibonacciBuilder { type Backoff = FibonacciBackoff; fn build(self) -> Self::Backoff { (*self).build() } } /// FibonacciBackoff offers a delay with Fibonacci-based retries. /// /// This backoff strategy is constructed by [`FibonacciBuilder`]. #[doc(hidden)] #[derive(Debug)] pub struct FibonacciBackoff { jitter: bool, rng: fastrand::Rng, min_delay: Duration, max_delay: Option, max_times: Option, previous_delay: Option, current_delay: Option, attempts: usize, } impl Iterator for FibonacciBackoff { type Item = Duration; fn next(&mut self) -> Option { if self.attempts >= self.max_times.unwrap_or(usize::MAX) { return None; } self.attempts += 1; match self.current_delay { None => { // If current_delay is None, it's must be the first time to retry. let mut next = self.min_delay; self.current_delay = Some(next); // If jitter is enabled, add random jitter based on min delay. if self.jitter { next += next.mul_f32(self.rng.f32()); } Some(next) } Some(cur) => { let mut next = cur; // If current delay larger than max delay, we should stop increment anymore. if next < self.max_delay.unwrap_or(Duration::MAX) { if let Some(prev) = self.previous_delay { next = next.saturating_add(prev); self.current_delay = Some(next); } self.previous_delay = Some(cur); } // If jitter is enabled, add random jitter based on min delay. if self.jitter { next += self.min_delay.mul_f32(self.rng.f32()); } Some(next) } } } } #[cfg(test)] mod tests { use core::time::Duration; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; const TEST_BUILDER: FibonacciBuilder = FibonacciBuilder::new() .with_jitter() .with_min_delay(Duration::from_secs(2)) .with_max_delay(Duration::from_secs(30)) .with_max_times(5); #[test] fn test_fibonacci_default() { let mut fib = FibonacciBuilder::default().build(); assert_eq!(Some(Duration::from_secs(1)), fib.next()); assert_eq!(Some(Duration::from_secs(1)), fib.next()); assert_eq!(Some(Duration::from_secs(2)), fib.next()); assert_eq!(None, fib.next()); } #[test] fn test_fibonacci_jitter() { let mut fib = FibonacciBuilder::default().with_jitter().build(); let v = fib.next().expect("value must valid"); assert!(v >= Duration::from_secs(1), "current: {v:?}"); assert!(v < Duration::from_secs(2), "current: {v:?}"); let v = fib.next().expect("value must valid"); assert!(v >= Duration::from_secs(1), "current: {v:?}"); assert!(v < Duration::from_secs(2), "current: {v:?}"); let v = fib.next().expect("value must valid"); assert!(v >= Duration::from_secs(2), "current: {v:?}"); assert!(v < Duration::from_secs(3), "current: {v:?}"); assert_eq!(None, fib.next()); } #[test] fn test_fibonacci_min_delay() { let mut fib = FibonacciBuilder::default() .with_min_delay(Duration::from_millis(500)) .build(); assert_eq!(Some(Duration::from_millis(500)), fib.next()); assert_eq!(Some(Duration::from_millis(500)), fib.next()); assert_eq!(Some(Duration::from_secs(1)), fib.next()); assert_eq!(None, fib.next()); } #[test] fn test_fibonacci_max_delay() { let mut fib = FibonacciBuilder::default() .with_max_times(4) .with_max_delay(Duration::from_secs(2)) .build(); assert_eq!(Some(Duration::from_secs(1)), fib.next()); assert_eq!(Some(Duration::from_secs(1)), fib.next()); assert_eq!(Some(Duration::from_secs(2)), fib.next()); assert_eq!(Some(Duration::from_secs(2)), fib.next()); assert_eq!(None, fib.next()); } #[test] fn test_fibonacci_no_max_delay() { let mut fib = FibonacciBuilder::default() .with_max_times(4) .with_min_delay(Duration::from_secs(10_000_000_000_000_000_000)) .without_max_delay() .build(); assert_eq!( Some(Duration::from_secs(10_000_000_000_000_000_000)), fib.next() ); assert_eq!( Some(Duration::from_secs(10_000_000_000_000_000_000)), fib.next() ); assert_eq!(Some(Duration::MAX), fib.next()); assert_eq!(Some(Duration::MAX), fib.next()); assert_eq!(None, fib.next()); } #[test] fn test_fibonacci_max_times() { let mut fib = FibonacciBuilder::default().with_max_times(6).build(); assert_eq!(Some(Duration::from_secs(1)), fib.next()); assert_eq!(Some(Duration::from_secs(1)), fib.next()); assert_eq!(Some(Duration::from_secs(2)), fib.next()); assert_eq!(Some(Duration::from_secs(3)), fib.next()); assert_eq!(Some(Duration::from_secs(5)), fib.next()); assert_eq!(Some(Duration::from_secs(8)), fib.next()); assert_eq!(None, fib.next()); } #[test] fn test_fibonacci_no_max_times() { let mut fib = FibonacciBuilder::default() .with_min_delay(Duration::from_secs(0)) .without_max_times() .build(); // to fully test we would need to call this `usize::MAX` // which seems unreasonable for a test as it would take too long... for _ in 0..10_000 { assert_eq!(Some(Duration::from_secs(0)), fib.next()); } } // allow assertions on constants because they are not optimized out by unit tests #[allow(clippy::assertions_on_constants)] #[test] fn test_fibonacci_const_builder() { assert!(TEST_BUILDER.jitter); assert_eq!(TEST_BUILDER.min_delay, Duration::from_secs(2)); assert_eq!(TEST_BUILDER.max_delay, Some(Duration::from_secs(30))); assert_eq!(TEST_BUILDER.max_times, Some(5)); } } backon-1.6.0/src/backoff/mod.rs000064400000000000000000000006621046102023000144000ustar 00000000000000mod api; pub use api::*; mod constant; pub use constant::ConstantBackoff; pub use constant::ConstantBuilder; mod fibonacci; pub use fibonacci::FibonacciBackoff; pub use fibonacci::FibonacciBuilder; mod exponential; pub use exponential::ExponentialBackoff; pub use exponential::ExponentialBuilder; // Random seed value for no_std (the value is "backon" in hex) #[cfg(not(feature = "std"))] const RANDOM_SEED: u64 = 0x6261636b6f6e; backon-1.6.0/src/blocking_retry.rs000064400000000000000000000234131046102023000152420ustar 00000000000000use core::time::Duration; use crate::Backoff; use crate::BlockingSleeper; use crate::DefaultBlockingSleeper; use crate::backoff::BackoffBuilder; use crate::blocking_sleep::MaybeBlockingSleeper; /// BlockingRetryable adds retry support for blocking functions. /// /// For example: /// /// - Functions without extra args: /// /// ```ignore /// fn fetch() -> Result { /// Ok("hello, world!".to_string()) /// } /// ``` /// /// - Closures /// /// ```ignore /// || { /// Ok("hello, world!".to_string()) /// } /// ``` /// /// # Example /// /// ```no_run /// use anyhow::Result; /// use backon::BlockingRetryable; /// use backon::ExponentialBuilder; /// /// fn fetch() -> Result { /// Ok("hello, world!".to_string()) /// } /// /// fn main() -> Result<()> { /// let content = fetch.retry(ExponentialBuilder::default()).call()?; /// println!("fetch succeeded: {}", content); /// /// Ok(()) /// } /// ``` pub trait BlockingRetryable Result> { /// Generate a new retry. fn retry(self, builder: B) -> BlockingRetry; } impl BlockingRetryable for F where B: BackoffBuilder, F: FnMut() -> Result, { fn retry(self, builder: B) -> BlockingRetry { BlockingRetry::new(self, builder.build()) } } /// Retry structure generated by [`BlockingRetryable`]. pub struct BlockingRetry< B: Backoff, T, E, F: FnMut() -> Result, SF: MaybeBlockingSleeper = DefaultBlockingSleeper, RF = fn(&E) -> bool, NF = fn(&E, Duration), > { backoff: B, retryable: RF, notify: NF, f: F, sleep_fn: SF, } impl BlockingRetry where B: Backoff, F: FnMut() -> Result, { /// Create a new retry. fn new(f: F, backoff: B) -> Self { BlockingRetry { backoff, retryable: |_: &E| true, notify: |_: &E, _: Duration| {}, sleep_fn: DefaultBlockingSleeper::default(), f, } } } impl BlockingRetry where B: Backoff, F: FnMut() -> Result, SF: MaybeBlockingSleeper, RF: FnMut(&E) -> bool, NF: FnMut(&E, Duration), { /// Set the sleeper for retrying. /// /// The sleeper should implement the [`BlockingSleeper`] trait. The simplest way is to use a closure like `Fn(Duration)`. /// /// If not specified, we use the [`DefaultBlockingSleeper`]. /// /// # Examples /// /// ```no_run /// use anyhow::Result; /// use backon::BlockingRetryable; /// use backon::ExponentialBuilder; /// /// fn fetch() -> Result { /// Ok("hello, world!".to_string()) /// } /// /// fn main() -> Result<()> { /// let retry = fetch /// .retry(ExponentialBuilder::default()) /// .sleep(std::thread::sleep); /// let content = retry.call()?; /// println!("fetch succeeded: {}", content); /// /// Ok(()) /// } /// ``` pub fn sleep(self, sleep_fn: SN) -> BlockingRetry { BlockingRetry { backoff: self.backoff, retryable: self.retryable, notify: self.notify, f: self.f, sleep_fn, } } /// Set the conditions for retrying. /// /// If not specified, all errors are considered retryable. /// /// # Examples /// /// ```no_run /// use anyhow::Result; /// use backon::BlockingRetryable; /// use backon::ExponentialBuilder; /// /// fn fetch() -> Result { /// Ok("hello, world!".to_string()) /// } /// /// fn main() -> Result<()> { /// let retry = fetch /// .retry(ExponentialBuilder::default()) /// .when(|e| e.to_string() == "EOF"); /// let content = retry.call()?; /// println!("fetch succeeded: {}", content); /// /// Ok(()) /// } /// ``` pub fn when bool>( self, retryable: RN, ) -> BlockingRetry { BlockingRetry { backoff: self.backoff, retryable, notify: self.notify, f: self.f, sleep_fn: self.sleep_fn, } } /// Set to notify for all retry attempts. /// /// When a retry happens, the input function will be invoked with the error and the sleep duration before pausing. /// /// If not specified, this operation does nothing. /// /// # Examples /// /// ```no_run /// use core::time::Duration; /// /// use anyhow::Result; /// use backon::BlockingRetryable; /// use backon::ExponentialBuilder; /// /// fn fetch() -> Result { /// Ok("hello, world!".to_string()) /// } /// /// fn main() -> Result<()> { /// let retry = fetch.retry(ExponentialBuilder::default()).notify( /// |err: &anyhow::Error, dur: Duration| { /// println!("retrying error {:?} with sleeping {:?}", err, dur); /// }, /// ); /// let content = retry.call()?; /// println!("fetch succeeded: {}", content); /// /// Ok(()) /// } /// ``` pub fn notify( self, notify: NN, ) -> BlockingRetry { BlockingRetry { backoff: self.backoff, retryable: self.retryable, notify, f: self.f, sleep_fn: self.sleep_fn, } } } impl BlockingRetry where B: Backoff, F: FnMut() -> Result, SF: BlockingSleeper, RF: FnMut(&E) -> bool, NF: FnMut(&E, Duration), { /// Call the retried function. /// /// TODO: implement [`FnOnce`] after it stable. pub fn call(mut self) -> Result { loop { let result = (self.f)(); match result { Ok(v) => return Ok(v), Err(err) => { if !(self.retryable)(&err) { return Err(err); } match self.backoff.next() { None => return Err(err), Some(dur) => { (self.notify)(&err, dur); self.sleep_fn.sleep(dur); } } } } } } } #[cfg(test)] mod tests { extern crate alloc; use alloc::string::ToString; use alloc::vec; use alloc::vec::Vec; use core::time::Duration; use spin::Mutex; use super::*; use crate::ExponentialBuilder; fn always_error() -> anyhow::Result<()> { Err(anyhow::anyhow!("test_query meets error")) } #[test] fn test_retry() -> anyhow::Result<()> { let result = always_error .retry(ExponentialBuilder::default().with_min_delay(Duration::from_millis(1))) .call(); assert!(result.is_err()); assert_eq!("test_query meets error", result.unwrap_err().to_string()); Ok(()) } #[test] fn test_retry_with_not_retryable_error() -> anyhow::Result<()> { let error_times = Mutex::new(0); let f = || { let mut x = error_times.lock(); *x += 1; Err::<(), anyhow::Error>(anyhow::anyhow!("not retryable")) }; let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1)); let result = f .retry(backoff) // Only retry If error message is `retryable` .when(|e| e.to_string() == "retryable") .call(); assert!(result.is_err()); assert_eq!("not retryable", result.unwrap_err().to_string()); // `f` always returns error "not retryable", so it should be executed // only once. assert_eq!(*error_times.lock(), 1); Ok(()) } #[test] fn test_retry_with_retryable_error() -> anyhow::Result<()> { let error_times = Mutex::new(0); let f = || { // println!("I have been called!"); let mut x = error_times.lock(); *x += 1; Err::<(), anyhow::Error>(anyhow::anyhow!("retryable")) }; let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1)); let result = f .retry(backoff) // Only retry If error message is `retryable` .when(|e| e.to_string() == "retryable") .call(); assert!(result.is_err()); assert_eq!("retryable", result.unwrap_err().to_string()); // `f` always returns error "retryable", so it should be executed // 4 times (retry 3 times). assert_eq!(*error_times.lock(), 4); Ok(()) } #[test] fn test_fn_mut_when_and_notify() -> anyhow::Result<()> { let mut calls_retryable: Vec<()> = vec![]; let mut calls_notify: Vec<()> = vec![]; let f = || Err::<(), anyhow::Error>(anyhow::anyhow!("retryable")); let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1)); let result = f .retry(backoff) .when(|_| { calls_retryable.push(()); true }) .notify(|_, _| { calls_notify.push(()); }) .call(); assert!(result.is_err()); assert_eq!("retryable", result.unwrap_err().to_string()); // `f` always returns error "retryable", so it should be executed // 4 times (retry 3 times). assert_eq!(calls_retryable.len(), 4); assert_eq!(calls_notify.len(), 3); Ok(()) } } backon-1.6.0/src/blocking_retry_with_context.rs000064400000000000000000000146011046102023000200400ustar 00000000000000use core::time::Duration; use crate::Backoff; use crate::BlockingSleeper; use crate::DefaultBlockingSleeper; use crate::backoff::BackoffBuilder; use crate::blocking_sleep::MaybeBlockingSleeper; /// BlockingRetryableWithContext adds retry support for blocking functions. pub trait BlockingRetryableWithContext< B: BackoffBuilder, T, E, Ctx, F: FnMut(Ctx) -> (Ctx, Result), > { /// Generate a new retry fn retry(self, builder: B) -> BlockingRetryWithContext; } impl BlockingRetryableWithContext for F where B: BackoffBuilder, F: FnMut(Ctx) -> (Ctx, Result), { fn retry(self, builder: B) -> BlockingRetryWithContext { BlockingRetryWithContext::new(self, builder.build()) } } /// Retry structure generated by [`BlockingRetryableWithContext`]. pub struct BlockingRetryWithContext< B: Backoff, T, E, Ctx, F: FnMut(Ctx) -> (Ctx, Result), SF: MaybeBlockingSleeper = DefaultBlockingSleeper, RF = fn(&E) -> bool, NF = fn(&E, Duration), > { backoff: B, retryable: RF, notify: NF, f: F, sleep_fn: SF, ctx: Option, } impl BlockingRetryWithContext where B: Backoff, F: FnMut(Ctx) -> (Ctx, Result), { /// Create a new retry. fn new(f: F, backoff: B) -> Self { BlockingRetryWithContext { backoff, retryable: |_: &E| true, notify: |_: &E, _: Duration| {}, sleep_fn: DefaultBlockingSleeper::default(), f, ctx: None, } } } impl BlockingRetryWithContext where B: Backoff, F: FnMut(Ctx) -> (Ctx, Result), SF: MaybeBlockingSleeper, RF: FnMut(&E) -> bool, NF: FnMut(&E, Duration), { /// Set the context for retrying. /// /// Context is used to capture ownership manually to prevent lifetime issues. pub fn context(self, context: Ctx) -> BlockingRetryWithContext { BlockingRetryWithContext { backoff: self.backoff, retryable: self.retryable, notify: self.notify, f: self.f, sleep_fn: self.sleep_fn, ctx: Some(context), } } /// Set the sleeper for retrying. /// /// The sleeper should implement the [`BlockingSleeper`] trait. The simplest way is to use a closure like `Fn(Duration)`. /// /// If not specified, we use the [`DefaultBlockingSleeper`]. pub fn sleep( self, sleep_fn: SN, ) -> BlockingRetryWithContext { BlockingRetryWithContext { backoff: self.backoff, retryable: self.retryable, notify: self.notify, f: self.f, sleep_fn, ctx: self.ctx, } } /// Set the conditions for retrying. /// /// If not specified, all errors are considered retryable. pub fn when bool>( self, retryable: RN, ) -> BlockingRetryWithContext { BlockingRetryWithContext { backoff: self.backoff, retryable, notify: self.notify, f: self.f, sleep_fn: self.sleep_fn, ctx: self.ctx, } } /// Set to notify for all retry attempts. /// /// When a retry happens, the input function will be invoked with the error and the sleep duration before pausing. /// /// If not specified, this operation does nothing. pub fn notify( self, notify: NN, ) -> BlockingRetryWithContext { BlockingRetryWithContext { backoff: self.backoff, retryable: self.retryable, notify, f: self.f, sleep_fn: self.sleep_fn, ctx: self.ctx, } } } impl BlockingRetryWithContext where B: Backoff, F: FnMut(Ctx) -> (Ctx, Result), SF: BlockingSleeper, RF: FnMut(&E) -> bool, NF: FnMut(&E, Duration), { /// Call the retried function. /// /// TODO: implement [`FnOnce`] after it stable. pub fn call(mut self) -> (Ctx, Result) { let mut ctx = self.ctx.take().expect("context must be valid"); loop { let (xctx, result) = (self.f)(ctx); // return ctx ownership back ctx = xctx; match result { Ok(v) => return (ctx, Ok(v)), Err(err) => { if !(self.retryable)(&err) { return (ctx, Err(err)); } match self.backoff.next() { None => return (ctx, Err(err)), Some(dur) => { (self.notify)(&err, dur); self.sleep_fn.sleep(dur); } } } } } } } #[cfg(test)] mod tests { extern crate alloc; use alloc::string::ToString; use core::time::Duration; use anyhow::Result; use anyhow::anyhow; use spin::Mutex; use super::*; use crate::ExponentialBuilder; struct Test; impl Test { fn hello(&mut self) -> Result { Err(anyhow!("not retryable")) } } #[test] fn test_retry_with_not_retryable_error() -> Result<()> { let error_times = Mutex::new(0); let test = Test; let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1)); let (_, result) = { |mut v: Test| { let mut x = error_times.lock(); *x += 1; let res = v.hello(); (v, res) } } .retry(backoff) .context(test) // Only retry If error message is `retryable` .when(|e| e.to_string() == "retryable") .call(); assert!(result.is_err()); assert_eq!("not retryable", result.unwrap_err().to_string()); // `f` always returns error "not retryable", so it should be executed // only once. assert_eq!(*error_times.lock(), 1); Ok(()) } } backon-1.6.0/src/blocking_sleep.rs000064400000000000000000000043661046102023000152130ustar 00000000000000use core::time::Duration; /// A sleeper is used sleep for a specified duration. pub trait BlockingSleeper: 'static { /// sleep for a specified duration. fn sleep(&self, dur: Duration); } /// A stub trait allowing non-[`BlockingSleeper`] types to be used as a generic parameter in [`BlockingRetry`][crate::BlockingRetry]. /// It does not provide actual functionality. #[doc(hidden)] pub trait MaybeBlockingSleeper: 'static {} /// All `BlockingSleeper` will implement `MaybeBlockingSleeper`, but not vice versa. impl MaybeBlockingSleeper for T {} /// All `Fn(Duration)` implements `Sleeper`. impl BlockingSleeper for F { fn sleep(&self, dur: Duration) { self(dur) } } /// The default implementation of `Sleeper` when no features are enabled. /// /// It will fail to compile if a containing [`Retry`][crate::Retry] is `.await`ed without calling [`Retry::sleep`][crate::Retry::sleep] to provide a valid sleeper. #[cfg(not(feature = "std-blocking-sleep"))] pub type DefaultBlockingSleeper = PleaseEnableAFeatureOrProvideACustomSleeper; /// The default implementation of `Sleeper` while feature `std-blocking-sleep` enabled. /// /// it uses [`std::thread::sleep`]. #[cfg(feature = "std-blocking-sleep")] pub type DefaultBlockingSleeper = StdSleeper; /// A placeholder type that does not implement [`Sleeper`] and will therefore fail to compile if used as one. /// /// Users should enable a feature of this crate that provides a valid [`Sleeper`] implementation when this type appears in compilation errors. Alternatively, a custom [`Sleeper`] implementation should be provided where necessary, such as in [`crate::Retry::sleeper`]. #[doc(hidden)] #[allow(dead_code)] #[derive(Clone, Copy, Debug, Default)] pub struct PleaseEnableAFeatureOrProvideACustomSleeper; /// Implement `MaybeSleeper` but not `Sleeper`. impl MaybeBlockingSleeper for PleaseEnableAFeatureOrProvideACustomSleeper {} /// The implementation of `StdSleeper` uses [`std::thread::sleep`]. #[cfg(feature = "std-blocking-sleep")] #[derive(Clone, Copy, Debug, Default)] pub struct StdSleeper; #[cfg(feature = "std-blocking-sleep")] impl BlockingSleeper for StdSleeper { fn sleep(&self, dur: Duration) { std::thread::sleep(dur) } } backon-1.6.0/src/docs/examples/basic.md000064400000000000000000000005561046102023000160330ustar 00000000000000Retry an async function. ```rust use backon::ExponentialBuilder; use backon::Retryable; use anyhow::Result; async fn fetch() -> Result { Ok("Hello, World!".to_string()) } #[tokio::main] async fn main() -> Result<()> { let content = fetch.retry(ExponentialBuilder::default()).await?; println!("fetch succeeded: {}", content); Ok(()) } ``` backon-1.6.0/src/docs/examples/closure.md000064400000000000000000000005621046102023000164230ustar 00000000000000Retry an closure. ```rust use backon::ExponentialBuilder; use backon::Retryable; use backon::BlockingRetryable; fn main() -> anyhow::Result<()> { let var = 42; // `f` can use input variables let f = || Ok::(var); let result = f.retry(backon::ExponentialBuilder::default()).call()?; println!("var = {result}"); Ok(()) } ``` backon-1.6.0/src/docs/examples/custom_sleeper.md000064400000000000000000000017161046102023000200020ustar 00000000000000Let's implement a custom async Sleeper, say you are using Monoio as your async runtime, you may want to implement it with `monoio::time::sleep()`. If you want to implement a custom blocking Sleeper, you will find it pretty similar. ```rust use std::time::Duration; use backon::Sleeper; /// Sleeper implemented using `monoio::time::sleep()`. struct MonoioSleeper; impl Sleeper for MonoioSleeper { type Sleep = monoio::time::Sleep; fn sleep(&self, dur: Duration) -> Self::Sleep { monoio::time::sleep(dur) } } ``` Then you can use it like: ```rust use backon::ExponentialBuilder; use backon::Retryable; use anyhow::Result; async fn fetch() -> Result { Ok("Hello, World!".to_string()) } #[monoio::main(timer_enabled = true)] async fn main() -> Result<()> { let content = fetch .retry(ExponentialBuilder::default()) .sleep(MonoioSleeper) .await?; println!("fetch succeeded: {}", content); Ok(()) } ```backon-1.6.0/src/docs/examples/inside_mut_self.md000064400000000000000000000011061046102023000201130ustar 00000000000000Retry an async function inside `&mut self` functions. ```rust use anyhow::Result; use backon::ExponentialBuilder; use backon::Retryable; struct Test; impl Test { async fn fetch(&self, url: &str) -> Result { Ok(reqwest::get(url).await?.text().await?) } async fn run(&mut self) -> Result { let content = (|| async { self.fetch("https://www.rust-lang.org").await }) .retry(ExponentialBuilder::default()) .when(|e| e.to_string() == "retryable") .await?; Ok(content) } } ``` backon-1.6.0/src/docs/examples/mod.rs000064400000000000000000000011201046102023000155410ustar 00000000000000//! Examples of using backon. #[doc = include_str!("basic.md")] pub mod basic {} #[doc = include_str!("closure.md")] pub mod closure {} #[doc = include_str!("inside_mut_self.md")] pub mod inside_mut_self {} #[doc = include_str!("sqlx.md")] pub mod sqlx {} #[doc = include_str!("with_args.md")] pub mod with_args {} #[doc = include_str!("with_mut_self.md")] pub mod with_mut_self {} #[doc = include_str!("with_self.md")] pub mod with_self {} #[doc = include_str!("with_specific_error.md")] pub mod with_specific_error {} #[doc = include_str!("retry_after.md")] pub mod retry_after {} backon-1.6.0/src/docs/examples/retry_after.md000064400000000000000000000032641046102023000172770ustar 00000000000000Retry an async function with the `Retry-After` headers. ```no_run use core::time::Duration; use std::error::Error; use std::fmt::Display; use std::fmt::Formatter; use anyhow::Result; use backon::ExponentialBuilder; use backon::Retryable; use reqwest::header::HeaderMap; use reqwest::StatusCode; #[derive(Debug)] struct HttpError { headers: HeaderMap, } impl Display for HttpError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "http error") } } impl Error for HttpError {} async fn fetch() -> Result { let resp = reqwest::get("https://www.rust-lang.org").await?; if resp.status() != StatusCode::OK { let source = HttpError { headers: resp.headers().clone(), }; return Err(anyhow::Error::new(source)); } Ok(resp.text().await?) } #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { let content = fetch .retry(ExponentialBuilder::default()) .adjust(|err, dur| { match err.downcast_ref::() { Some(v) => { if let Some(retry_after) = v.headers.get("Retry-After") { // Parse the Retry-After header and adjust the backoff duration let retry_after = retry_after.to_str().unwrap_or("0"); let retry_after = retry_after.parse::().unwrap_or(0); Some(Duration::from_secs(retry_after)) } else { dur } } None => dur, } }) .await?; println!("fetch succeeded: {}", content); Ok(()) } ``` backon-1.6.0/src/docs/examples/sqlx.md000064400000000000000000000007471046102023000157430ustar 00000000000000Retry sqlx operations. ```rust use backon::Retryable; use anyhow::Result; use backon::ExponentialBuilder; #[tokio::main] async fn main() -> Result<()> { let pool = sqlx::sqlite::SqlitePoolOptions::new() .max_connections(5) .connect("sqlite::memory:") .await?; let row: (i64,) = (|| sqlx::query_as("SELECT $1").bind(150_i64).fetch_one(&pool)) .retry(ExponentialBuilder::default()) .await?; assert_eq!(row.0, 150); Ok(()) } ``` backon-1.6.0/src/docs/examples/wasm32_basic.md000064400000000000000000000013171046102023000172230ustar 00000000000000Retry an async function in a wasm32 environment using `backon` for exponential backoff, and `wasm-bindgen` + `spawn_local` to run async code in the browser. ```rust use anyhow::Result; use backon::{ExponentialBuilder, Retryable}; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::spawn_local; async fn fetch() -> Result { Ok("Hello, wasm32!".to_string()) } #[wasm_bindgen(start)] fn start() { spawn_local(async { match fetch.retry(ExponentialBuilder::default()).await { Ok(content) => web_sys::console::log_1(&format!("fetch succeeded: {}", content).into()), Err(e) => web_sys::console::error_1(&format!("fetch failed: {:?}", e).into()), } }); } ```backon-1.6.0/src/docs/examples/with_args.md000064400000000000000000000012601046102023000167320ustar 00000000000000Retry function with args. It's a pity that rust doesn't allow us to implement `Retryable` for async function with args. So we have to use a workaround to make it work. ```rust use anyhow::Result; use backon::ExponentialBuilder; use backon::Retryable; async fn fetch(url: &str) -> Result { Ok(reqwest::get(url).await?.text().await?) } #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { let content = (|| async { fetch("https://www.rust-lang.org").await }) .retry(ExponentialBuilder::default()) .when(|e| e.to_string() == "retryable") .await?; println!("fetch succeeded: {}", content); Ok(()) } ``` backon-1.6.0/src/docs/examples/with_mut_self.md000064400000000000000000000017041046102023000176170ustar 00000000000000Retry an async function which takes `&mut self` as receiver. This is a bit more complex since we need to capture the receiver in the closure with ownership. backon supports this use case by `RetryableWithContext`. ```rust use anyhow::Result; use backon::ExponentialBuilder; use backon::RetryableWithContext; struct Test; impl Test { async fn fetch(&mut self, url: &str) -> Result { Ok(reqwest::get(url).await?.text().await?) } } #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { let test = Test; let (_, result) = (|mut v: Test| async { let res = v.fetch("https://www.rust-lang.org").await; // Return input context back. (v, res) }) .retry(ExponentialBuilder::default()) // Passing context in. .context(test) .when(|e| e.to_string() == "retryable") .await; println!("fetch succeeded: {}", result.unwrap()); Ok(()) } ``` backon-1.6.0/src/docs/examples/with_self.md000064400000000000000000000012161046102023000167300ustar 00000000000000Retry an async function which takes `&self` as receiver. ```rust use anyhow::Result; use backon::ExponentialBuilder; use backon::Retryable; struct Test; impl Test { async fn fetch(&self, url: &str) -> Result { Ok(reqwest::get(url).await?.text().await?) } } #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { let test = Test; let content = (|| async { test.fetch("https://www.rust-lang.org").await }) .retry(ExponentialBuilder::default()) .when(|e| e.to_string() == "retryable") .await?; println!("fetch succeeded: {}", content); Ok(()) } ``` backon-1.6.0/src/docs/examples/with_specific_error.md000064400000000000000000000007041046102023000207760ustar 00000000000000Retry with specify retryable error by `when`. ```rust use anyhow::Result; use backon::ExponentialBuilder; use backon::Retryable; async fn fetch() -> Result { Ok("Hello, World!".to_string()) } #[tokio::main] async fn main() -> Result<()> { let content = fetch .retry(ExponentialBuilder::default()) .when(|e| e.to_string() == "retryable") .await?; println!("fetch succeeded: {}", content); Ok(()) } ``` backon-1.6.0/src/docs/mod.rs000064400000000000000000000001051046102023000137250ustar 00000000000000//! Docs for the backon crate, like [`examples`]. pub mod examples; backon-1.6.0/src/embassy_timer_sleep.rs000064400000000000000000000011341046102023000162540ustar 00000000000000use core::time::Duration; use crate::BlockingSleeper; use crate::Sleeper; /// A no_std async sleeper based on the embassy framework (https://embassy.dev) #[derive(Clone, Copy, Debug, Default)] pub struct EmbassySleeper; impl Sleeper for EmbassySleeper { type Sleep = embassy_time::Timer; fn sleep(&self, dur: Duration) -> Self::Sleep { embassy_time::Timer::after_millis(dur.as_millis() as u64) } } impl BlockingSleeper for EmbassySleeper { fn sleep(&self, dur: Duration) { embassy_time::block_for(embassy_time::Duration::from_millis(dur.as_millis() as u64)); } } backon-1.6.0/src/lib.rs000064400000000000000000000222471046102023000127770ustar 00000000000000#![doc( html_logo_url = "https://raw.githubusercontent.com/Xuanwo/backon/main/.github/assets/logo.jpeg" )] #![cfg_attr(docsrs, feature(doc_cfg))] //! [![Build Status]][actions] [![Latest Version]][crates.io] [![](https://img.shields.io/discord/1111711408875393035?logo=discord&label=discord)](https://discord.gg/8ARnvtJePD) //! //! [Build Status]: https://img.shields.io/github/actions/workflow/status/Xuanwo/backon/ci.yml?branch=main //! [actions]: https://github.com/Xuanwo/backon/actions?query=branch%3Amain //! [Latest Version]: https://img.shields.io/crates/v/backon.svg //! [crates.io]: https://crates.io/crates/backon //! //! BackON //! //! Make **retry** like a built-in feature provided by Rust. //! //! - **Simple**: Just like a built-in feature: `your_fn.retry(ExponentialBuilder::default()).await`. //! - **Flexible**: Supports both blocking and async functions. //! - **Powerful**: Allows control over retry behavior such as [`when`](https://docs.rs/backon/latest/backon/struct.Retry.html#method.when) and [`notify`](https://docs.rs/backon/latest/backon/struct.Retry.html#method.notify). //! - **Customizable**: Supports custom retry strategies like [exponential](https://docs.rs/backon/latest/backon/struct.ExponentialBuilder.html), [constant](https://docs.rs/backon/latest/backon/struct.ConstantBuilder.html), etc. //! //! # Backoff //! //! Retry in BackON requires a backoff strategy. BackON will accept a [`BackoffBuilder`] which will generate a new [`Backoff`] for each retry. It also accepts any object that implements [`Backoff`]. You can therefore easily implement your own custom backoff strategy. //! //! BackON provides several backoff implementations with reasonable defaults: //! //! - [`ConstantBuilder`]: backoff with a constant delay, limited to a specific number of attempts. //! - [`ExponentialBuilder`]: backoff with an exponential delay, also supports jitter. //! - [`FibonacciBuilder`]: backoff with a fibonacci delay, also supports jitter. //! //! # Sleep //! //! Retry in BackON requires an implementation for sleeping, such an implementation //! is called a Sleeper, it will implement [`Sleeper`] or [`BlockingSleeper`] depending //! on if it is going to be used in an asynchronous context. //! //! ## Default Sleeper //! //! Currently, BackON has 3 built-in Sleeper implementations for different //! environments, they are gated under their own features, which are enabled //! by default: //! //! | `Sleeper` | feature | Environment | Asynchronous | //! |-------------------------|---------------------|-------------|---------------| //! | [`TokioSleeper`] | tokio-sleep | non-wasm32 | Yes | //! | [`GlooTimersSleep`] | gloo-timers-sleep | wasm32 | Yes | //! | [`FuturesTimerSleeper`] | futures-timer-sleep |wasm/non-wasm| Yes | //! | [`EmbassySleep`] | embassy-sleep | no_std | Yes | //! | [`StdSleeper`] | std-blocking-sleep | std | No | //! //! ## Custom Sleeper //! //! If you do not want to use the built-in Sleeper, you CAN provide a custom //! implementation, let's implement an asynchronous dummy Sleeper that does //! not sleep at all. You will find it pretty similar when you implement a //! blocking one. //! //! ``` //! use std::time::Duration; //! //! use backon::Sleeper; //! //! /// A dummy `Sleeper` impl that prints then becomes ready! //! struct DummySleeper; //! //! impl Sleeper for DummySleeper { //! type Sleep = std::future::Ready<()>; //! //! fn sleep(&self, dur: Duration) -> Self::Sleep { //! println!("Hello from DummySleeper!"); //! std::future::ready(()) //! } //! } //! ``` //! //! ## The empty Sleeper //! //! If neither feature is enabled nor a custom implementation is provided, BackON //! will fallback to the empty sleeper, in which case, a compile-time error that //! `PleaseEnableAFeatureOrProvideACustomSleeper needs to implement Sleeper or //! BlockingSleeper` will be raised to remind you to choose or bring a real Sleeper //! implementation. //! //! # Retry //! //! For additional examples, please visit [`docs::examples`]. //! //! ## Retry an async function //! //! ```rust //! use anyhow::Result; //! use backon::ExponentialBuilder; //! use backon::Retryable; //! use core::time::Duration; //! //! async fn fetch() -> Result { //! Ok("hello, world!".to_string()) //! } //! //! #[tokio::main(flavor = "current_thread")] //! async fn main() -> Result<()> { //! let content = fetch //! // Retry with exponential backoff //! .retry(ExponentialBuilder::default()) //! // Sleep implementation, default to tokio::time::sleep if `tokio-sleep` has been enabled. //! .sleep(tokio::time::sleep) //! // When to retry //! .when(|e| e.to_string() == "EOF") //! // Notify when retrying //! .notify(|err: &anyhow::Error, dur: Duration| { //! println!("retrying {:?} after {:?}", err, dur); //! }) //! .await?; //! println!("fetch succeeded: {}", content); //! //! Ok(()) //! } //! ``` //! //! ## Retry a blocking function //! //! ```rust //! use anyhow::Result; //! use backon::BlockingRetryable; //! use backon::ExponentialBuilder; //! use core::time::Duration; //! //! fn fetch() -> Result { //! Ok("hello, world!".to_string()) //! } //! //! fn main() -> Result<()> { //! let content = fetch //! // Retry with exponential backoff //! .retry(ExponentialBuilder::default()) //! // Sleep implementation, default to std::thread::sleep if `std-blocking-sleep` has been enabled. //! .sleep(std::thread::sleep) //! // When to retry //! .when(|e| e.to_string() == "EOF") //! // Notify when retrying //! .notify(|err: &anyhow::Error, dur: Duration| { //! println!("retrying {:?} after {:?}", err, dur); //! }) //! .call()?; //! println!("fetch succeeded: {}", content); //! //! Ok(()) //! } //! ``` //! //! ## Retry an async function with context //! //! Sometimes users can meet the problem that the async function is needs to take `FnMut`: //! //! ```shell //! error: captured variable cannot escape `FnMut` closure body //! --> src/retry.rs:404:27 //! | //! 400 | let mut test = Test; //! | -------- variable defined here //! ... //! 404 | let result = { || async { test.hello().await } } //! | - ^^^^^^^^----^^^^^^^^^^^^^^^^ //! | | | | //! | | | variable captured here //! | | returns an `async` block that contains a reference to a captured variable, which then escapes the closure body //! | inferred to be a `FnMut` closure //! | //! = note: `FnMut` closures only have access to their captured variables while they are executing... //! = note: ...therefore, they cannot allow references to captured variables to escape //! ``` //! //! `RetryableWithContext` is designed for this, it allows you to pass a context //! to the retry function, and return it back after the retry is done. //! //! ```no_run //! use anyhow::anyhow; //! use anyhow::Result; //! use backon::ExponentialBuilder; //! use backon::RetryableWithContext; //! //! struct Test; //! //! impl Test { //! async fn hello(&mut self) -> Result { //! Err(anyhow!("not retryable")) //! } //! } //! //! #[tokio::main(flavor = "current_thread")] //! async fn main() -> Result<()> { //! let mut test = Test; //! //! // (Test, Result) //! let (_, result) = { //! |mut v: Test| async { //! let res = v.hello().await; //! (v, res) //! } //! } //! .retry(ExponentialBuilder::default()) //! .context(test) //! .await; //! //! Ok(()) //! } //! ``` #![deny(missing_docs)] #![deny(unused_qualifications)] #![no_std] #[cfg(feature = "std-blocking-sleep")] extern crate std; mod backoff; pub use backoff::*; mod retry; pub use retry::Retry; pub use retry::Retryable; mod retry_with_context; pub use retry_with_context::RetryWithContext; pub use retry_with_context::RetryableWithContext; mod sleep; pub use sleep::DefaultSleeper; #[cfg(feature = "futures-timer-sleep")] pub use sleep::FuturesTimerSleeper; #[cfg(all(target_arch = "wasm32", feature = "gloo-timers-sleep"))] pub use sleep::GlooTimersSleep; pub use sleep::Sleeper; #[cfg(all(not(target_arch = "wasm32"), feature = "tokio-sleep"))] pub use sleep::TokioSleeper; mod blocking_retry; pub use blocking_retry::BlockingRetry; pub use blocking_retry::BlockingRetryable; mod blocking_retry_with_context; pub use blocking_retry_with_context::BlockingRetryWithContext; pub use blocking_retry_with_context::BlockingRetryableWithContext; mod blocking_sleep; pub use blocking_sleep::BlockingSleeper; pub use blocking_sleep::DefaultBlockingSleeper; #[cfg(feature = "std-blocking-sleep")] pub use blocking_sleep::StdSleeper; #[cfg(feature = "embassy-sleep")] mod embassy_timer_sleep; #[cfg(feature = "embassy-sleep")] pub use embassy_timer_sleep::EmbassySleeper; #[cfg(docsrs)] pub mod docs; backon-1.6.0/src/retry.rs000064400000000000000000000441321046102023000133730ustar 00000000000000use core::future::Future; use core::pin::Pin; use core::task::Context; use core::task::Poll; use core::task::ready; use core::time::Duration; use crate::Backoff; use crate::DefaultSleeper; use crate::Sleeper; use crate::backoff::BackoffBuilder; use crate::sleep::MaybeSleeper; /// Retryable will add retry support for functions that produce futures with results. /// /// This means all types that implement `FnMut() -> impl Future>` /// will be able to use `retry`. /// /// For example: /// /// - Functions without extra args: /// /// ```ignore /// async fn fetch() -> Result { /// Ok(reqwest::get("https://www.rust-lang.org").await?.text().await?) /// } /// ``` /// /// - Closures /// /// ```ignore /// || async { /// let x = reqwest::get("https://www.rust-lang.org") /// .await? /// .text() /// .await?; /// /// Err(anyhow::anyhow!(x)) /// } /// ``` pub trait Retryable< B: BackoffBuilder, T, E, Fut: Future>, FutureFn: FnMut() -> Fut, > { /// Generate a new retry fn retry(self, builder: B) -> Retry; } impl Retryable for FutureFn where B: BackoffBuilder, Fut: Future>, FutureFn: FnMut() -> Fut, { fn retry(self, builder: B) -> Retry { Retry::new(self, builder.build()) } } /// Struct generated by [`Retryable`]. pub struct Retry< B: Backoff, T, E, Fut: Future>, FutureFn: FnMut() -> Fut, SF: MaybeSleeper = DefaultSleeper, RF = fn(&E) -> bool, NF = fn(&E, Duration), AF = fn(&E, Option) -> Option, > { backoff: B, future_fn: FutureFn, retryable_fn: RF, notify_fn: NF, sleep_fn: SF, adjust_fn: AF, state: State, } impl Retry where B: Backoff, Fut: Future>, FutureFn: FnMut() -> Fut, { /// Initiate a new retry. fn new(future_fn: FutureFn, backoff: B) -> Self { Retry { backoff, future_fn, retryable_fn: |_: &E| true, notify_fn: |_: &E, _: Duration| {}, adjust_fn: |_: &E, dur: Option| dur, sleep_fn: DefaultSleeper::default(), state: State::Idle, } } } impl Retry where B: Backoff, Fut: Future>, FutureFn: FnMut() -> Fut, SF: MaybeSleeper, RF: FnMut(&E) -> bool, NF: FnMut(&E, Duration), AF: FnMut(&E, Option) -> Option, { /// Set the sleeper for retrying. /// /// The sleeper should implement the [`Sleeper`] trait. The simplest way is to use a closure that returns a `Future`. /// /// If not specified, we use the [`DefaultSleeper`]. /// /// ```no_run /// use std::future::ready; /// /// use anyhow::Result; /// use backon::ExponentialBuilder; /// use backon::Retryable; /// /// async fn fetch() -> Result { /// Ok(reqwest::get("https://www.rust-lang.org") /// .await? /// .text() /// .await?) /// } /// /// #[tokio::main(flavor = "current_thread")] /// async fn main() -> Result<()> { /// let content = fetch /// .retry(ExponentialBuilder::default()) /// .sleep(|_| ready(())) /// .await?; /// println!("fetch succeeded: {}", content); /// /// Ok(()) /// } /// ``` pub fn sleep(self, sleep_fn: SN) -> Retry { Retry { backoff: self.backoff, retryable_fn: self.retryable_fn, notify_fn: self.notify_fn, future_fn: self.future_fn, sleep_fn, adjust_fn: self.adjust_fn, state: State::Idle, } } /// Set the conditions for retrying. /// /// If not specified, all errors are considered retryable. /// /// # Examples /// /// ```no_run /// use anyhow::Result; /// use backon::ExponentialBuilder; /// use backon::Retryable; /// /// async fn fetch() -> Result { /// Ok(reqwest::get("https://www.rust-lang.org") /// .await? /// .text() /// .await?) /// } /// /// #[tokio::main(flavor = "current_thread")] /// async fn main() -> Result<()> { /// let content = fetch /// .retry(ExponentialBuilder::default()) /// .when(|e| e.to_string() == "EOF") /// .await?; /// println!("fetch succeeded: {}", content); /// /// Ok(()) /// } /// ``` pub fn when bool>( self, retryable: RN, ) -> Retry { Retry { backoff: self.backoff, retryable_fn: retryable, notify_fn: self.notify_fn, future_fn: self.future_fn, sleep_fn: self.sleep_fn, adjust_fn: self.adjust_fn, state: self.state, } } /// Set to notify for all retry attempts. /// /// When a retry happens, the input function will be invoked with the error and the sleep duration before pausing. /// /// If not specified, this operation does nothing. /// /// # Examples /// /// ```no_run /// use core::time::Duration; /// /// use anyhow::Result; /// use backon::ExponentialBuilder; /// use backon::Retryable; /// /// async fn fetch() -> Result { /// Ok(reqwest::get("https://www.rust-lang.org") /// .await? /// .text() /// .await?) /// } /// /// #[tokio::main(flavor = "current_thread")] /// async fn main() -> Result<()> { /// let content = fetch /// .retry(ExponentialBuilder::default()) /// .notify(|err: &anyhow::Error, dur: Duration| { /// println!("retrying error {:?} with sleeping {:?}", err, dur); /// }) /// .await?; /// println!("fetch succeeded: {}", content); /// /// Ok(()) /// } /// ``` pub fn notify( self, notify: NN, ) -> Retry { Retry { backoff: self.backoff, retryable_fn: self.retryable_fn, notify_fn: notify, sleep_fn: self.sleep_fn, future_fn: self.future_fn, adjust_fn: self.adjust_fn, state: self.state, } } /// Sets the function to adjust the backoff duration for retry attempts. /// /// When a retry occurs, the provided function will be called with the error and the proposed backoff duration, allowing you to modify the final duration used. /// /// If the function returns `None`, it indicates that no further retries should be made, and the error will be returned regardless of the backoff duration provided by the input. /// /// If no `adjust` function is specified, the original backoff duration from the input will be used without modification. /// /// `adjust` can be used to implement dynamic backoff strategies, such as adjust backoff values from the http `Retry-After` headers. /// /// # Examples /// /// ```no_run /// use core::time::Duration; /// use std::error::Error; /// use std::fmt::Display; /// use std::fmt::Formatter; /// /// use anyhow::Result; /// use backon::ExponentialBuilder; /// use backon::Retryable; /// use reqwest::header::HeaderMap; /// use reqwest::StatusCode; /// /// #[derive(Debug)] /// struct HttpError { /// headers: HeaderMap, /// } /// /// impl Display for HttpError { /// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { /// write!(f, "http error") /// } /// } /// /// impl Error for HttpError {} /// /// async fn fetch() -> Result { /// let resp = reqwest::get("https://www.rust-lang.org").await?; /// if resp.status() != StatusCode::OK { /// let source = HttpError { /// headers: resp.headers().clone(), /// }; /// return Err(anyhow::Error::new(source)); /// } /// Ok(resp.text().await?) /// } /// /// #[tokio::main(flavor = "current_thread")] /// async fn main() -> Result<()> { /// let content = fetch /// .retry(ExponentialBuilder::default()) /// .adjust(|err, dur| { /// match err.downcast_ref::() { /// Some(v) => { /// if let Some(retry_after) = v.headers.get("Retry-After") { /// // Parse the Retry-After header and adjust the backoff duration /// let retry_after = retry_after.to_str().unwrap_or("0"); /// let retry_after = retry_after.parse::().unwrap_or(0); /// Some(Duration::from_secs(retry_after)) /// } else { /// dur /// } /// } /// None => dur, /// } /// }) /// .await?; /// println!("fetch succeeded: {}", content); /// /// Ok(()) /// } /// ``` pub fn adjust) -> Option>( self, adjust: NAF, ) -> Retry { Retry { backoff: self.backoff, retryable_fn: self.retryable_fn, notify_fn: self.notify_fn, sleep_fn: self.sleep_fn, future_fn: self.future_fn, adjust_fn: adjust, state: self.state, } } } /// State maintains internal state of retry. #[derive(Default)] enum State>, SleepFut: Future> { #[default] Idle, Polling(Fut), Sleeping(SleepFut), } impl Future for Retry where B: Backoff, Fut: Future>, FutureFn: FnMut() -> Fut, SF: Sleeper, RF: FnMut(&E) -> bool, NF: FnMut(&E, Duration), AF: FnMut(&E, Option) -> Option, { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { // Safety: This is safe because we don't move the `Retry` struct itself, // only its internal state. // // We do the exactly same thing like `pin_project` but without depending on it directly. let this = unsafe { self.get_unchecked_mut() }; loop { match &mut this.state { State::Idle => { let fut = (this.future_fn)(); this.state = State::Polling(fut); continue; } State::Polling(fut) => { // Safety: This is safe because we don't move the `Retry` struct and this fut, // only its internal state. // // We do the exactly same thing like `pin_project` but without depending on it directly. let mut fut = unsafe { Pin::new_unchecked(fut) }; match ready!(fut.as_mut().poll(cx)) { Ok(v) => return Poll::Ready(Ok(v)), Err(err) => { // If input error is not retryable, return error directly. if !(this.retryable_fn)(&err) { return Poll::Ready(Err(err)); } let adjusted_backoff = (this.adjust_fn)(&err, this.backoff.next()); match adjusted_backoff { None => return Poll::Ready(Err(err)), Some(dur) => { (this.notify_fn)(&err, dur); this.state = State::Sleeping(this.sleep_fn.sleep(dur)); continue; } } } } } State::Sleeping(sl) => { // Safety: This is safe because we don't move the `Retry` struct and this fut, // only its internal state. // // We do the exactly same thing like `pin_project` but without depending on it directly. let mut sl = unsafe { Pin::new_unchecked(sl) }; ready!(sl.as_mut().poll(cx)); this.state = State::Idle; continue; } } } } } #[cfg(test)] #[cfg(any(feature = "tokio-sleep", feature = "gloo-timers-sleep",))] mod default_sleeper_tests { extern crate alloc; use alloc::string::ToString; use alloc::vec; use alloc::vec::Vec; use core::time::Duration; use tokio::sync::Mutex; #[cfg(not(target_arch = "wasm32"))] use tokio::test; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; use crate::ExponentialBuilder; async fn always_error() -> anyhow::Result<()> { Err(anyhow::anyhow!("test_query meets error")) } #[test] async fn test_retry() { let result = always_error .retry(ExponentialBuilder::default().with_min_delay(Duration::from_millis(1))) .await; assert!(result.is_err()); assert_eq!("test_query meets error", result.unwrap_err().to_string()); } #[test] async fn test_retry_with_not_retryable_error() { let error_times = Mutex::new(0); let f = || async { let mut x = error_times.lock().await; *x += 1; Err::<(), anyhow::Error>(anyhow::anyhow!("not retryable")) }; let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1)); let result = f .retry(backoff) // Only retry If error message is `retryable` .when(|e| e.to_string() == "retryable") .await; assert!(result.is_err()); assert_eq!("not retryable", result.unwrap_err().to_string()); // `f` always returns error "not retryable", so it should be executed // only once. assert_eq!(*error_times.lock().await, 1); } #[test] async fn test_retry_with_retryable_error() { let error_times = Mutex::new(0); let f = || async { let mut x = error_times.lock().await; *x += 1; Err::<(), anyhow::Error>(anyhow::anyhow!("retryable")) }; let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1)); let result = f .retry(backoff) // Only retry If error message is `retryable` .when(|e| e.to_string() == "retryable") .await; assert!(result.is_err()); assert_eq!("retryable", result.unwrap_err().to_string()); // `f` always returns error "retryable", so it should be executed // 4 times (retry 3 times). assert_eq!(*error_times.lock().await, 4); } #[test] async fn test_retry_with_adjust() { let error_times = std::sync::Mutex::new(0); let f = || async { Err::<(), anyhow::Error>(anyhow::anyhow!("retryable")) }; let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1)); let result = f .retry(backoff) // Only retry If error message is `retryable` .when(|e| e.to_string() == "retryable") .adjust(|_, dur| { let mut x = error_times.lock().unwrap(); *x += 1; dur }) .await; assert!(result.is_err()); assert_eq!("retryable", result.unwrap_err().to_string()); // `f` always returns error "retryable", so it should be executed // 4 times (retry 3 times). assert_eq!(*error_times.lock().unwrap(), 4); } #[test] async fn test_fn_mut_when_and_notify() { let mut calls_retryable: Vec<()> = vec![]; let mut calls_notify: Vec<()> = vec![]; let f = || async { Err::<(), anyhow::Error>(anyhow::anyhow!("retryable")) }; let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1)); let result = f .retry(backoff) .when(|_| { calls_retryable.push(()); true }) .notify(|_, _| { calls_notify.push(()); }) .await; assert!(result.is_err()); assert_eq!("retryable", result.unwrap_err().to_string()); // `f` always returns error "retryable", so it should be executed // 4 times (retry 3 times). assert_eq!(calls_retryable.len(), 4); assert_eq!(calls_notify.len(), 3); } } #[cfg(test)] mod custom_sleeper_tests { extern crate alloc; use alloc::string::ToString; use core::future::ready; use core::time::Duration; #[cfg(not(target_arch = "wasm32"))] use tokio::test; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; use crate::ExponentialBuilder; async fn always_error() -> anyhow::Result<()> { Err(anyhow::anyhow!("test_query meets error")) } #[test] async fn test_retry_with_sleep() { let result = always_error .retry(ExponentialBuilder::default().with_min_delay(Duration::from_millis(1))) .sleep(|_| ready(())) .await; assert!(result.is_err()); assert_eq!("test_query meets error", result.unwrap_err().to_string()); } } backon-1.6.0/src/retry_with_context.rs000064400000000000000000000313771046102023000162010ustar 00000000000000use core::future::Future; use core::pin::Pin; use core::task::Context; use core::task::Poll; use core::task::ready; use core::time::Duration; use crate::Backoff; use crate::DefaultSleeper; use crate::Sleeper; use crate::backoff::BackoffBuilder; use crate::sleep::MaybeSleeper; /// `RetryableWithContext` adds retry support for functions that produce futures with results /// and context. /// /// This means all types implementing `FnMut(Ctx) -> impl Future)>` /// can use `retry`. /// /// Users must provide context to the function and can receive it back after the retry is completed. /// /// # Example /// /// Without context, we might encounter errors such as the following: /// /// ```shell /// error: captured variable cannot escape `FnMut` closure body /// --> src/retry.rs:404:27 /// | /// 400 | let mut test = Test; /// | -------- variable defined here /// ... /// 404 | let result = { || async { test.hello().await } } /// | - ^^^^^^^^----^^^^^^^^^^^^^^^^ /// | | | | /// | | | variable captured here /// | | returns an `async` block that contains a reference to a captured variable, which then escapes the closure body /// | inferred to be a `FnMut` closure /// | /// = note: `FnMut` closures only have access to their captured variables while they are executing... /// = note: ...therefore, they cannot allow references to captured variables to escape /// ``` /// /// However, with context support, we can implement it this way: /// /// ```no_run /// use anyhow::anyhow; /// use anyhow::Result; /// use backon::ExponentialBuilder; /// use backon::RetryableWithContext; /// /// struct Test; /// /// impl Test { /// async fn hello(&mut self) -> Result { /// Err(anyhow!("not retryable")) /// } /// } /// /// #[tokio::main(flavor = "current_thread")] /// async fn main() -> Result<()> { /// let mut test = Test; /// /// // (Test, Result) /// let (_, result) = { /// |mut v: Test| async { /// let res = v.hello().await; /// (v, res) /// } /// } /// .retry(ExponentialBuilder::default()) /// .context(test) /// .await; /// /// Ok(()) /// } /// ``` pub trait RetryableWithContext< B: BackoffBuilder, T, E, Ctx, Fut: Future)>, FutureFn: FnMut(Ctx) -> Fut, > { /// Generate a new retry fn retry(self, builder: B) -> RetryWithContext; } impl RetryableWithContext for FutureFn where B: BackoffBuilder, Fut: Future)>, FutureFn: FnMut(Ctx) -> Fut, { fn retry(self, builder: B) -> RetryWithContext { RetryWithContext::new(self, builder.build()) } } /// Retry struct generated by [`RetryableWithContext`]. pub struct RetryWithContext< B: Backoff, T, E, Ctx, Fut: Future)>, FutureFn: FnMut(Ctx) -> Fut, SF: MaybeSleeper = DefaultSleeper, RF = fn(&E) -> bool, NF = fn(&E, Duration), > { backoff: B, retryable: RF, notify: NF, future_fn: FutureFn, sleep_fn: SF, state: State, } impl RetryWithContext where B: Backoff, Fut: Future)>, FutureFn: FnMut(Ctx) -> Fut, { /// Create a new retry. fn new(future_fn: FutureFn, backoff: B) -> Self { RetryWithContext { backoff, retryable: |_: &E| true, notify: |_: &E, _: Duration| {}, future_fn, sleep_fn: DefaultSleeper::default(), state: State::Idle(None), } } } impl RetryWithContext where B: Backoff, Fut: Future)>, FutureFn: FnMut(Ctx) -> Fut, SF: MaybeSleeper, RF: FnMut(&E) -> bool, NF: FnMut(&E, Duration), { /// Set the sleeper for retrying. /// /// The sleeper should implement the [`Sleeper`] trait. The simplest way is to use a closure that returns a `Future`. /// /// If not specified, we use the [`DefaultSleeper`]. pub fn sleep( self, sleep_fn: SN, ) -> RetryWithContext { assert!( matches!(self.state, State::Idle(None)), "sleep must be set before context" ); RetryWithContext { backoff: self.backoff, retryable: self.retryable, notify: self.notify, future_fn: self.future_fn, sleep_fn, state: State::Idle(None), } } /// Set the context for retrying. /// /// Context is used to capture ownership manually to prevent lifetime issues. pub fn context( self, context: Ctx, ) -> RetryWithContext { RetryWithContext { backoff: self.backoff, retryable: self.retryable, notify: self.notify, future_fn: self.future_fn, sleep_fn: self.sleep_fn, state: State::Idle(Some(context)), } } /// Set the conditions for retrying. /// /// If not specified, all errors are considered retryable. /// /// # Examples /// /// ```no_run /// use anyhow::Result; /// use backon::ExponentialBuilder; /// use backon::Retryable; /// /// async fn fetch() -> Result { /// Ok(reqwest::get("https://www.rust-lang.org") /// .await? /// .text() /// .await?) /// } /// /// #[tokio::main(flavor = "current_thread")] /// async fn main() -> Result<()> { /// let content = fetch /// .retry(ExponentialBuilder::default()) /// .when(|e| e.to_string() == "EOF") /// .await?; /// println!("fetch succeeded: {}", content); /// /// Ok(()) /// } /// ``` pub fn when bool>( self, retryable: RN, ) -> RetryWithContext { RetryWithContext { backoff: self.backoff, retryable, notify: self.notify, future_fn: self.future_fn, sleep_fn: self.sleep_fn, state: self.state, } } /// Set to notify for all retry attempts. /// /// When a retry happens, the input function will be invoked with the error and the sleep duration before pausing. /// /// If not specified, this operation does nothing. /// /// # Examples /// /// ```no_run /// use core::time::Duration; /// /// use anyhow::Result; /// use backon::ExponentialBuilder; /// use backon::Retryable; /// /// async fn fetch() -> Result { /// Ok(reqwest::get("https://www.rust-lang.org") /// .await? /// .text() /// .await?) /// } /// /// #[tokio::main(flavor = "current_thread")] /// async fn main() -> Result<()> { /// let content = fetch /// .retry(ExponentialBuilder::default()) /// .notify(|err: &anyhow::Error, dur: Duration| { /// println!("retrying error {:?} with sleeping {:?}", err, dur); /// }) /// .await?; /// println!("fetch succeeded: {}", content); /// /// Ok(()) /// } /// ``` pub fn notify( self, notify: NN, ) -> RetryWithContext { RetryWithContext { backoff: self.backoff, retryable: self.retryable, notify, future_fn: self.future_fn, sleep_fn: self.sleep_fn, state: self.state, } } } /// State maintains internal state of retry. enum State)>, SleepFut: Future> { Idle(Option), Polling(Fut), Sleeping((Option, SleepFut)), } impl Future for RetryWithContext where B: Backoff, Fut: Future)>, FutureFn: FnMut(Ctx) -> Fut, SF: Sleeper, RF: FnMut(&E) -> bool, NF: FnMut(&E, Duration), { type Output = (Ctx, Result); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { // Safety: This is safe because we don't move the `Retry` struct itself, // only its internal state. // // We do the exactly same thing like `pin_project` but without depending on it directly. let this = unsafe { self.get_unchecked_mut() }; loop { match &mut this.state { State::Idle(ctx) => { let ctx = ctx.take().expect("context must be valid"); let fut = (this.future_fn)(ctx); this.state = State::Polling(fut); continue; } State::Polling(fut) => { // Safety: This is safe because we don't move the `Retry` struct and this fut, // only its internal state. // // We do the exactly same thing like `pin_project` but without depending on it directly. let mut fut = unsafe { Pin::new_unchecked(fut) }; let (ctx, res) = ready!(fut.as_mut().poll(cx)); match res { Ok(v) => return Poll::Ready((ctx, Ok(v))), Err(err) => { // If input error is not retryable, return error directly. if !(this.retryable)(&err) { return Poll::Ready((ctx, Err(err))); } match this.backoff.next() { None => return Poll::Ready((ctx, Err(err))), Some(dur) => { (this.notify)(&err, dur); this.state = State::Sleeping((Some(ctx), this.sleep_fn.sleep(dur))); continue; } } } } } State::Sleeping((ctx, sl)) => { // Safety: This is safe because we don't move the `Retry` struct and this fut, // only its internal state. // // We do the exactly same thing like `pin_project` but without depending on it directly. let mut sl = unsafe { Pin::new_unchecked(sl) }; ready!(sl.as_mut().poll(cx)); let ctx = ctx.take().expect("context must be valid"); this.state = State::Idle(Some(ctx)); continue; } } } } } #[cfg(test)] #[cfg(any(feature = "tokio-sleep", feature = "gloo-timers-sleep",))] mod tests { extern crate alloc; use alloc::string::ToString; use core::time::Duration; use anyhow::Result; use anyhow::anyhow; use tokio::sync::Mutex; #[cfg(not(target_arch = "wasm32"))] use tokio::test; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; use crate::ExponentialBuilder; struct Test; impl Test { async fn hello(&mut self) -> Result { Err(anyhow!("not retryable")) } } #[test] async fn test_retry_with_not_retryable_error() { let error_times = Mutex::new(0); let test = Test; let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1)); let (_, result) = { |mut v: Test| async { let mut x = error_times.lock().await; *x += 1; let res = v.hello().await; (v, res) } } .retry(backoff) .context(test) // Only retry If error message is `retryable` .when(|e| e.to_string() == "retryable") .await; assert!(result.is_err()); assert_eq!("not retryable", result.unwrap_err().to_string()); // `f` always returns error "not retryable", so it should be executed // only once. assert_eq!(*error_times.lock().await, 1); } } backon-1.6.0/src/sleep.rs000064400000000000000000000101261046102023000133320ustar 00000000000000use core::future::Future; use core::future::Ready; use core::time::Duration; /// A sleeper is used to generate a future that completes after a specified duration. pub trait Sleeper: 'static { /// The future returned by the `sleep` method. type Sleep: Future; /// Create a future that completes after a set period. fn sleep(&self, dur: Duration) -> Self::Sleep; } /// A stub trait allowing non-[`Sleeper`] types to be used as a generic parameter in [`Retry`][crate::Retry]. /// It does not provide actual functionality. #[doc(hidden)] pub trait MaybeSleeper: 'static { type Sleep: Future; } /// All `Sleeper` will implement `MaybeSleeper`, but not vice versa. impl MaybeSleeper for T { type Sleep = ::Sleep; } /// All `Fn(Duration) -> impl Future` implements `Sleeper`. impl Fut + 'static, Fut: Future> Sleeper for F { type Sleep = Fut; fn sleep(&self, dur: Duration) -> Self::Sleep { self(dur) } } /// The default implementation of `Sleeper` when no features are enabled. /// /// It will fail to compile if a containing [`Retry`][crate::Retry] is `.await`ed without calling [`Retry::sleep`][crate::Retry::sleep] to provide a valid sleeper. #[cfg(all(not(feature = "tokio-sleep"), not(feature = "gloo-timers-sleep"),))] pub type DefaultSleeper = PleaseEnableAFeatureOrProvideACustomSleeper; /// The default implementation of `Sleeper` while feature `tokio-sleep` enabled. /// /// it uses `tokio::time::sleep`. #[cfg(all(not(target_arch = "wasm32"), feature = "tokio-sleep"))] pub type DefaultSleeper = TokioSleeper; /// The default implementation of `Sleeper` while feature `gloo-timers-sleep` enabled. /// /// It uses `gloo_timers::sleep::sleep`. #[cfg(all(target_arch = "wasm32", feature = "gloo-timers-sleep"))] pub type DefaultSleeper = GlooTimersSleep; /// A placeholder type that does not implement [`Sleeper`] and will therefore fail to compile if used as one. /// /// Users should enable a feature of this crate that provides a valid [`Sleeper`] implementation when this type appears in compilation errors. Alternatively, a custom [`Sleeper`] implementation should be provided where necessary, such as in [`crate::Retry::sleeper`]. #[doc(hidden)] #[allow(dead_code)] #[derive(Clone, Copy, Debug, Default)] pub struct PleaseEnableAFeatureOrProvideACustomSleeper; /// Implement `MaybeSleeper` but not `Sleeper`. impl MaybeSleeper for PleaseEnableAFeatureOrProvideACustomSleeper { type Sleep = Ready<()>; } /// The default implementation of `Sleeper` uses `tokio::time::sleep`. /// /// It will adhere to [pausing/auto-advancing](https://docs.rs/tokio/latest/tokio/time/fn.pause.html) /// in Tokio's Runtime semantics, if enabled. #[cfg(all(not(target_arch = "wasm32"), feature = "tokio-sleep"))] #[derive(Clone, Copy, Debug, Default)] pub struct TokioSleeper; #[cfg(all(not(target_arch = "wasm32"), feature = "tokio-sleep"))] impl Sleeper for TokioSleeper { type Sleep = tokio::time::Sleep; fn sleep(&self, dur: Duration) -> Self::Sleep { tokio::time::sleep(dur) } } /// The implementation of `Sleeper` that uses `futures_timer::Delay`. /// /// This implementation is based on /// the [`futures-timer`](https://docs.rs/futures-timer/latest/futures_timer/) crate. /// It is async runtime agnostic and will also work in WASM environments. #[cfg(feature = "futures-timer-sleep")] #[derive(Clone, Copy, Debug, Default)] pub struct FuturesTimerSleeper; #[cfg(feature = "futures-timer-sleep")] impl Sleeper for FuturesTimerSleeper { type Sleep = futures_timer::Delay; fn sleep(&self, dur: Duration) -> Self::Sleep { futures_timer::Delay::new(dur) } } /// The default implementation of `Sleeper` utilizes `gloo_timers::future::sleep`. #[cfg(all(target_arch = "wasm32", feature = "gloo-timers-sleep"))] #[derive(Clone, Copy, Debug, Default)] pub struct GlooTimersSleep; #[cfg(all(target_arch = "wasm32", feature = "gloo-timers-sleep"))] impl Sleeper for GlooTimersSleep { type Sleep = gloo_timers::future::TimeoutFuture; fn sleep(&self, dur: Duration) -> Self::Sleep { gloo_timers::future::sleep(dur) } }