Error Handling
libmagic-rs uses Rust's Result
type system for comprehensive, type-safe error handling.
Error Types
LibmagicError
The main error enum covers all library operations:
#![allow(unused)] fn main() { use thiserror::Error; #[derive(Debug, Error)] pub enum LibmagicError { #[error("Parse error at line {line}: {message}")] ParseError { line: usize, message: String }, #[error("Evaluation error: {0}")] EvaluationError(String), #[error("IO error: {0}")] IoError(#[from] std::io::Error), #[error("Invalid magic file format: {0}")] InvalidFormat(String), } }
Result Type Alias
For convenience, the library provides a type alias:
#![allow(unused)] fn main() { pub type Result<T> = std::result::Result<T, LibmagicError>; }
Error Handling Patterns
Basic Error Handling
#![allow(unused)] fn main() { use libmagic_rs::{MagicDatabase, LibmagicError}; match MagicDatabase::load_from_file("magic.db") { Ok(db) => { // Use the database println!("Loaded magic database successfully"); } Err(e) => { eprintln!("Failed to load magic database: {}", e); return; } } }
Using the ? Operator
#![allow(unused)] fn main() { fn analyze_file(path: &str) -> Result<String> { let db = MagicDatabase::load_from_file("magic.db")?; let result = db.evaluate_file(path)?; Ok(result.description) } }
Matching Specific Errors
#![allow(unused)] fn main() { use libmagic_rs::LibmagicError; match db.evaluate_file("example.bin") { Ok(result) => println!("File type: {}", result.description), Err(LibmagicError::IoError(e)) => { eprintln!("File access error: {}", e); } Err(LibmagicError::EvaluationError(msg)) => { eprintln!("Evaluation failed: {}", msg); } Err(e) => { eprintln!("Other error: {}", e); } } }
Error Context
Adding Context with map_err
#![allow(unused)] fn main() { use libmagic_rs::LibmagicError; fn load_custom_magic(path: &str) -> Result<MagicDatabase> { MagicDatabase::load_from_file(path).map_err(|e| { LibmagicError::InvalidFormat(format!( "Failed to load custom magic file '{}': {}", path, e )) }) } }
Using anyhow
for Application Errors
use anyhow::{Context, Result}; use libmagic_rs::MagicDatabase; fn main() -> Result<()> { let db = MagicDatabase::load_from_file("magic.db").context("Failed to load magic database")?; let result = db .evaluate_file("example.bin") .context("Failed to analyze file")?; println!("File type: {}", result.description); Ok(()) }
Error Recovery
Graceful Degradation
#![allow(unused)] fn main() { fn analyze_with_fallback(path: &str) -> String { match MagicDatabase::load_from_file("magic.db") { Ok(db) => match db.evaluate_file(path) { Ok(result) => result.description, Err(_) => "unknown file type".to_string(), }, Err(_) => "magic database unavailable".to_string(), } } }
Retry Logic
#![allow(unused)] fn main() { use std::thread; use std::time::Duration; fn load_with_retry(path: &str, max_attempts: u32) -> Result<MagicDatabase> { let mut attempts = 0; loop { match MagicDatabase::load_from_file(path) { Ok(db) => return Ok(db), Err(e) if attempts < max_attempts => { attempts += 1; eprintln!("Attempt {} failed: {}", attempts, e); thread::sleep(Duration::from_millis(100)); } Err(e) => return Err(e), } } } }
Best Practices
1. Use Specific Error Types
#![allow(unused)] fn main() { // Good: Specific error information Err(LibmagicError::ParseError { line: 42, message: "Invalid offset specification".to_string() }) // Avoid: Generic error messages Err(LibmagicError::EvaluationError("something went wrong".to_string())) }
2. Provide Context
#![allow(unused)] fn main() { // Good: Contextual error information fn parse_magic_file(path: &Path) -> Result<Vec<MagicRule>> { std::fs::read_to_string(path) .map_err(|e| LibmagicError::IoError(e)) .and_then(|content| parse_magic_string(&content)) } // Better: Even more context fn parse_magic_file(path: &Path) -> Result<Vec<MagicRule>> { let content = std::fs::read_to_string(path).map_err(|e| { LibmagicError::InvalidFormat(format!( "Cannot read magic file '{}': {}", path.display(), e )) })?; parse_magic_string(&content).map_err(|e| { LibmagicError::InvalidFormat(format!("Invalid magic file '{}': {}", path.display(), e)) }) } }
3. Handle Errors at the Right Level
// Library level: Return detailed errors pub fn evaluate_file<P: AsRef<Path>>(&self, path: P) -> Result<EvaluationResult> { // Detailed error handling } // Application level: Handle user-facing concerns fn main() { match analyze_file("example.bin") { Ok(description) => println!("{}", description), Err(e) => { eprintln!("Error: {}", e); std::process::exit(1); } } }
4. Document Error Conditions
#![allow(unused)] fn main() { /// Evaluate magic rules against a file /// /// # Errors /// /// This function will return an error if: /// - The file cannot be read (`IoError`) /// - The file is too large for processing (`EvaluationError`) /// - Rule evaluation encounters invalid data (`EvaluationError`) /// /// # Examples /// /// ```rust,no_run /// use libmagic_rs::MagicDatabase; /// /// let db = MagicDatabase::load_from_file("magic.db")?; /// match db.evaluate_file("example.bin") { /// Ok(result) => println!("Type: {}", result.description), /// Err(e) => eprintln!("Error: {}", e), /// } /// # Ok::<(), Box<dyn std::error::Error>>(()) /// ``` pub fn evaluate_file<P: AsRef<Path>>(&self, path: P) -> Result<EvaluationResult> { // Implementation } }
Testing Error Conditions
#![allow(unused)] fn main() { #[cfg(test)] mod tests { use super::*; #[test] fn test_missing_file_error() { let result = MagicDatabase::load_from_file("nonexistent.magic"); assert!(result.is_err()); match result { Err(LibmagicError::IoError(_)) => (), // Expected _ => panic!("Expected IoError for missing file"), } } #[test] fn test_invalid_magic_file() { let result = parse_magic_string("invalid syntax here"); assert!(result.is_err()); if let Err(LibmagicError::ParseError { line, message }) = result { assert_eq!(line, 1); assert!(message.contains("syntax")); } else { panic!("Expected ParseError for invalid syntax"); } } } }
This comprehensive error handling approach ensures libmagic-rs provides clear, actionable error information while maintaining type safety and enabling robust error recovery strategies.