do-over 0.1.0

Async resilience policies for Rust inspired by Polly
Documentation
//! Circuit Breaker Example
//!
//! This example demonstrates the CircuitBreaker policy with state transitions:
//! Closed → Open → Half-Open → Closed (recovery)

use do_over::{circuit_breaker::CircuitBreaker, error::DoOverError, policy::Policy};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;

#[tokio::main]
async fn main() {
    println!("=== Do-Over Circuit Breaker Example ===\n");
    println!("Configuration: failure_threshold=3, reset_timeout=2s\n");

    // Create a circuit breaker that opens after 3 failures and resets after 2 seconds
    let breaker = CircuitBreaker::new(3, Duration::from_secs(2));
    let failure_mode = Arc::new(AtomicUsize::new(1)); // 1 = fail, 0 = succeed

    // Phase 1: Trigger failures to open the circuit
    phase1_trigger_failures(&breaker, &failure_mode).await;

    println!("\n{}\n", "".repeat(60));

    // Phase 2: Show requests rejected while circuit is open
    phase2_circuit_open(&breaker).await;

    println!("\n{}\n", "".repeat(60));

    // Phase 3: Wait for reset timeout, circuit transitions to half-open
    phase3_half_open(&breaker, &failure_mode).await;

    println!("\n{}\n", "".repeat(60));

    // Phase 4: Recovery - circuit closes after success
    phase4_recovery(&breaker, &failure_mode).await;
}

async fn phase1_trigger_failures(
    breaker: &CircuitBreaker,
    failure_mode: &Arc<AtomicUsize>,
) {
    println!("📌 Phase 1: Triggering failures to open the circuit");
    println!("   State: CLOSED (allowing requests)\n");

    for i in 1..=4 {
        let fm = Arc::clone(failure_mode);
        let result: Result<String, DoOverError<String>> = breaker
            .execute(|| {
                let mode = Arc::clone(&fm);
                async move {
                    if mode.load(Ordering::SeqCst) == 1 {
                        Err(DoOverError::Inner("Service error".to_string()))
                    } else {
                        Ok("Success".to_string())
                    }
                }
            })
            .await;

        match &result {
            Err(DoOverError::Inner(e)) => {
                println!("   Request {}: ❌ Failed - {}", i, e);
                if i == 3 {
                    println!("   → Failure threshold reached! Circuit is now OPEN");
                }
            }
            Err(DoOverError::CircuitOpen) => {
                println!("   Request {}: 🚫 Rejected - Circuit is OPEN", i);
            }
            Ok(_) => println!("   Request {}: ✅ Success", i),
            Err(e) => println!("   Request {}: Error - {:?}", i, e),
        }
    }
}

async fn phase2_circuit_open(breaker: &CircuitBreaker) {
    println!("📌 Phase 2: Circuit is OPEN - Requests are rejected immediately");
    println!("   (No actual service calls are made)\n");

    for i in 1..=3 {
        let result: Result<String, DoOverError<String>> = breaker
            .execute(|| async {
                // This should not be called when circuit is open
                println!("   ⚠️  This should not print - service was called!");
                Ok("Success".to_string())
            })
            .await;

        match result {
            Err(DoOverError::CircuitOpen) => {
                println!("   Request {}: 🚫 Instantly rejected (CircuitOpen)", i);
            }
            _ => println!("   Request {}: Unexpected result", i),
        }
    }

    println!("\n   → Requests fail fast without calling the service");
}

async fn phase3_half_open(breaker: &CircuitBreaker, failure_mode: &Arc<AtomicUsize>) {
    println!("📌 Phase 3: Waiting for reset timeout (2s)...");
    tokio::time::sleep(Duration::from_secs(2)).await;
    println!("   → Reset timeout elapsed, circuit is now HALF-OPEN\n");

    println!("   State: HALF-OPEN (testing if service recovered)");
    println!("   First request will be a test request...\n");

    // Keep failure mode on to show transition back to OPEN
    let fm = Arc::clone(failure_mode);
    let result: Result<String, DoOverError<String>> = breaker
        .execute(|| {
            let mode = Arc::clone(&fm);
            async move {
                println!("   → Test request executed (service is still failing)");
                if mode.load(Ordering::SeqCst) == 1 {
                    Err(DoOverError::Inner("Service still down".to_string()))
                } else {
                    Ok("Success".to_string())
                }
            }
        })
        .await;

    match result {
        Err(DoOverError::Inner(e)) => {
            println!("   Test request: ❌ Failed - {}", e);
            println!("   → Circuit returns to OPEN state");
        }
        _ => println!("   Test request: {:?}", result),
    }
}

async fn phase4_recovery(breaker: &CircuitBreaker, failure_mode: &Arc<AtomicUsize>) {
    println!("📌 Phase 4: Service Recovery");
    println!("   Waiting for reset timeout again (2s)...");
    tokio::time::sleep(Duration::from_secs(2)).await;

    // Switch to success mode
    failure_mode.store(0, Ordering::SeqCst);
    println!("   → Service has recovered! Switching to success mode\n");

    println!("   State: HALF-OPEN (testing again)");

    let fm = Arc::clone(failure_mode);
    let result: Result<String, DoOverError<String>> = breaker
        .execute(|| {
            let mode = Arc::clone(&fm);
            async move {
                if mode.load(Ordering::SeqCst) == 1 {
                    Err(DoOverError::Inner("Service error".to_string()))
                } else {
                    Ok("Service recovered!".to_string())
                }
            }
        })
        .await;

    match result {
        Ok(msg) => {
            println!("   Test request: ✅ {}", msg);
            println!("   → Circuit is now CLOSED (back to normal operation)");
        }
        Err(e) => println!("   Test request: {:?}", e),
    }

    // Verify circuit is closed
    println!("\n   Verifying circuit is fully operational:");
    for i in 1..=3 {
        let result: Result<String, DoOverError<String>> = breaker
            .execute(|| async { Ok(format!("Response {}", i)) })
            .await;
        match result {
            Ok(msg) => println!("   Request {}: ✅ {}", i, msg),
            Err(e) => println!("   Request {}: {:?}", i, e),
        }
    }

    println!("\n   ✨ Circuit breaker has fully recovered!");
}