Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Error Handling Patterns

Common patterns for error handling with rok-utils.

Propagating Errors

#![allow(unused)]
fn main() {
use rok_utils::{RokError, RokResultExt};

fn read_config(path: &str) -> Result<String, RokError> {
    std::fs::read_to_string(path)
        .context("Failed to read config file")
        .map_err(|e| RokError::Wrapped {
            message: "Configuration error".to_string(),
            source: Box::new(e),
        })
}
}

Fallback Values

#![allow(unused)]
fn main() {
use rok_utils::{RokError, RokResultExt};

fn get_setting(key: &str) -> Result<String, RokError> {
    // Try to get from config
    Err(RokError::NotFound(key.to_string()))
}

fn get_with_default(key: &str, default: &str) -> String {
    get_setting(key)
        .map_err(|_| {
            // Log warning but don't fail
            eprintln!("Setting {} not found, using default", key);
        })
        .unwrap_or_else(|_| default.to_string())
}

assert_eq!(get_with_default("app_name", "MyApp"), "MyApp");
}

Mapping Errors

#![allow(unused)]
fn main() {
use rok_utils::{RokError, RokResultExt};

fn parse_number(input: &str) -> Result<i32, RokError> {
    input
        .trim()
        .parse::<i32>()
        .map_err(|_| RokError::ValidationFailure {
            field: "number".to_string(),
            reason: format!("'{}' is not a valid number", input),
        })
}
}

Collecting Errors

#![allow(unused)]
fn main() {
use rok_utils::RokError;
use std::collections::VecDeque;

#[derive(Debug)]
struct ValidationErrors {
    errors: VecDeque<RokError>,
}

impl ValidationErrors {
    fn new() -> Self {
        Self {
            errors: VecDeque::new(),
        }
    }

    fn add(&mut self, field: &str, reason: &str) {
        self.errors.push_back(RokError::ValidationFailure {
            field: field.to_string(),
            reason: reason.to_string(),
        });
    }

    fn is_empty(&self) -> bool {
        self.errors.is_empty()
    }

    fn into_result<T>(self, value: T) -> Result<T, RokError> {
        if let Some(first) = self.errors.into_iter().next() {
            Err(first)
        } else {
            Ok(value)
        }
    }
}

fn validate_form(name: &str, email: &str) -> Result<(String, String), RokError> {
    let mut errors = ValidationErrors::new();

    if name.trim().is_empty() {
        errors.add("name", "Name is required");
    }

    if !email.contains('@') {
        errors.add("email", "Invalid email format");
    }

    errors.into_result((name.to_string(), email.to_string()))
}
}

Retry with Backoff

#![allow(unused)]
fn main() {
use rok_utils::{RokError, fp::retry};
use std::time::Duration;

fn fetch_with_retry(url: &str, max_attempts: usize) -> Result<String, RokError> {
    let mut last_error = None;

    for attempt in 0..max_attempts {
        match fetch_url(url) {
            Ok(response) => return Ok(response),
            Err(e) => {
                last_error = Some(e);
                if attempt < max_attempts - 1 {
                    std::thread::sleep(Duration::from_millis(100 * (attempt + 1) as u64));
                }
            }
        }
    }

    Err(last_error.unwrap_or(RokError::Internal("Unknown error".to_string())))
}

fn fetch_url(_url: &str) -> Result<String, RokError> {
    // Simulated fetch
    Err(RokError::Internal("Not implemented".to_string()))
}
}

Contextual Errors

#![allow(unused)]
fn main() {
use rok_utils::{RokError, RokResultExt};

fn process_user(id: u64) -> Result<User, RokError> {
    let user = find_user(id)
        .context(format!("Looking up user #{}", id))?
        .or_not_found(&format!("User #{}", id))?;

    let profile = load_profile(&user)
        .context(format!("Loading profile for user #{}", id))?;

    Ok(User { profile, ..user })
}
}