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};
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"));
}
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};
let context = error_context!(
"Database connection failed",
severity: ErrorSeverity::Critical
).with_component("database")
.with_correlation_id("req-123")
.with_recovery_suggestion("Check database connectivity");
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;
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);
# fn external_api_call() -> Result<String, std::io::Error> { Ok("success".to_string()) }
let result = circuit_breaker.execute(|| {
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();
# 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};
# let source_error = std::io::Error::new(std::io::ErrorKind::Other, "test");
let error = oops!("Something went wrong", source_error);
let validation_err = validation_error!(
"email",
"Invalid email format",
suggestion: "Use format: user@domain.com"
);
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;
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(),
};
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;
#[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 {}
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(),
}
}
}
}
}
fn get_user(id: &str) -> Result<User, DecrustError> {
if id.is_empty() {
return Err(UserServiceError::NotFound { id: id.to_string() }.into());
}
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};
let config = ErrorReportConfig {
format: ErrorReportFormat::Json,
include_backtrace: true,
include_rich_context: true,
..Default::default()
};
let reporter = ErrorReporter::new();
# 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;
let config = CircuitBreakerConfig {
failure_threshold: 3, success_threshold_to_close: 2, reset_timeout: Duration::from_secs(60), operation_timeout: Some(Duration::from_secs(10)), half_open_max_concurrent_operations: 1, ..Default::default()
};
let circuit_breaker = CircuitBreaker::new("payment-service", config);
let result = circuit_breaker.execute(|| {
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};
fn process_with_dyn_traits(
result: &dyn DecrustResultExt<String, std::io::Error>,
option: &dyn DecrustOptionExt<i32>
) {
}
fn process_data() -> Result<String, DecrustError> {
let result: Result<String, std::io::Error> = Ok("test".to_string());
let option: Option<i32> = Some(42);
let processed = result.decrust_context_msg("Processing data")?;
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
- Use specific error variants for different error categories
- Add rich context with
decrust_context_msg() for better debugging
- Implement circuit breakers for external service calls
- Use macros for common error patterns to reduce boilerplate
- Configure error reporting for production monitoring
- 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;
async fn read_config_async() -> Result<String, DecrustError> {
let result = std::fs::read_to_string("Cargo.toml") .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)
}
let _future = read_config_async();
With Configuration Parsing
use decrust_core::{DecrustError, Backtrace, OptionalError};
use std::path::PathBuf;
struct AppConfig {
database_url: String,
api_key: String,
}
fn load_config() -> Result<AppConfig, DecrustError> {
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(),
})?;
let config = AppConfig {
database_url: "postgresql://localhost/mydb".to_string(),
api_key: "dummy_key".to_string(),
};
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)
}
let _config = load_config();