Skip to main content

Wrap

Struct Wrap 

Source
pub struct Wrap<O, I> {
    pub outer: O,
    pub inner: I,
}
Expand description

Composes two policies together.

The outer policy wraps the inner policy. Execution flows: outer → inner → operation

§Type Parameters

  • O - The outer policy type
  • I - The inner policy type

§Examples

Basic composition:

use do_over::{wrap::Wrap, retry::RetryPolicy, timeout::TimeoutPolicy};
use std::time::Duration;

let policy = Wrap::new(
    RetryPolicy::fixed(3, Duration::from_millis(100)),
    TimeoutPolicy::new(Duration::from_secs(5)),
);

Multi-layer composition:

use do_over::{wrap::Wrap, bulkhead::Bulkhead, retry::RetryPolicy, timeout::TimeoutPolicy};
use std::time::Duration;

let policy = Wrap::new(
    Bulkhead::new(10),
    Wrap::new(
        RetryPolicy::fixed(3, Duration::from_millis(100)),
        TimeoutPolicy::new(Duration::from_secs(5)),
    ),
);

Fields§

§outer: O

The outer policy (executed first).

§inner: I

The inner policy (executed second, wrapping the operation).

Implementations§

Source§

impl<O, I> Wrap<O, I>

Source

pub fn new(outer: O, inner: I) -> Self

Create a new policy composition.

§Arguments
  • outer - The outer policy (executed first)
  • inner - The inner policy (wraps the operation)
§Examples
use do_over::{wrap::Wrap, retry::RetryPolicy, timeout::TimeoutPolicy};
use std::time::Duration;

let policy = Wrap::new(
    RetryPolicy::fixed(3, Duration::from_millis(100)),
    TimeoutPolicy::new(Duration::from_secs(5)),
);
Examples found in repository?
examples/comprehensive.rs (lines 54-60)
53fn create_inventory_policy() -> InventoryPolicy {
54    Wrap::new(
55        RateLimiter::new(3, Duration::from_secs(1)), // 3 requests per second
56        Wrap::new(
57            RetryPolicy::fixed(2, Duration::from_millis(100)),
58            TimeoutPolicy::new(Duration::from_millis(500)),
59        ),
60    )
61}
62
63fn create_payment_policy() -> PaymentPolicy {
64    Wrap::new(
65        CircuitBreaker::new(3, Duration::from_secs(5)), // Opens after 3 failures
66        Wrap::new(
67            RetryPolicy::fixed(2, Duration::from_millis(200)),
68            TimeoutPolicy::new(Duration::from_secs(1)),
69        ),
70    )
71}
72
73fn create_notification_policy() -> NotificationPolicy {
74    Wrap::new(
75        Hedge::new(Duration::from_millis(100)), // Start hedge after 100ms
76        TimeoutPolicy::new(Duration::from_millis(500)),
77    )
78}
More examples
Hide additional examples
examples/http_service.rs (lines 12-15)
11async fn main() {
12    let policy = Wrap::new(
13        RetryPolicy::fixed(2, Duration::from_millis(50)),
14        TimeoutPolicy::new(Duration::from_secs(1)),
15    );
16
17    let app = Router::new().route(
18        "/",
19        get(|| async move {
20            let result: Result<&str, DoOverError<()>> =
21                policy.execute(|| async { Ok("hello from do-over") }).await;
22            result.unwrap()
23        }),
24    );
25
26    println!("Starting server on http://0.0.0.0:3000");
27    let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();
28    axum::serve(listener, app).await.unwrap();
29}
examples/composition.rs (lines 42-45)
37async fn pattern1_retry_timeout() {
38    println!("📌 Pattern 1: Retry wrapping Timeout");
39    println!("   Wrap::new(Retry(3, 100ms), Timeout(200ms))");
40    println!("   → Each retry attempt gets its own 200ms timeout\n");
41
42    let policy = Wrap::new(
43        RetryPolicy::fixed(3, Duration::from_millis(100)),
44        TimeoutPolicy::new(Duration::from_millis(200)),
45    );
46
47    let attempt_count = Arc::new(AtomicUsize::new(0));
48    let start = Instant::now();
49
50    // Scenario: First 2 attempts timeout, third succeeds quickly
51    let result: Result<String, DoOverError<String>> = {
52        let ac = Arc::clone(&attempt_count);
53        policy
54            .execute(|| {
55                let count = Arc::clone(&ac);
56                async move {
57                    let attempt = count.fetch_add(1, Ordering::SeqCst) + 1;
58                    let elapsed = start.elapsed().as_millis();
59                    println!(
60                        "   [+{:>4}ms] Attempt {}: Started",
61                        elapsed, attempt
62                    );
63
64                    if attempt < 3 {
65                        // First two attempts are slow - will timeout
66                        println!(
67                            "   [+{:>4}ms] Attempt {}: Processing slowly (will timeout)...",
68                            elapsed, attempt
69                        );
70                        tokio::time::sleep(Duration::from_millis(500)).await;
71                        Ok("Should not reach here".to_string())
72                    } else {
73                        // Third attempt is fast
74                        tokio::time::sleep(Duration::from_millis(50)).await;
75                        let elapsed = start.elapsed().as_millis();
76                        println!(
77                            "   [+{:>4}ms] Attempt {}: ✅ Completed quickly!",
78                            elapsed, attempt
79                        );
80                        Ok("Success on attempt 3".to_string())
81                    }
82                }
83            })
84            .await
85    };
86
87    let total_time = start.elapsed().as_millis();
88    println!("\n   Result: {:?}", result.unwrap());
89    println!("   Total time: {}ms", total_time);
90    println!("   Total attempts: {}", attempt_count.load(Ordering::SeqCst));
91    println!("\n   💡 Each attempt had its own timeout; operation succeeded on 3rd try");
92}
93
94async fn pattern2_circuit_retry() {
95    println!("📌 Pattern 2: CircuitBreaker wrapping Retry");
96    println!("   Wrap::new(CircuitBreaker(3, 1s), Retry(2, 50ms))");
97    println!("   → Retries happen inside the circuit breaker\n");
98
99    let policy = Wrap::new(
100        CircuitBreaker::new(3, Duration::from_secs(1)),
101        RetryPolicy::fixed(2, Duration::from_millis(50)),
102    );
103
104    let failure_count = Arc::new(AtomicUsize::new(0));
105    let start = Instant::now();
106
107    // Make several calls that fail to open the circuit
108    println!("   Phase 1: Failing calls to open circuit");
109    for i in 1..=4 {
110        let fc = Arc::clone(&failure_count);
111        let s = start;
112        let result: Result<String, DoOverError<String>> = policy
113            .execute(|| {
114                let count = Arc::clone(&fc);
115                async move {
116                    count.fetch_add(1, Ordering::SeqCst);
117                    let elapsed = s.elapsed().as_millis();
118                    println!(
119                        "   [+{:>4}ms] Call {}: Inner operation failing...",
120                        elapsed, i
121                    );
122                    Err(DoOverError::Inner(format!("Service error")))
123                }
124            })
125            .await;
126
127        let elapsed = start.elapsed().as_millis();
128        match &result {
129            Err(DoOverError::Inner(_)) => {
130                println!(
131                    "   [+{:>4}ms] Call {}: ❌ Failed after retries",
132                    elapsed, i
133                );
134            }
135            Err(DoOverError::CircuitOpen) => {
136                println!(
137                    "   [+{:>4}ms] Call {}: 🚫 Circuit is OPEN",
138                    elapsed, i
139                );
140            }
141            _ => {}
142        }
143    }
144
145    let inner_calls = failure_count.load(Ordering::SeqCst);
146    println!("\n   Summary:");
147    println!("   - Inner operation calls: {} (includes retries)", inner_calls);
148    println!("   - Circuit opened after threshold reached");
149    println!("\n   💡 Retries happened inside circuit breaker; failures accumulated to open circuit");
150}
151
152async fn pattern3_full_stack() {
153    println!("📌 Pattern 3: Full Resilience Stack");
154    println!("   Bulkhead(2) → Retry(2, 100ms) → Timeout(500ms)");
155    println!("   Recommended ordering for production systems\n");
156
157    // Note: Using Bulkhead + Retry + Timeout for this example to avoid
158    // CircuitBreaker clone issues in concurrent async context
159    let policy = Arc::new(Wrap::new(
160        Bulkhead::new(2),
161        Wrap::new(
162            RetryPolicy::fixed(2, Duration::from_millis(100)),
163            TimeoutPolicy::new(Duration::from_millis(500)),
164        ),
165    ));
166
167    let call_count = Arc::new(AtomicUsize::new(0));
168    let start = Instant::now();
169
170    println!("   Launching 4 concurrent requests (bulkhead limit is 2)...\n");
171
172    let mut handles = vec![];
173    for i in 1..=4 {
174        let p = Arc::clone(&policy);
175        let cc = Arc::clone(&call_count);
176        let s = start;
177
178        let handle = tokio::spawn(async move {
179            let result: Result<String, DoOverError<String>> = p
180                .execute(|| {
181                    let count = Arc::clone(&cc);
182                    async move {
183                        let call = count.fetch_add(1, Ordering::SeqCst) + 1;
184                        let elapsed = s.elapsed().as_millis();
185                        println!(
186                            "   [+{:>4}ms] Request {}, call {}: Processing...",
187                            elapsed, i, call
188                        );
189                        tokio::time::sleep(Duration::from_millis(200)).await;
190                        let elapsed = s.elapsed().as_millis();
191                        println!(
192                            "   [+{:>4}ms] Request {}, call {}: ✅ Done",
193                            elapsed, i, call
194                        );
195                        Ok(format!("Response {}", i))
196                    }
197                })
198                .await;
199
200            let elapsed = s.elapsed().as_millis();
201            match &result {
202                Ok(msg) => println!("   [+{:>4}ms] Request {}: ✅ {}", elapsed, i, msg),
203                Err(DoOverError::BulkheadFull) => {
204                    println!("   [+{:>4}ms] Request {}: 🚫 Rejected by bulkhead", elapsed, i)
205                }
206                Err(e) => println!("   [+{:>4}ms] Request {}: ❌ {:?}", elapsed, i, e),
207            }
208        });
209        handles.push(handle);
210
211        tokio::time::sleep(Duration::from_millis(10)).await;
212    }
213
214    for handle in handles {
215        handle.await.unwrap();
216    }
217
218    let total_time = start.elapsed().as_millis();
219    let total_calls = call_count.load(Ordering::SeqCst);
220
221    println!("\n   Summary:");
222    println!("   - Total inner operation calls: {}", total_calls);
223    println!("   - Total time: {}ms", total_time);
224    println!("\n   Policy execution order:");
225    println!("   1. Bulkhead: Limits to 2 concurrent executions");
226    println!("   2. Retry: Retries transient failures");
227    println!("   3. Timeout: Each attempt bounded to 500ms");
228}

Trait Implementations§

Source§

impl<O: Clone, I: Clone> Clone for Wrap<O, I>

Source§

fn clone(&self) -> Wrap<O, I>

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<O, I, E> Policy<E> for Wrap<O, I>
where O: Policy<E>, I: Policy<E>, E: Send + Sync,

Source§

fn execute<'life0, 'async_trait, F, Fut, T>( &'life0 self, f: F, ) -> Pin<Box<dyn Future<Output = Result<T, E>> + Send + 'async_trait>>
where F: Fn() -> Fut + Send + Sync + 'async_trait, Fut: Future<Output = Result<T, E>> + Send + 'async_trait, T: Send + 'async_trait, Self: 'async_trait, 'life0: 'async_trait,

Execute an async operation with this policy’s resilience behavior. Read more

Auto Trait Implementations§

§

impl<O, I> Freeze for Wrap<O, I>
where O: Freeze, I: Freeze,

§

impl<O, I> RefUnwindSafe for Wrap<O, I>

§

impl<O, I> Send for Wrap<O, I>
where O: Send, I: Send,

§

impl<O, I> Sync for Wrap<O, I>
where O: Sync, I: Sync,

§

impl<O, I> Unpin for Wrap<O, I>
where O: Unpin, I: Unpin,

§

impl<O, I> UnwindSafe for Wrap<O, I>
where O: UnwindSafe, I: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.