Error Handling Reference
All fallible operations in Matchy return Result<T, MatchyError>
.
MatchyError Type
#![allow(unused)] fn main() { pub enum MatchyError { /// File does not exist FileNotFound { path: String }, /// Invalid database format InvalidFormat { reason: String }, /// Corrupted database data CorruptData { offset: usize, reason: String }, /// Invalid entry (IP, pattern, string) InvalidEntry { entry: String, reason: String }, /// I/O error IoError(std::io::Error), /// Memory mapping failed MmapError(String), /// Pattern compilation failed PatternError { pattern: String, reason: String }, /// Internal error InternalError(String), } }
Common Error Patterns
Opening a Database
#![allow(unused)] fn main() { use matchy::{Database, MatchyError}; match Database::open("database.mxy") { Ok(db) => { /* success */ } Err(MatchyError::FileNotFound { path }) => { eprintln!("Database not found: {}", path); // Handle missing file - maybe create default? } Err(MatchyError::InvalidFormat { reason }) => { eprintln!("Invalid format: {}", reason); // File exists but not valid matchy database } Err(MatchyError::CorruptData { offset, reason }) => { eprintln!("Corrupted at offset {}: {}", offset, reason); // Database is damaged - rebuild required } Err(e) => { eprintln!("Unexpected error: {}", e); return Err(e.into()); } } }
Building a Database
#![allow(unused)] fn main() { use matchy::{DatabaseBuilder, MatchyError}; let mut builder = DatabaseBuilder::new(); // Add entries with error handling match builder.add_ip_entry("192.0.2.1/32", None) { Ok(_) => {} Err(MatchyError::InvalidEntry { entry, reason }) => { eprintln!("Invalid IP '{}': {}", entry, reason); // Skip this entry and continue } Err(e) => return Err(e.into()), } // Build with error handling match builder.build() { Ok(bytes) => { std::fs::write("database.mxy", &bytes)?; } Err(MatchyError::InternalError(msg)) => { eprintln!("Build failed: {}", msg); return Err(msg.into()); } Err(e) => return Err(e.into()), } }
Querying
#![allow(unused)] fn main() { use matchy::{Database, MatchyError}; let db = Database::open("database.mxy")?; match db.lookup("example.com") { Ok(Some(result)) => { println!("Found: {:?}", result); } Ok(None) => { println!("Not found"); } Err(MatchyError::CorruptData { offset, reason }) => { eprintln!("Data corruption at {}: {}", offset, reason); // Database may be partially readable } Err(e) => { eprintln!("Lookup error: {}", e); return Err(e.into()); } } }
Error Context
Use context
methods to add helpful information:
#![allow(unused)] fn main() { use matchy::Database; fn load_db(path: &str) -> Result<Database, Box<dyn std::error::Error>> { Database::open(path) .map_err(|e| format!("Failed to load database from '{}': {}", path, e).into()) } }
Or with anyhow
:
#![allow(unused)] fn main() { use anyhow::{Context, Result}; use matchy::Database; fn load_db(path: &str) -> Result<Database> { Database::open(path) .with_context(|| format!("Failed to load database from '{}'", path)) } }
Validation Errors
IP Address Validation
#![allow(unused)] fn main() { builder.add_ip_entry("not-an-ip", None)?; // Error: InvalidEntry { entry: "not-an-ip", reason: "Invalid IP address" } builder.add_ip_entry("192.0.2.1/33", None)?; // Error: InvalidEntry { entry: "192.0.2.1/33", reason: "Invalid prefix length" } }
Pattern Validation
#![allow(unused)] fn main() { builder.add_pattern_entry("*.*.com", None)?; // Error: PatternError { pattern: "*.*.com", reason: "Multiple wildcards" } builder.add_pattern_entry("[invalid", None)?; // Error: PatternError { pattern: "[invalid", reason: "Unclosed bracket" } }
String Validation
#![allow(unused)] fn main() { builder.add_exact_entry("", None)?; // Error: InvalidEntry { entry: "", reason: "Empty string" } }
Error Recovery
Partial Success
Continue after validation errors:
#![allow(unused)] fn main() { let entries = vec!["192.0.2.1", "not-valid", "10.0.0.1"]; let mut success_count = 0; let mut error_count = 0; for entry in entries { match builder.add_ip_entry(entry, None) { Ok(_) => success_count += 1, Err(e) => { eprintln!("Skipping invalid entry '{}': {}", entry, e); error_count += 1; } } } println!("Added {} entries, skipped {} invalid", success_count, error_count); }
Fallback Databases
#![allow(unused)] fn main() { let db = Database::open("primary.mxy") .or_else(|_| Database::open("backup.mxy")) .or_else(|_| Database::open("default.mxy"))?; }
Retry Logic
#![allow(unused)] fn main() { use std::time::Duration; use std::thread; fn open_with_retry(path: &str, max_attempts: u32) -> Result<Database, MatchyError> { for attempt in 1..=max_attempts { match Database::open(path) { Ok(db) => return Ok(db), Err(MatchyError::IoError(_)) if attempt < max_attempts => { eprintln!("Attempt {} failed, retrying...", attempt); thread::sleep(Duration::from_millis(100 * attempt as u64)); } Err(e) => return Err(e), } } unreachable!() } }
Display Implementation
All errors implement Display
:
#![allow(unused)] fn main() { use matchy::MatchyError; let err = MatchyError::FileNotFound { path: "missing.mxy".to_string() }; println!("{}", err); // Output: Database file not found: missing.mxy eprintln!("Error: {}", err); // Stderr: Error: Database file not found: missing.mxy }
Error Conversion
To std::io::Error
#![allow(unused)] fn main() { impl From<MatchyError> for std::io::Error { fn from(err: MatchyError) -> Self { match err { MatchyError::FileNotFound { path } => { std::io::Error::new( std::io::ErrorKind::NotFound, format!("Database not found: {}", path) ) } MatchyError::IoError(e) => e, _ => std::io::Error::new(std::io::ErrorKind::Other, err.to_string()), } } } }
To Box
#![allow(unused)] fn main() { fn do_work() -> Result<(), Box<dyn std::error::Error>> { let db = Database::open("db.mxy")?; // MatchyError automatically converts Ok(()) } }
Best Practices
1. Match Specific Errors First
#![allow(unused)] fn main() { match db.lookup(query) { Ok(Some(result)) => { /* handle result */ } Ok(None) => { /* handle not found */ } Err(MatchyError::CorruptData { .. }) => { /* handle corruption */ } Err(e) => { /* generic handler */ } } }
2. Provide Context
#![allow(unused)] fn main() { builder.add_ip_entry(ip, data) .map_err(|e| format!("Failed to add IP '{}': {}", ip, e))?; }
3. Log Errors
#![allow(unused)] fn main() { use log::{error, warn}; match Database::open(path) { Ok(db) => db, Err(e) => { error!("Failed to open database '{}': {}", path, e); return Err(e.into()); } } }
4. Use Result Type Aliases
#![allow(unused)] fn main() { type Result<T> = std::result::Result<T, MatchyError>; fn my_function() -> Result<Database> { Database::open("database.mxy") } }
Complete Example
use matchy::{Database, DatabaseBuilder, MatchyError}; use std::fs; fn main() -> Result<(), Box<dyn std::error::Error>> { // Try to open existing database let db = match Database::open("cache.mxy") { Ok(db) => { println!("Loaded existing database"); db } Err(MatchyError::FileNotFound { .. }) => { println!("Building new database..."); build_database()? } Err(e) => { eprintln!("Error opening database: {}", e); return Err(e.into()); } }; // Query with error handling let queries = vec!["192.0.2.1", "example.com", "*.google.com"]; for query in queries { match db.lookup(query) { Ok(Some(result)) => { println!("{}: {:?}", query, result); } Ok(None) => { println!("{}: Not found", query); } Err(e) => { eprintln!("{}: Error - {}", query, e); } } } Ok(()) } fn build_database() -> Result<Database, Box<dyn std::error::Error>> { let mut builder = DatabaseBuilder::new(); // Add entries with individual error handling let entries = vec![ ("192.0.2.1", "Valid IP"), ("not-an-ip", "Invalid - will skip"), ("10.0.0.0/8", "Valid CIDR"), ]; for (entry, description) in entries { match builder.add_ip_entry(entry, None) { Ok(_) => println!("Added: {} ({})", entry, description), Err(e) => eprintln!("Skipped: {} - {}", entry, e), } } // Build and save let db_bytes = builder.build()?; fs::write("cache.mxy", &db_bytes)?; // Reopen Database::open("cache.mxy").map_err(Into::into) }
See Also
- DatabaseBuilder - Building with validation
- Database Querying - Query errors
- Rust Error Handling