waddling-errors 0.7.3

Structured, secure-by-default diagnostic codes for distributed systems with no_std and role-based documentation
Documentation
//! Integration with thiserror and anyhow (Manual Approach)
//!
//! This example demonstrates how waddling-errors complements popular error
//! handling libraries using the manual trait-based approach.
//!
//! For the macro approach (recommended), see:
//! `waddling-errors-macros/examples/integration_with_error_handling.rs`
//!
//! ## What Each Library Does
//!
//! - **waddling-errors**: Error code catalog with documentation (WHAT went wrong)
//! - **thiserror**: Rust Error trait implementation (HOW to represent errors)
//! - **anyhow**: Error propagation and context chains (WHERE errors occur)
//!
//! ## Running
//!
//! ```bash
//! cargo run --example integration_with_error_handling
//! ```

use waddling_errors::prelude::*;

// Step 1: Define error structure with manual traits
// ==================================================

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Component {
    Auth,
    Database,
}

impl ComponentId for Component {
    fn as_str(&self) -> &'static str {
        match self {
            Component::Auth => "AUTH",
            Component::Database => "DB",
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Primary {
    Token,
    Connection,
}

impl PrimaryId for Primary {
    fn as_str(&self) -> &'static str {
        match self {
            Primary::Token => "TOKEN",
            Primary::Connection => "CONN",
        }
    }
}

// Define error code constants
const TOKEN_EXPIRED: Code<Component, Primary> = Code::error(Component::Auth, Primary::Token, 1);
const TOKEN_INVALID: Code<Component, Primary> = Code::error(Component::Auth, Primary::Token, 3);
const DB_TIMEOUT: Code<Component, Primary> =
    Code::error(Component::Database, Primary::Connection, 17);

// Step 2: Use thiserror to create ergonomic error types
// ======================================================
// Note: This example shows the pattern without actually using thiserror/anyhow
// to avoid adding dependencies. In real code, you'd add:
// [dependencies]
// thiserror = "1.0"
// anyhow = "1.0"

use std::fmt;

#[derive(Debug)]
pub enum AppError {
    Auth {
        message: String,
        code: String,
        source: Option<Box<dyn std::error::Error + Send + Sync>>,
    },

    Database {
        message: String,
        code: String,
        source: Option<Box<dyn std::error::Error + Send + Sync>>,
    },

    Validation(String),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Auth { message, code, .. } => write!(f, "{}: {}", code, message),
            Self::Database { message, code, .. } => write!(f, "{}: {}", code, message),
            Self::Validation(msg) => write!(f, "Validation error: {}", msg),
        }
    }
}

impl std::error::Error for AppError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::Auth {
                source: Some(err), ..
            }
            | Self::Database {
                source: Some(err), ..
            } => Some(err.as_ref()),
            _ => None,
        }
    }
}

// In real code with thiserror, this would be:
// #[derive(Error, Debug)]
// pub enum AppError {
//     #[error("{code}: {message}")]
//     Auth {
//         message: String,
//         code: String,
//         #[source]
//         source: Option<Box<dyn std::error::Error + Send + Sync>>,
//     },
//     ...
// }

impl AppError {
    /// Create error from waddling-errors Code
    pub fn from_auth_code(code: Code<Component, Primary>, message: String) -> Self {
        Self::Auth {
            message,
            code: code.code(),
            source: None,
        }
    }

    pub fn from_db_code(code: Code<Component, Primary>, message: String) -> Self {
        Self::Database {
            message,
            code: code.code(),
            source: None,
        }
    }

    /// Get the waddling-errors code from this error
    pub fn error_code(&self) -> Option<&str> {
        match self {
            Self::Auth { code, .. } | Self::Database { code, .. } => Some(code),
            Self::Validation(_) => None,
        }
    }
}

// Step 3: Use anyhow-style Result for easy error propagation
// ===========================================================
// In real code with anyhow, you'd use anyhow::Result and .context()

type Result<T> = std::result::Result<T, AppError>;

/// Simulate token validation
fn validate_token(token: &str) -> Result<()> {
    if token.is_empty() {
        return Err(AppError::from_auth_code(
            TOKEN_INVALID,
            "Token is empty".to_string(),
        ));
    }

    if token == "expired" {
        return Err(AppError::from_auth_code(
            TOKEN_EXPIRED,
            "Token has expired".to_string(),
        ));
    }

    Ok(())
}

/// Simulate database connection
fn connect_database(timeout_ms: u32) -> Result<()> {
    if timeout_ms < 100 {
        return Err(AppError::from_db_code(
            DB_TIMEOUT,
            format!("Connection timeout after {}ms", timeout_ms),
        ));
    }

    Ok(())
}

/// Business logic that propagates errors
fn authenticate_user(token: &str) -> Result<String> {
    validate_token(token)?;
    connect_database(500)?;

    Ok("user123".to_string())
}

/// API endpoint simulation
fn handle_request(token: &str) -> Result<String> {
    authenticate_user(token)
}

fn main() {
    println!("╔════════════════════════════════════════════════════════════════╗");
    println!("║  Integration Example: waddling-errors + thiserror + anyhow    ║");
    println!("║  (Manual Approach)                                             ║");
    println!("╚════════════════════════════════════════════════════════════════╝\n");

    // Example 1: Invalid token
    println!("Example 1: Invalid Token");
    println!("─────────────────────────");
    match handle_request("") {
        Ok(user) => println!("✅ Authenticated user: {}", user),
        Err(e) => {
            println!("❌ Error: {}", e);

            if let Some(code) = e.error_code() {
                println!("   📊 Error code for monitoring: {}", code);
                println!("   (Send this to Sentry/DataDog/etc.)");
            }

            if let Some(source) = std::error::Error::source(&e) {
                println!("   Caused by: {}", source);
            }
        }
    }

    println!("\n");

    // Example 2: Expired token
    println!("Example 2: Expired Token");
    println!("────────────────────────");
    match handle_request("expired") {
        Ok(user) => println!("✅ Authenticated user: {}", user),
        Err(e) => {
            println!("❌ Error: {}", e);
            if let Some(code) = e.error_code() {
                println!("   📊 Error code: {}", code);
            }
        }
    }

    println!("\n");

    // Example 3: Success
    println!("Example 3: Valid Token");
    println!("──────────────────────");
    match handle_request("valid_token_abc123") {
        Ok(user) => println!("✅ Authenticated user: {}", user),
        Err(e) => println!("❌ Error: {}", e),
    }

    println!("\n");

    // Show error codes
    println!("╔════════════════════════════════════════════════════════════════╗");
    println!("║  Error Code Catalog                                            ║");
    println!("╚════════════════════════════════════════════════════════════════╝\n");

    println!("Available error codes:");
    println!("{} - Token expired", TOKEN_EXPIRED.code());
    println!("{} - Token invalid", TOKEN_INVALID.code());
    println!("{} - Database timeout", DB_TIMEOUT.code());

    println!("\n╔════════════════════════════════════════════════════════════════╗");
    println!("║  Summary: How They Work Together                              ║");
    println!("╚════════════════════════════════════════════════════════════════╝\n");

    println!("1. waddling-errors:");
    println!("   - Defines structured error codes (E.AUTH.TOKEN.001)");
    println!("   - Type-safe with ComponentId and PrimaryId traits");
    println!("   - Provides codes for monitoring/observability\n");

    println!("2. thiserror (simulated here):");
    println!("   - Implements Display and Error traits");
    println!("   - Provides ergonomic error types with #[error] attribute");
    println!("   - Handles error source chains with #[source]\n");

    println!("3. anyhow (simulated here):");
    println!("   - Makes error propagation easy with ?");
    println!("   - Adds context to errors with .context()");
    println!("   - Provides nice error chain display\n");

    println!("💡 Use all three together for production-ready error handling!");
    println!("\n📝 Note: This example simulates thiserror/anyhow patterns without");
    println!("   adding dependencies. In real code, add them to Cargo.toml:");
    println!("   thiserror = \"1.0\"");
    println!("   anyhow = \"1.0\"");
    println!("\n📝 For macro approach (70% less code), see:");
    println!("   waddling-errors-macros/examples/integration_with_error_handling.rs");
}