use waddling_errors::prelude::*;
#[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",
}
}
}
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);
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,
}
}
}
impl AppError {
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,
}
}
pub fn error_code(&self) -> Option<&str> {
match self {
Self::Auth { code, .. } | Self::Database { code, .. } => Some(code),
Self::Validation(_) => None,
}
}
}
type Result<T> = std::result::Result<T, AppError>;
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(())
}
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(())
}
fn authenticate_user(token: &str) -> Result<String> {
validate_token(token)?;
connect_database(500)?;
Ok("user123".to_string())
}
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");
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");
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");
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");
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");
}