decrust-core 1.2.3

Core error handling framework for Decrust
Documentation
decrust-core-1.2.3 has been yanked.

Decrust: Advanced Error Handling Framework for Rust

Decrust is a comprehensive, production-ready error handling framework that provides rich error context, automatic error recovery, circuit breaker patterns, and powerful debugging capabilities. It's designed to make error handling in Rust applications both robust and developer-friendly.

🚀 Quick Start

use decrust_core::{DecrustError, DecrustResultExt, DecrustOptionExt, oops, validation_error};

// Basic error creation with rich context
fn process_user_data(data: Option<&str>) -> Result<String, DecrustError> {
    let user_data = data.decrust_ok_or_missing_value("user data")?;

    if user_data.is_empty() {
        return Err(validation_error!("user_data", "Data cannot be empty"));
    }

    // Simulate an IO operation that might fail
    std::fs::read_to_string("config.json")
        .map_err(|e| oops!("Failed to read configuration", e))
        .and_then(|_| Ok(format!("Processed: {}", user_data)))
}

🎯 Core Features

1. Rich Error Context 📍

Every error includes comprehensive context with location tracking, severity levels, and metadata for better debugging and monitoring.

use decrust_core::{error_context, types::ErrorSeverity, oops};

// Create rich error context with metadata
let context = error_context!(
    "Database connection failed",
    severity: ErrorSeverity::Critical
).with_component("database")
 .with_correlation_id("req-123")
 .with_recovery_suggestion("Check database connectivity");

// Use in error creation
let io_error = std::io::Error::new(std::io::ErrorKind::ConnectionRefused, "Connection refused");
let error = oops!("Database unavailable", io_error, severity: ErrorSeverity::Critical);

2. Circuit Breaker Pattern

Built-in circuit breaker for handling external service failures gracefully.

use decrust_core::{circuit_breaker::{CircuitBreaker, CircuitBreakerConfig}, DecrustError, Backtrace};
use std::time::Duration;

// Configure circuit breaker
let config = CircuitBreakerConfig {
    failure_threshold: 5,
    reset_timeout: Duration::from_secs(30),
    operation_timeout: Some(Duration::from_secs(5)),
    ..Default::default()
};

let circuit_breaker = CircuitBreaker::new("external-api", config);

// Execute operations through circuit breaker
# fn external_api_call() -> Result<String, std::io::Error> { Ok("success".to_string()) }
let result = circuit_breaker.execute(|| {
    // Your external service call here
    external_api_call().map_err(|e| DecrustError::Oops {
        message: "API call failed".to_string(),
        source: Box::new(e),
        backtrace: Backtrace::generate(),
    })
});

3. Automatic Error Recovery 🔄

Smart error recovery with configurable retry strategies and fix suggestions.

use decrust_core::{DecrustError, decrust::{Decrust, AutocorrectableError}, Backtrace};

let mut decrust = Decrust::new();

// Register custom fix generators
// decrust.register_fix_generator(Box::new(CustomFixGenerator::new()));

// Apply fixes automatically
# let error = DecrustError::Validation {
#     field: "test".to_string(),
#     message: "test".to_string(),
#     expected: None,
#     actual: None,
#     rule: None,
#     backtrace: Backtrace::generate()
# };
if let Some(fix) = decrust.suggest_autocorrection(&error, None) {
    println!("Suggested fix: {}", fix.description);
}

4. Powerful Macros 🛠️

Ergonomic macros for common error handling patterns.

use decrust_core::{oops, validation_error, error_context, location, types::ErrorSeverity};

// Quick error creation
# let source_error = std::io::Error::new(std::io::ErrorKind::Other, "test");
let error = oops!("Something went wrong", source_error);

// Validation errors with suggestions
let validation_err = validation_error!(
    "email",
    "Invalid email format",
    suggestion: "Use format: user@domain.com"
);

// Rich context with location tracking
let context = error_context!("Operation failed", severity: ErrorSeverity::Error);
let loc = location!(context: "user authentication", function: "login");

5. Comprehensive Error Types 📋

Pre-built error variants for common scenarios with rich metadata.

use decrust_core::{DecrustError, Backtrace, OptionalError};
use std::time::Duration;

// Network errors with retry information
let network_error = DecrustError::Network {
    source: Box::new(std::io::Error::new(std::io::ErrorKind::ConnectionRefused, "refused")),
    kind: "HTTP".to_string(),
    url: Some("https://api.example.com".to_string()),
    backtrace: Backtrace::generate(),
};

// Configuration errors with suggestions
let config_error = DecrustError::Config {
    message: "Invalid database URL format".to_string(),
    path: Some("config.toml".into()),
    source: OptionalError(None),
    backtrace: Backtrace::generate(),
};

🔧 Advanced Usage Patterns

Creating Custom Error Types

You can create domain-specific error types that integrate seamlessly with Decrust:

use decrust_core::{DecrustError, DecrustResultExt, types::ErrorSeverity, Backtrace, OptionalError};

# struct User;
// Define your domain-specific error
#[derive(Debug)]
pub enum UserServiceError {
    NotFound { id: String },
    InvalidEmail { email: String },
    PermissionDenied { user_id: String },
}

impl std::fmt::Display for UserServiceError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            UserServiceError::NotFound { id } => write!(f, "User not found: {}", id),
            UserServiceError::InvalidEmail { email } => write!(f, "Invalid email format: {}", email),
            UserServiceError::PermissionDenied { user_id } => write!(f, "Permission denied for user: {}", user_id),
        }
    }
}

impl std::error::Error for UserServiceError {}

// Convert to DecrustError with rich context
impl From<UserServiceError> for DecrustError {
    fn from(err: UserServiceError) -> Self {
        match err {
            UserServiceError::NotFound { id } => DecrustError::NotFound {
                resource_type: "User".to_string(),
                identifier: id,
                backtrace: Backtrace::generate(),
            },
            UserServiceError::InvalidEmail { email } => DecrustError::Validation {
                field: "email".to_string(),
                message: format!("Invalid email format: {}", email),
                expected: None,
                actual: None,
                rule: None,
                backtrace: Backtrace::generate(),
            },
            UserServiceError::PermissionDenied { user_id } => {
                DecrustError::ExternalService {
                    service_name: "UserService".to_string(),
                    message: format!("Permission denied for user: {}", user_id),
                    source: OptionalError(None),
                    backtrace: Backtrace::generate(),
                }
            }
        }
    }
}

// Usage in your application
fn get_user(id: &str) -> Result<User, DecrustError> {
    // Your business logic here
    if id.is_empty() {
        return Err(UserServiceError::NotFound { id: id.to_string() }.into());
    }

    // Add rich context to any errors
    database_call()
        .map_err(|e| DecrustError::Oops {
            message: "Database query failed".to_string(),
            source: Box::new(e),
            backtrace: Backtrace::generate(),
        })
        .decrust_context_msg("Fetching user from database")?;

    Ok(User)
}
# fn database_call() -> Result<(), std::io::Error> { Ok(()) }

Error Reporting and Monitoring

use decrust_core::{ErrorReporter, ErrorReportConfig, types::ErrorReportFormat, DecrustError, Backtrace};

// Configure error reporting
let config = ErrorReportConfig {
    format: ErrorReportFormat::Json,
    include_backtrace: true,
    include_rich_context: true,
    ..Default::default()
};

let reporter = ErrorReporter::new();

// Report errors with rich context
# let error = DecrustError::Validation {
#     field: "test".to_string(),
#     message: "test".to_string(),
#     expected: None,
#     actual: None,
#     rule: None,
#     backtrace: Backtrace::generate()
# };
let report = reporter.report_to_string(&error, &config);
println!("Error Report: {}", report);

Circuit Breaker with Custom Policies

use decrust_core::{circuit_breaker::{CircuitBreaker, CircuitBreakerConfig}, DecrustError};
use std::time::Duration;

// Advanced circuit breaker configuration
let config = CircuitBreakerConfig {
    failure_threshold: 3,                    // Open after 3 failures
    success_threshold_to_close: 2,           // Close after 2 successes in half-open
    reset_timeout: Duration::from_secs(60),  // Try half-open after 60 seconds
    operation_timeout: Some(Duration::from_secs(10)), // Individual operation timeout
    half_open_max_concurrent_operations: 1, // Only 1 operation in half-open
    ..Default::default()
};

let circuit_breaker = CircuitBreaker::new("payment-service", config);

// Use with async operations (when std-thread feature is enabled)
let result = circuit_breaker.execute(|| {
    // Your potentially failing operation
    call_payment_service()
});

match result {
    Ok(response) => println!("Payment successful: {:?}", response),
    Err(DecrustError::CircuitBreakerOpen { retry_after, .. }) => {
        println!("Circuit breaker is open, retry after: {:?}", retry_after);
    }
    Err(e) => println!("Payment failed: {}", e),
}
# fn call_payment_service() -> Result<String, DecrustError> { Ok("success".to_string()) }

Object-Safe Extension Trait Usage

The extension traits are object-safe and support dynamic dispatch:

use decrust_core::{DecrustResultExt, DecrustOptionExt, DecrustError};

// Object-safe trait usage with dynamic dispatch
fn process_with_dyn_traits(
    result: &dyn DecrustResultExt<String, std::io::Error>,
    option: &dyn DecrustOptionExt<i32>
) {
    // These work because the traits are object-safe
}

// Regular usage for better error handling
fn process_data() -> Result<String, DecrustError> {
    let result: Result<String, std::io::Error> = Ok("test".to_string());
    let option: Option<i32> = Some(42);

    // Add context to results (object-safe methods)
    let processed = result.decrust_context_msg("Processing data")?;

    // Convert options to results with meaningful errors (object-safe methods)
    let value = option.decrust_ok_or_missing_value("required value")?;

    Ok(format!("{} - {}", processed, value))
}

📚 Feature Flags

  • std-thread: Enables threading support for circuit breaker timeouts
  • serde: Enables serialization support for error types
  • tracing: Enables integration with the tracing ecosystem

🎨 Best Practices

  1. Use specific error variants for different error categories
  2. Add rich context with decrust_context_msg() for better debugging
  3. Implement circuit breakers for external service calls
  4. Use macros for common error patterns to reduce boilerplate
  5. Configure error reporting for production monitoring
  6. Create domain-specific error types that convert to DecrustError

🔗 Integration Examples

With Tokio and Async

use decrust_core::{DecrustError, DecrustResultExt, Backtrace};
use std::path::PathBuf;

// Simulate async file reading without requiring tokio dependency
async fn read_config_async() -> Result<String, DecrustError> {
    // Simulate reading a config file
    let result = std::fs::read_to_string("Cargo.toml") // Use existing file
        .map_err(|e| DecrustError::Io {
            source: e,
            path: Some("Cargo.toml".into()),
            operation: "read config file".to_string(),
            backtrace: Backtrace::generate(),
        })
        .decrust_context_msg("Loading application configuration")?;

    Ok(result)
}

// Test the async function (without actually running it)
let _future = read_config_async();

With Configuration Parsing

use decrust_core::{DecrustError, Backtrace, OptionalError};
use std::path::PathBuf;

// Simple configuration struct (without serde dependency)
struct AppConfig {
    database_url: String,
    api_key: String,
}

fn load_config() -> Result<AppConfig, DecrustError> {
    // Simulate reading configuration from Cargo.toml (which exists)
    let config_str = std::fs::read_to_string("Cargo.toml")
        .map_err(|e| DecrustError::Config {
            message: "Failed to read configuration file".to_string(),
            path: Some("Cargo.toml".into()),
            source: OptionalError::new(Some(Box::new(e))),
            backtrace: Backtrace::generate(),
        })?;

    // Simulate parsing (just create a dummy config)
    let config = AppConfig {
        database_url: "postgresql://localhost/mydb".to_string(),
        api_key: "dummy_key".to_string(),
    };

    // Validate configuration
    if config.database_url.is_empty() {
        return Err(DecrustError::Validation {
            field: "database_url".to_string(),
            message: "Database URL cannot be empty".to_string(),
            expected: None,
            actual: None,
            rule: None,
            backtrace: Backtrace::generate(),
        });
    }

    Ok(config)
}

// Test the function
let _config = load_config();