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 Reference

Matchy exposes a small set of public error types. Builder workflows use MatchyError or component errors such as FormatError; database opening and querying use DatabaseError.

Public Error Types

#![allow(unused)]
fn main() {
pub enum MatchyError {
    Paraglob(matchy_paraglob::error::ParaglobError),
    Format(matchy_format::FormatError),
    Io(std::io::Error),
    Database(String),
    Validation(String),
}
}
#![allow(unused)]
fn main() {
pub enum DatabaseError {
    Io(String),
    Format(matchy_format::mmdb::MmdbError),
    Unsupported(String),
    Config(String),
}
}

DatabaseBuilder methods are re-exported from matchy-format and return FormatError directly. The ? operator can still convert those errors into Box<dyn std::error::Error> in examples and applications.

Opening A Database

#![allow(unused)]
fn main() {
use matchy::{Database, DatabaseError};

match Database::from("database.mxy").open() {
    Ok(db) => {
        println!("Loaded database");
    }
    Err(DatabaseError::Io(msg)) => {
        eprintln!("File or mmap error: {msg}");
    }
    Err(DatabaseError::Format(err)) => {
        eprintln!("Invalid database format: {err}");
    }
    Err(err) => {
        eprintln!("Database error: {err}");
    }
}
}

Building A Database

#![allow(unused)]
fn main() {
use matchy::{DatabaseBuilder, DataValue, MatchMode};
use std::collections::HashMap;

let mut builder = DatabaseBuilder::new(MatchMode::CaseInsensitive);

let mut data = HashMap::new();
data.insert("category".to_string(), DataValue::String("malware".to_string()));

match builder.add_entry("*.evil.com", data) {
    Ok(()) => {}
    Err(err) => {
        eprintln!("Entry was rejected: {err}");
    }
}
}

Schema validation errors are also reported when entries are added:

#![allow(unused)]
fn main() {
use matchy::{DatabaseBuilder, DatabaseBuilderExt, DataValue, MatchMode};
use std::collections::HashMap;

let mut builder = DatabaseBuilder::new(MatchMode::CaseInsensitive)
    .with_schema("threatdb")?;

let mut data = HashMap::new();
data.insert("threat_level".to_string(), DataValue::String("high".to_string()));

if let Err(err) = builder.add_entry("192.0.2.1", data) {
    eprintln!("ThreatDB schema validation failed: {err}");
}
}

Querying

#![allow(unused)]
fn main() {
use matchy::{Database, QueryResult};

let db = Database::from("database.mxy").open()?;

match db.lookup("example.com") {
    Ok(Some(QueryResult::NotFound)) | Ok(None) => {
        println!("No match");
    }
    Ok(Some(result)) => {
        println!("Found: {result:?}");
    }
    Err(err) => {
        eprintln!("Lookup error: {err}");
    }
}
}

Ok(None) means the database has no applicable lookup table for the query type. For example, an IP query against a string-only database can return None. Misses in an existing table are represented by QueryResult::NotFound.

Adding Context

With standard error handling:

#![allow(unused)]
fn main() {
use matchy::Database;

fn load_db(path: &str) -> Result<Database, Box<dyn std::error::Error>> {
    Database::from(path)
        .open()
        .map_err(|err| format!("failed to load database from {path}: {err}").into())
}
}

With anyhow:

#![allow(unused)]
fn main() {
use anyhow::{Context, Result};
use matchy::Database;

fn load_db(path: &str) -> Result<Database> {
    Database::from(path)
        .open()
        .with_context(|| format!("failed to load database from {path}"))
}
}

Retry Logic

#![allow(unused)]
fn main() {
use matchy::{Database, DatabaseError};
use std::thread;
use std::time::Duration;

fn open_with_retry(path: &str, max_attempts: u32) -> Result<Database, DatabaseError> {
    for attempt in 1..=max_attempts {
        match Database::from(path).open() {
            Ok(db) => return Ok(db),
            Err(DatabaseError::Io(_)) if attempt < max_attempts => {
                thread::sleep(Duration::from_millis(100 * u64::from(attempt)));
            }
            Err(err) => return Err(err),
        }
    }
    unreachable!()
}
}

Complete Example

use matchy::{Database, DatabaseBuilder, DataValue, MatchMode, QueryResult};
use std::collections::HashMap;
use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let db = match Database::from("cache.mxy").open() {
        Ok(db) => db,
        Err(_) => build_database()?,
    };

    for query in ["192.0.2.1", "example.com", "login.evil.com"] {
        match db.lookup(query) {
            Ok(Some(QueryResult::NotFound)) | Ok(None) => {
                println!("{query}: no match");
            }
            Ok(Some(result)) => {
                println!("{query}: {result:?}");
            }
            Err(err) => {
                eprintln!("{query}: {err}");
            }
        }
    }

    Ok(())
}

fn build_database() -> Result<Database, Box<dyn std::error::Error>> {
    let mut builder = DatabaseBuilder::new(MatchMode::CaseInsensitive);

    let mut ip_data = HashMap::new();
    ip_data.insert("description".to_string(), DataValue::String("test IP".to_string()));
    builder.add_entry("192.0.2.1", ip_data)?;

    let mut pattern_data = HashMap::new();
    pattern_data.insert("category".to_string(), DataValue::String("phishing".to_string()));
    builder.add_entry("*.evil.com", pattern_data)?;

    let db_bytes = builder.build()?;
    fs::write("cache.mxy", &db_bytes)?;

    Ok(Database::from("cache.mxy").open()?)
}

See Also