Classify Retryable Errors with an Enum

Model transport, timeout, and validation failures in one error enum, then decide retryability without matching on strings.

RustResilience

Code

snippet
use std::error::Error;
use std::fmt;

#[derive(Debug)]
enum AppError {
    Transport { endpoint: String, message: String },
    Timeout { after_ms: u64 },
    Validation { field: &'static str, message: String },
}

impl fmt::Display for AppError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::Transport { endpoint, message } => {
                write!(formatter, "transport error for {endpoint}: {message}")
            }
            AppError::Timeout { after_ms } => {
                write!(formatter, "timed out after {after_ms} ms")
            }
            AppError::Validation { field, message } => {
                write!(formatter, "invalid {field}: {message}")
            }
        }
    }
}

impl Error for AppError {}

impl AppError {
    fn is_retryable(&self) -> bool {
        matches!(
            self,
            AppError::Transport { .. } | AppError::Timeout { .. }
        )
    }
}

fn classify(error: &AppError) -> &'static str {
    if error.is_retryable() {
        "retry with backoff"
    } else {
        "return to caller"
    }
}

fn main() {
    let errors = [
        AppError::Transport {
            endpoint: "/orders".to_string(),
            message: "connection reset".to_string(),
        },
        AppError::Timeout { after_ms: 250 },
        AppError::Validation {
            field: "email",
            message: "missing @".to_string(),
        },
    ];
    for error in &errors {
        println!("{error} -> {}", classify(error));
        if let Some(source) = error.source() {
            println!("  source: {source}");
        }
    }
}