diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8c62540..a44cb35 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -38,6 +38,18 @@ jobs:
RUST_LOG: DEBUG
RUST_BACKTRACE: full
+ nightly-unit:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - run: rustup toolchain install nightly
+ - name: Test
+ run: cargo +nightly test --all-features
+ env:
+ RUST_LOG: DEBUG
+ RUST_BACKTRACE: full
+ RUSTDOCFLAGS: "--cfg docsrs"
+
wasm-unit:
runs-on: ubuntu-latest
steps:
diff --git a/Cargo.toml b/Cargo.toml
index 7c72235..014c2e0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,6 +8,15 @@ name = "backon"
repository = "https://github.com/Xuanwo/backon"
version = "0.5.0"
+[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]
gloo-timers-sleep = ["dep:gloo-timers", "gloo-timers/futures"]
tokio-sleep = ["dep:tokio", "tokio/time"]
diff --git a/README.md b/README.md
index 0ec7b33..7e974f2 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
-BackON: Make **retry** like a built-in feature provided by Rust.
+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.
diff --git a/examples/async.rs b/examples/async.rs
deleted file mode 100644
index 1a62f8e..0000000
--- a/examples/async.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-use anyhow::Result;
-use backon::ExponentialBuilder;
-use backon::Retryable;
-
-// For more examples, please see: https://docs.rs/backon/#examples
-
-async fn fetch() -> Result {
- let response = reqwest::get("https://httpbingo.org/unstable?failure_rate=0.7").await?;
- if !response.status().is_success() {
- println!("{}", response.status());
- anyhow::bail!("some kind of error");
- }
- let text = response.text().await?;
- Ok(text)
-}
-
-#[tokio::main(flavor = "current_thread")]
-async fn main() -> Result<()> {
- let _ = fetch.retry(ExponentialBuilder::default()).await?;
- println!("fetch succeeded");
-
- Ok(())
-}
diff --git a/examples/blocking.rs b/examples/blocking.rs
deleted file mode 100644
index 887f0c7..0000000
--- a/examples/blocking.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-use anyhow::Result;
-
-// For more examples, please see: https://docs.rs/backon/#examples
-
-fn fetch() -> Result {
- Ok("hello, world!".to_string())
-}
-
-// this example does not run on wasm32-unknown-unknown
-#[cfg(not(target_arch = "wasm32"))]
-fn main() -> Result<()> {
- use backon::BlockingRetryable;
-
- let content = fetch.retry(backon::ExponentialBuilder::default()).call()?;
- println!("fetch succeeded: {}", content);
-
- Ok(())
-}
-
-#[cfg(target_arch = "wasm32")]
-fn main() {}
diff --git a/examples/sqlx.rs b/examples/sqlx.rs
deleted file mode 100644
index cafa140..0000000
--- a/examples/sqlx.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-// For more examples, please see: https://docs.rs/backon/#examples
-
-// this example does not run on wasm32-unknown-unknown
-#[cfg(not(target_arch = "wasm32"))]
-#[tokio::main]
-async fn main() -> anyhow::Result<()> {
- use backon::Retryable;
-
- 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(backon::ExponentialBuilder::default())
- .await?;
-
- assert_eq!(row.0, 150);
-
- Ok(())
-}
-
-#[cfg(target_arch = "wasm32")]
-fn main() {}
diff --git a/rustfmt.toml b/rustfmt.toml
index ef49173..bb916f5 100644
--- a/rustfmt.toml
+++ b/rustfmt.toml
@@ -1,5 +1,6 @@
edition = "2021"
reorder_imports = true
+
# format_code_in_doc_comments = true
# imports_granularity = "Item"
# group_imports = "StdExternalCrate"
diff --git a/src/docs/examples/basic.md b/src/docs/examples/basic.md
new file mode 100644
index 0000000..1833761
--- /dev/null
+++ b/src/docs/examples/basic.md
@@ -0,0 +1,19 @@
+Retry an async function.
+
+```rust
+use backon::ExponentialBuilder;
+use backon::Retryable;
+use anyhow::Result;
+
+async fn fetch() -> Result {
+ Ok("Hello, Workd!".to_string())
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ let content = fetch.retry(ExponentialBuilder::default()).await?;
+
+ println!("fetch succeeded: {}", content);
+ Ok(())
+}
+```
diff --git a/examples/closure.rs b/src/docs/examples/closure.md
similarity index 51%
rename from examples/closure.rs
rename to src/docs/examples/closure.md
index d865330..afaf2f5 100644
--- a/examples/closure.rs
+++ b/src/docs/examples/closure.md
@@ -1,10 +1,11 @@
-// For more examples, please see: https://docs.rs/backon/#examples
+Retry an closure.
-// this example does not run on wasm32-unknown-unknown
-#[cfg(not(target_arch = "wasm32"))]
-fn main() -> anyhow::Result<()> {
- use backon::BlockingRetryable;
+```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);
@@ -13,6 +14,4 @@ fn main() -> anyhow::Result<()> {
Ok(())
}
-
-#[cfg(target_arch = "wasm32")]
-fn main() {}
+```
diff --git a/src/docs/examples/mod.rs b/src/docs/examples/mod.rs
new file mode 100644
index 0000000..e36f0b8
--- /dev/null
+++ b/src/docs/examples/mod.rs
@@ -0,0 +1,22 @@
+//! Examples of using backon.
+
+#[doc = include_str!("basic.md")]
+pub mod basic {}
+
+#[doc = include_str!("closure.md")]
+pub mod closure {}
+
+#[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 {}
diff --git a/src/docs/examples/sqlx.md b/src/docs/examples/sqlx.md
new file mode 100644
index 0000000..bd6febf
--- /dev/null
+++ b/src/docs/examples/sqlx.md
@@ -0,0 +1,23 @@
+Retry 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(())
+}
+```
diff --git a/src/docs/examples/with_args.md b/src/docs/examples/with_args.md
new file mode 100644
index 0000000..82b750a
--- /dev/null
+++ b/src/docs/examples/with_args.md
@@ -0,0 +1,24 @@
+Retry 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(())
+ }
+```
diff --git a/src/docs/examples/with_mut_self.md b/src/docs/examples/with_mut_self.md
new file mode 100644
index 0000000..31b2c47
--- /dev/null
+++ b/src/docs/examples/with_mut_self.md
@@ -0,0 +1,36 @@
+Retry 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(())
+ }
+```
diff --git a/src/docs/examples/with_self.md b/src/docs/examples/with_self.md
new file mode 100644
index 0000000..9889b5b
--- /dev/null
+++ b/src/docs/examples/with_self.md
@@ -0,0 +1,27 @@
+Retry 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(())
+ }
+```
diff --git a/src/docs/examples/with_specific_error.md b/src/docs/examples/with_specific_error.md
new file mode 100644
index 0000000..ef082e9
--- /dev/null
+++ b/src/docs/examples/with_specific_error.md
@@ -0,0 +1,21 @@
+Retry with specify retryable error by `when`.
+
+```rust
+use anyhow::Result;
+use backon::ExponentialBuilder;
+use backon::Retryable;
+
+async fn fetch() -> Result {
+ Ok("Hello, Workd!".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(())
+}
+```
diff --git a/src/docs/mod.rs b/src/docs/mod.rs
new file mode 100644
index 0000000..02c9324
--- /dev/null
+++ b/src/docs/mod.rs
@@ -0,0 +1,3 @@
+//! Docs for the backon crate, like examples.
+
+pub mod examples;
diff --git a/src/lib.rs b/src/lib.rs
index 1e14d09..941412b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,8 +1,23 @@
-//! backon intends to provide an opposite backoff implementation of the popular [backoff](https://docs.rs/backoff).
+#![doc(
+ html_logo_url = "https://raw.githubusercontent.com/Xuanwo/backon/main/.github/assets/logo.jpeg"
+)]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+
+//! 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
+//!
+//!
+//!
+//! Make **retry** like a built-in feature provided by Rust.
//!
-//! - Newer: developed by Rust edition 2021 and latest stable.
-//! - Cleaner: Iterator based abstraction, easy to use, customization friendly.
-//! - Easier: Trait based implementations, works like a native function provided by closures.
+//! - **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
//!
@@ -14,143 +29,65 @@
//! - [`ExponentialBackoff`]: backoff with exponential delay, also provides jitter supports.
//! - [`FibonacciBackoff`]: backoff with fibonacci delay, also provides jitter supports.
//!
-//! Internally, `tokio::time::sleep()` will be used to sleep between retries, therefore
-//! it will respect [pausing/auto-advancing](https://docs.rs/tokio/latest/tokio/time/fn.pause.html)
-//! tokio's Runtime semantics, if enabled.
+//! # Retry
//!
-//! # Examples
+//! For more examples, please visit [`docs::examples`].
//!
-//! Retry with default settings.
+//! ## Retry an async function
//!
-//! ```no_run
+//! ```rust
//! use anyhow::Result;
//! use backon::ExponentialBuilder;
//! use backon::Retryable;
+//! use std::time::Duration;
//!
//! async fn fetch() -> Result {
-//! Ok(reqwest::get("https://www.rust-lang.org")
-//! .await?
-//! .text()
-//! .await?)
+//! Ok("hello, world!".to_string())
//! }
//!
-//! #[tokio::main(flavor = "current_thread")]
-//! async fn main() -> Result<()> {
-//! let content = fetch.retry(ExponentialBuilder::default()).await?;
-//!
-//! println!("fetch succeeded: {}", content);
-//! Ok(())
-//! }
-//! ```
-//!
-//! Retry with specify retryable error.
-//!
-//! ```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")]
+//! #[tokio::main]
//! async fn main() -> Result<()> {
//! let content = fetch
+//! // Retry with exponential backoff
//! .retry(ExponentialBuilder::default())
-//! .when(|e| e.to_string() == "retryable")
+//! // 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 functions with args.
//!
-//! ```no_run
-//! 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(())
//! }
//! ```
//!
-//! Retry functions with receiver `&self`.
+//! ## Retry a blocking function
//!
-//! ```no_run
+//! ```rust
//! use anyhow::Result;
+//! use backon::BlockingRetryable;
//! use backon::ExponentialBuilder;
-//! use backon::Retryable;
-//!
-//! struct Test;
+//! use std::time::Duration;
//!
-//! impl Test {
-//! async fn fetch(&self, url: &str) -> Result {
-//! Ok(reqwest::get(url).await?.text().await?)
-//! }
+//! fn fetch() -> Result {
+//! Ok("hello, world!".to_string())
//! }
//!
-//! #[tokio::main(flavor = "current_thread")]
-//! async fn main() -> Result<()> {
-//! let test = Test;
-//! let content = (|| async { test.fetch("https://www.rust-lang.org").await })
+//! fn main() -> Result<()> {
+//! let content = fetch
+//! // Retry with exponential backoff
//! .retry(ExponentialBuilder::default())
-//! .when(|e| e.to_string() == "retryable")
-//! .await?;
-//!
+//! // 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 functions with receiver `&mut self`.
-//!
-//! ```no_run
-//! 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(())
//! }
//! ```
@@ -190,3 +127,6 @@ mod blocking_retry_with_context;
pub use blocking_retry_with_context::BlockingRetryWithContext;
#[cfg(not(target_arch = "wasm32"))]
pub use blocking_retry_with_context::BlockingRetryableWithContext;
+
+#[cfg(docsrs)]
+pub mod docs;
diff --git a/src/retry.rs b/src/retry.rs
index 202f878..0d06f03 100644
--- a/src/retry.rs
+++ b/src/retry.rs
@@ -41,7 +41,6 @@ use crate::Sleeper;
/// # Examples
///
/// For more examples, please see: [https://docs.rs/backon/#examples](https://docs.rs/backon/#examples)
-///
pub trait Retryable<
B: BackoffBuilder,
T,
diff --git a/src/sleep.rs b/src/sleep.rs
index d694cb2..ebe4091 100644
--- a/src/sleep.rs
+++ b/src/sleep.rs
@@ -43,6 +43,9 @@ impl Fut, Fut: Future