| Crates.io | derive-error-kind |
| lib.rs | derive-error-kind |
| version | 0.1.0 |
| created_at | 2025-06-11 22:20:20.172908+00 |
| updated_at | 2025-06-11 22:20:20.172908+00 |
| description | Proc macro for deriving error kinds |
| homepage | https://github.com/jonhteper/derive-error-kind |
| repository | https://github.com/jonhteper/derive-error-kind |
| max_upload_size | |
| id | 1709166 |
| size | 25,768 |
A Rust procedural macro for implementing the ErrorKind pattern that simplifies error classification and handling in complex applications.
The ErrorKind pattern is a common technique in Rust for separating:
This allows developers to handle errors more granularly without losing context.
Rust's standard library uses this pattern in std::io::ErrorKind, and many other libraries have adopted it due to its flexibility. However, manually implementing this pattern can be repetitive and error-prone, especially in applications with multiple nested error types.
This crate solves this problem by providing a derive macro that automates the implementation of the ErrorKind pattern.
The ErrorKind macro allows you to associate error types with a specific kind from an enum. This creates a clean and consistent way to categorize errors in your application, enabling more precise error handling.
Key features:
.kind() method that returns a categorized error typetransparent attributeAdd this to your Cargo.toml:
[dependencies]
derive-error-kind = "0.1.0"
First, define an enum for your error kinds:
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ErrorKind {
NotFound,
InvalidInput,
InternalError,
}
Then, use the ErrorKind derive macro on your error enums:
use derive_error_kind::ErrorKind;
#[derive(Debug, ErrorKind)]
#[error_kind(ErrorKind)]
pub enum MyError {
#[error_kind(ErrorKind, NotFound)]
ResourceNotFound,
#[error_kind(ErrorKind, InvalidInput)]
BadRequest { details: String },
#[error_kind(ErrorKind, InternalError)]
ServerError(String),
}
// Now you can use the .kind() method
let error = MyError::ResourceNotFound;
assert_eq!(error.kind(), ErrorKind::NotFound);
You can create hierarchical error structures with the transparent attribute:
use derive_error_kind::ErrorKind;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ErrorKind {
Database,
Cache,
Network,
Configuration,
}
#[derive(Debug, ErrorKind)]
#[error_kind(ErrorKind)]
pub enum DatabaseError {
#[error_kind(ErrorKind, Database)]
Connection,
#[error_kind(ErrorKind, Database)]
Query(String),
}
#[derive(Debug, ErrorKind)]
#[error_kind(ErrorKind)]
pub enum CacheError {
#[error_kind(ErrorKind, Cache)]
Expired,
#[error_kind(ErrorKind, Cache)]
Missing,
}
#[derive(Debug, ErrorKind)]
#[error_kind(ErrorKind)]
pub enum AppError {
#[error_kind(transparent)]
Db(DatabaseError),
#[error_kind(transparent)]
Cache(CacheError),
#[error_kind(ErrorKind, Network)]
Connection,
#[error_kind(ErrorKind, Configuration)]
InvalidConfig { field: String, message: String },
}
// The transparent attribute allows the kind to bubble up
let db_error = AppError::Db(DatabaseError::Connection);
assert_eq!(db_error.kind(), ErrorKind::Database);
let cache_error = AppError::Cache(CacheError::Missing);
assert_eq!(cache_error.kind(), ErrorKind::Cache);
thiserrorThe ErrorKind derive macro works well with other popular error handling crates:
use derive_error_kind::ErrorKind;
use thiserror::Error;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ErrorKind {
NotFound,
Unauthorized,
Internal,
}
#[derive(Debug, Error, ErrorKind)]
#[error_kind(ErrorKind)]
pub enum ApiError {
#[error("Resource not found: {0}")]
#[error_kind(ErrorKind, NotFound)]
NotFound(String),
#[error("Unauthorized access")]
#[error_kind(ErrorKind, Unauthorized)]
Unauthorized,
#[error("Internal server error: {0}")]
#[error_kind(ErrorKind, Internal)]
Internal(String),
}
// Use in error handling
fn process_api_result(result: Result<(), ApiError>) {
if let Err(err) = result {
match err.kind() {
ErrorKind::NotFound => {
// Handle not found errors
println!("Resource not found: {}", err);
},
ErrorKind::Unauthorized => {
// Handle authorization errors
println!("Please log in first");
},
ErrorKind::Internal => {
// Log internal errors
eprintln!("Internal error: {}", err);
},
}
}
}
Here's a more complete example for a web application with multiple error domains:
use derive_error_kind::ErrorKind;
use thiserror::Error;
use std::fmt;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ApiErrorKind {
Authentication,
Authorization,
NotFound,
BadRequest,
ServerError,
}
// Implement Display for HTTP status code mapping
impl fmt::Display for ApiErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Authentication => write!(f, "Authentication Failed"),
Self::Authorization => write!(f, "Not Authorized"),
Self::NotFound => write!(f, "Resource Not Found"),
Self::BadRequest => write!(f, "Bad Request"),
Self::ServerError => write!(f, "Internal Server Error"),
}
}
}
// Implement status code conversion
impl ApiErrorKind {
pub fn status_code(&self) -> u16 {
match self {
Self::Authentication => 401,
Self::Authorization => 403,
Self::NotFound => 404,
Self::BadRequest => 400,
Self::ServerError => 500,
}
}
}
// Database errors
#[derive(Debug, Error, ErrorKind)]
#[error_kind(ApiErrorKind)]
pub enum DbError {
#[error("Database connection failed: {0}")]
#[error_kind(ApiErrorKind, ServerError)]
Connection(String),
#[error("Query execution failed: {0}")]
#[error_kind(ApiErrorKind, ServerError)]
Query(String),
#[error("Entity not found: {0}")]
#[error_kind(ApiErrorKind, NotFound)]
NotFound(String),
}
// Auth errors
#[derive(Debug, Error, ErrorKind)]
#[error_kind(ApiErrorKind)]
pub enum AuthError {
#[error("Invalid credentials")]
#[error_kind(ApiErrorKind, Authentication)]
InvalidCredentials,
#[error("Token expired")]
#[error_kind(ApiErrorKind, Authentication)]
TokenExpired,
#[error("Insufficient permissions for {0}")]
#[error_kind(ApiErrorKind, Authorization)]
InsufficientPermissions(String),
}
// Application errors that can wrap domain-specific errors
#[derive(Debug, Error, ErrorKind)]
#[error_kind(ApiErrorKind)]
pub enum AppError {
#[error(transparent)]
#[error_kind(transparent)]
Database(#[from] DbError),
#[error(transparent)]
#[error_kind(transparent)]
Auth(#[from] AuthError),
#[error("Invalid input: {0}")]
#[error_kind(ApiErrorKind, BadRequest)]
InvalidInput(String),
#[error("Unexpected error: {0}")]
#[error_kind(ApiErrorKind, ServerError)]
Unexpected(String),
}
// Example API response
#[derive(Debug, serde::Serialize)]
pub struct ApiResponse<T> {
success: bool,
data: Option<T>,
error: Option<ErrorResponse>,
}
#[derive(Debug, serde::Serialize)]
pub struct ErrorResponse {
code: u16,
message: String,
}
// Use in a web handler (example with actix-web)
fn handle_error(err: AppError) -> HttpResponse {
let status_code = err.kind().status_code();
let response = ApiResponse {
success: false,
data: None,
error: Some(ErrorResponse {
code: status_code,
message: err.to_string(),
}),
};
HttpResponse::build(StatusCode::from_u16(status_code).unwrap())
.json(response)
}
// Usage example
async fn get_user(user_id: String) -> Result<User, AppError> {
let user = db::find_user(&user_id).await
.map_err(AppError::Database)?;
if !user.is_active {
return Err(AppError::Auth(AuthError::InsufficientPermissions("inactive user".to_string())));
}
Ok(user)
}
thiserror, anyhow, and other error handling crates#[error_kind(KindEnum)]: Top-level attribute that specifies which enum to use for error kinds#[error_kind(KindEnum, Variant)]: Variant-level attribute that specifies which variant of the kind enum to return#[error_kind(transparent)]: Variant-level attribute for nested errors, indicating that the inner error's kind should be usederror_kind attributeHere's an example showing how derive-error-kind can be used in a microservices architecture:
use derive_error_kind::ErrorKind;
use thiserror::Error;
use std::fmt;
// Global error kinds that are consistent across all services
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum GlobalErrorKind {
// Infrastructure errors
DatabaseError,
CacheError,
NetworkError,
// Business logic errors
ValidationError,
NotFoundError,
ConflictError,
// Security errors
AuthenticationError,
AuthorizationError,
// General errors
ConfigurationError,
InternalError,
}
// User service errors
#[derive(Debug, Error, ErrorKind)]
#[error_kind(GlobalErrorKind)]
pub enum UserServiceError {
#[error("Failed to connect to users database: {0}")]
#[error_kind(GlobalErrorKind, DatabaseError)]
Database(String),
#[error("User not found: {0}")]
#[error_kind(GlobalErrorKind, NotFoundError)]
NotFound(String),
#[error("Email already exists: {0}")]
#[error_kind(GlobalErrorKind, ConflictError)]
DuplicateEmail(String),
}
// Inventory service errors
#[derive(Debug, Error, ErrorKind)]
#[error_kind(GlobalErrorKind)]
pub enum InventoryServiceError {
#[error("Failed to connect to inventory database: {0}")]
#[error_kind(GlobalErrorKind, DatabaseError)]
Database(String),
#[error("Product not found: {0}")]
#[error_kind(GlobalErrorKind, NotFoundError)]
ProductNotFound(String),
#[error("Insufficient stock for product: {0}")]
#[error_kind(GlobalErrorKind, ConflictError)]
InsufficientStock(String),
}
// Order service errors
#[derive(Debug, Error, ErrorKind)]
#[error_kind(GlobalErrorKind)]
pub enum OrderServiceError {
#[error("Database error: {0}")]
#[error_kind(GlobalErrorKind, DatabaseError)]
Database(String),
#[error(transparent)]
#[error_kind(transparent)]
User(#[from] UserServiceError),
#[error(transparent)]
#[error_kind(transparent)]
Inventory(#[from] InventoryServiceError),
#[error("Order validation failed: {0}")]
#[error_kind(GlobalErrorKind, ValidationError)]
Validation(String),
}
// API Gateway error handling
fn handle_service_error<E: std::error::Error + 'static>(err: E) -> HttpResponse {
// Use downcast to check if we have an error with a kind() method
if let Some(user_err) = err.downcast_ref::<UserServiceError>() {
match user_err.kind() {
GlobalErrorKind::NotFoundError => return HttpResponse::NotFound().finish(),
GlobalErrorKind::ConflictError => return HttpResponse::Conflict().finish(),
_ => { /* continue with other error types */ }
}
}
if let Some(order_err) = err.downcast_ref::<OrderServiceError>() {
// Here, transparent errors from other services are automatically
// mapped to the correct GlobalErrorKind
match order_err.kind() {
GlobalErrorKind::ValidationError => return HttpResponse::BadRequest().finish(),
GlobalErrorKind::NotFoundError => return HttpResponse::NotFound().finish(),
GlobalErrorKind::DatabaseError => {
log::error!("Database error: {}", order_err);
return HttpResponse::InternalServerError().finish();
},
_ => { /* continue with general error handling */ }
}
}
// Default error response
HttpResponse::InternalServerError().finish()
}
Keep error categories (ErrorKind) simple and stable
Use the same error category throughout your application
Combine with thiserror for detailed error messages
derive-error-kind handles categorization while thiserror handles messagesUse transparent for nested errors
Licensed under MIT license.