tower-resilience-fallback 0.9.1

Fallback middleware for Tower services
Documentation

Fallback middleware for Tower services.

Provides alternative responses when the inner service fails, enabling graceful degradation and backup service routing.

Overview

The fallback pattern allows you to provide alternative responses when your primary service fails. This is useful for:

  • Returning cached or stale data when the primary source is unavailable
  • Providing degraded functionality instead of complete failure
  • Routing to backup services
  • Transforming errors into user-friendly responses

Fallback Strategies

Static Value

Return a fixed value when the service fails:

use tower_resilience_fallback::FallbackLayer;
use tower::ServiceBuilder;

# #[derive(Debug, Clone)]
# struct MyError;
let layer = FallbackLayer::<String, String, MyError>::value("default response".to_string());

Fallback Function

Compute a response based on the error:

use tower_resilience_fallback::FallbackLayer;

# #[derive(Debug, Clone)]
# struct MyError { message: String }
let layer = FallbackLayer::<String, String, MyError>::from_error(|error: &MyError| {
    format!("Service unavailable: {}", error.message)
});

Fallback with Request Context

Access both the original request and error:

use tower_resilience_fallback::FallbackLayer;
use std::sync::Arc;
use std::collections::HashMap;

# #[derive(Debug, Clone)]
# struct MyError;
let cache: Arc<HashMap<String, String>> = Arc::new(HashMap::new());
let cache_clone = Arc::clone(&cache);

let layer = FallbackLayer::<String, String, MyError>::from_request_error(move |req: &String, _err: &MyError| {
    cache_clone.get(req).cloned().unwrap_or_else(|| "not found".to_string())
});

Backup Service

Route to an alternative service (async):

use tower_resilience_fallback::FallbackLayer;

# #[derive(Debug, Clone)]
# struct MyError;
let layer = FallbackLayer::<String, String, MyError>::service(|req: String| async move {
    Ok::<_, MyError>(format!("backup: {}", req))
});

Error Transformation

Convert errors to a different type (still returns error, not success):

use tower_resilience_fallback::FallbackLayer;

# #[derive(Debug, Clone)]
# struct InternalError { code: u32 }
// Note: This changes the error type, so layer types must align
let layer = FallbackLayer::<String, String, InternalError>::exception(|err: InternalError| {
    InternalError { code: 500 } // Transform but still error
});

Selective Error Handling

Only trigger fallback for specific errors:

use tower_resilience_fallback::FallbackLayer;

# #[derive(Debug, Clone)]
# struct MyError { retryable: bool }
let layer: FallbackLayer<String, String, MyError> = FallbackLayer::builder()
    .value("fallback".to_string())
    .handle(|e: &MyError| e.retryable) // Only fallback on retryable errors
    .build();

Composition with Other Layers

Fallback works well with other resilience patterns:

use tower_resilience_fallback::FallbackLayer;
use tower::ServiceBuilder;

# #[derive(Debug, Clone)]
# struct MyError;
# async fn example() {
// Fallback catches anything that escapes retry + circuit breaker
let service = ServiceBuilder::new()
    .layer(FallbackLayer::<String, String, MyError>::value("unavailable".to_string()))
    // .layer(CircuitBreakerLayer::new(cb_config))
    // .layer(RetryLayer::new(retry_config))
    .service(tower::service_fn(|req: String| async move {
        Ok::<_, MyError>(req)
    }));
# }

Events

The fallback service emits events for observability:

  • Success: Inner service succeeded, no fallback needed
  • FailedAttempt: Inner service failed, fallback will be attempted
  • Applied: Fallback was successfully applied
  • Failed: Fallback itself failed (service fallback only)
  • Skipped: Error didn't match predicate, propagated as-is