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 })
}
}