Crates.io | bizerror |
lib.rs | bizerror |
version | 0.1.3 |
created_at | 2024-11-13 02:24:29.988299+00 |
updated_at | 2025-07-04 09:44:31.241608+00 |
description | Provides a standardized approach for defining and managing business-related errors |
homepage | https://github.com/vainjoker/bizerror |
repository | https://github.com/vainjoker/bizerror |
max_upload_size | |
id | 1445853 |
size | 177,009 |
BizError
is a lightweight, ergonomic library for creating structured, business-oriented errors in Rust. It simplifies error handling by providing a clear distinction between technical errors and business-logic failures, all while integrating seamlessly with the standard std::error::Error
trait and popular libraries like thiserror
.
BizError
is built on a simple observation:
This library is optimized for both scenarios, providing a zero-overhead solution for the common case and powerful context-aware tools for the complex cases.
BizError
and Debug
for your error enums.code()
and name()
for every error variant.u32
, i16
, etc.) or string types (&'static str
) for your codes.BizErrors
for scenarios like validation.thiserror
and the standard library.Add bizerror
and thiserror
to your Cargo.toml
:
[dependencies]
bizerror = "0.1.1"
thiserror = "1.0" # Recommended for Display trait implementation
Defining business errors is incredibly simple. Just derive BizError
and thiserror::Error
, then annotate your variants.
use bizerror::BizError;
use thiserror::Error;
#[derive(Debug, BizError, Error)]
#[bizconfig(code_type = "u16")] // Optional: configure the code type
pub enum ApiError {
#[bizcode(4001)]
#[error("Invalid input for field: {field}")]
ValidationError { field: String },
#[bizcode(4004)]
#[error("Resource not found with ID: {id}")]
NotFound { id: u64 },
#[bizcode(5000)]
#[error("Internal server error")]
ServerError,
}
// --- Using your error ---
let error = ApiError::NotFound { id: 123 };
// Get structured information
assert_eq!(error.code(), 4004);
assert_eq!(error.name(), "NotFound");
assert_eq!(error.to_string(), "Resource not found with ID: 123");
// The generated Debug trait provides structured logging
println!("{:?}", error);
// Output: ApiError { variant: "NotFound", code: 4004, message: "Resource not found with ID: 123" }
Let bizerror
handle the codes for you. Use #[bizconfig]
to set a starting point and increment step. You can still override any code with #[bizcode]
.
use bizerror::BizError;
use thiserror::Error;
#[derive(Debug, BizError, Error)]
#[bizconfig(auto_start = 1000, auto_increment = 10)]
pub enum ServiceError {
#[error("Authentication failed")]
AuthError, // Auto-assigned code: 1000
#[bizcode(2001)] // Explicit code takes precedence
#[error("Business rule violation")]
BusinessRuleError,
#[error("Database connection failed")]
DatabaseError, // Auto-assigned code: 1010
}
assert_eq!(ServiceError::AuthError.code(), 1000);
assert_eq!(ServiceError::BusinessRuleError.code(), 2001);
assert_eq!(ServiceError::DatabaseError.code(), 1010);
Error codes aren't limited to numbers. Use strings for more descriptive identifiers.
use bizerror::BizError;
use thiserror::Error;
#[derive(Debug, BizError, Error)]
#[bizconfig(code_type = "&'static str")]
pub enum PaymentError {
#[bizcode("INSUFFICIENT_FUNDS")]
#[error("The account has insufficient funds")]
InsufficientFunds,
#[bizcode("CARD_EXPIRED")]
#[error("The credit card has expired")]
CardExpired,
}
assert_eq!(PaymentError::InsufficientFunds.code(), "INSUFFICIENT_FUNDS");
For the 10% of cases where you need more insight, wrap your errors in a ContextualError
. This adds a descriptive message and captures the exact location where the error was handled.
use bizerror::{BizError, BizErrorExt, ContextualError};
use thiserror::Error;
use std::fs;
#[derive(Debug, BizError, Error)]
#[bizconfig(code_type = "u16")]
pub enum ConfigError {
#[bizcode(101)]
#[error("Failed to read config file: {0}")]
IoError(#[from] std::io::Error),
}
fn read_config() -> Result<String, ContextualError<ConfigError>> {
fs::read_to_string("app.config")
// with_context is available on any Result<T, E> where E: Into<BizError>
.with_context("Attempting to load application configuration")
}
match read_config() {
Ok(config) => println!("Config loaded!"),
Err(e) => {
println!("Error Code: {}", e.code());
println!("Context: {}", e.context());
println!("Location: {}", e.location());
println!("Root Cause: {}", e.inner());
}
}
Instead of failing on the first error, use BizErrors
to collect them all. This is perfect for validating user input or processing a batch of items.
use bizerror::{BizError, BizErrors};
use thiserror::Error;
#[derive(Debug, Clone, BizError, Error, PartialEq, Eq)]
#[bizconfig(code_type = "u16")]
pub enum ValidationError {
#[bizcode(1)]
#[error("Username must be at least 3 characters long")]
UsernameTooShort,
#[bizcode(2)]
#[error("Password must contain a special character")]
PasswordRequiresSpecialChar,
}
fn validate_signup(username: &str, password: &str) -> Result<(), BizErrors<ValidationError>> {
let mut errors = BizErrors::new();
if username.len() < 3 {
errors.push_simple(ValidationError::UsernameTooShort);
}
if !password.chars().any(|c| "!@#$%^&*()".contains(c)) {
errors.push_simple(ValidationError::PasswordRequiresSpecialChar);
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
let result = validate_signup("me", "password");
assert!(result.is_err());
let collected_errors = result.unwrap_err();
assert_eq!(collected_errors.len(), 2);
assert_eq!(
collected_errors.error_codes(),
vec![
ValidationError::UsernameTooShort.code(),
ValidationError::PasswordRequiresSpecialChar.code()
]
);
println!("Validation failed with {} errors:
{}", collected_errors.len(), collected_errors);
syn
We welcome contributions! Whether you're interested in:
Check out our TODO.md for detailed implementation plans and effort estimates.
We track our progress through:
Note: This roadmap is subject to change based on community feedback and real-world usage patterns. Priority may be adjusted based on user demand and contribution availability.
This project is licensed under either of:
at your option.