Crates.io | error-tree |
lib.rs | error-tree |
version | 0.6.0 |
source | src |
created_at | 2024-05-05 21:32:30.915319 |
updated_at | 2024-12-02 07:11:31.63346 |
description | This crate let's us use the `error_tree!` proc macro for ergonomic error hierarchy definition |
homepage | |
repository | https://github.com/klebs6/klebs-general |
max_upload_size | |
id | 1230559 |
size | 87,183 |
error-tree
is a Rust procedural macro crate designed to simplify the creation and management of complex error hierarchies in Rust applications. It allows you to define nested error enums in a straightforward and declarative manner, automatically generating From
implementations and other boilerplate code to facilitate error handling across multiple layers of your application.
From
Implementations: Automatically generates From
implementations for error conversions between different error types, even across multiple layers.Display
Implementations: Use the #[display("...")]
attribute to define custom Display
messages for your error variants.PartialEq
Implementations: Control equality comparisons using the #[cmp_neq]
attribute for specific variants.Clone
and PartialEq
.Add error-tree
to your Cargo.toml
:
[dependencies]
error-tree = "0.4.0"
Import the error_tree
macro and start defining your error enums using the error_tree!
macro:
use error_tree::error_tree;
error_tree! {
pub enum MyAppError {
#[display("An unexpected error occurred")]
UnexpectedError,
#[display("IO error: {inner}")]
IoError(std::io::Error),
#[display("Network error: {url}")]
NetworkError { url: String },
}
}
This macro will generate:
MyAppError
enum with the specified variants.From
trait for converting from wrapped error types to MyAppError
.Display
implementation based on the #[display("...")]
attributes.You can define multiple error enums and specify relationships between them. The macro will automatically generate the necessary From
implementations for error conversion.
use error_tree::error_tree;
use std::io;
error_tree! {
pub enum OuterError {
InnerError(InnerError),
#[display("An outer error occurred")]
OuterVariant,
}
pub enum InnerError {
#[display("An inner IO error: {inner}")]
IoError(io::Error),
#[display("A custom inner error")]
CustomError,
}
}
With this setup, you can convert an io::Error
directly into an OuterError
:
fn cause_io_error() -> Result<(), io::Error> {
Err(io::Error::new(io::ErrorKind::Other, "Disk not found"))
}
fn handle_error() -> Result<(), OuterError> {
cause_io_error()?;
Ok(())
}
fn main() {
match handle_error() {
Ok(_) => println!("Success"),
Err(e) => println!("Error occurred: {}", e),
}
}
From
ImplementationsThe macro generates From
implementations that allow seamless conversion between your error types:
impl From<io::Error> for InnerError {
fn from(inner: io::Error) -> Self {
InnerError::IoError(inner)
}
}
impl From<InnerError> for OuterError {
fn from(inner: InnerError) -> Self {
OuterError::InnerError(inner)
}
}
// This allows:
impl From<io::Error> for OuterError {
fn from(inner: io::Error) -> Self {
OuterError::InnerError(InnerError::IoError(inner))
}
}
Display
MessagesUse the #[display("...")]
attribute to define custom messages for your error variants:
error_tree! {
pub enum MyError {
#[display("Simple error occurred")]
SimpleError,
#[display("IO error occurred: {inner}")]
IoError(std::io::Error),
#[display("Data error: {data}")]
DataError { data: String },
}
}
PartialEq
BehaviorYou can derive PartialEq
for your error enums and control comparison behavior for specific variants using the #[cmp_neq]
attribute:
error_tree! {
#[derive(PartialEq)]
pub enum MyError {
SimpleError,
#[cmp_neq]
NonComparableError(std::io::Error),
DataError { data: String },
}
}
let error1 = MyError::NonComparableError(io::Error::new(io::ErrorKind::Other, "Error"));
let error2 = MyError::NonComparableError(io::Error::new(io::ErrorKind::Other, "Error"));
assert_ne!(error1, error2); // Due to #[cmp_neq], these are not equal
error-tree
excels at handling complex error hierarchies. Here's an example adapted from the crate's tests:
use error_tree::error_tree;
use std::sync::mpsc;
#[derive(Debug)]
pub struct CpalStreamError;
#[derive(Debug)]
pub struct CpalDeviceNameError;
#[derive(Debug)]
pub struct CpalDevicesError;
#[derive(Debug)]
pub struct CpalHostUnavailable;
error_tree! {
pub enum PassiveAudioCaptureError {
FormatError,
DeviceError(DeviceError),
IOError(IOError),
HostError(HostError),
StreamError(StreamError),
ChannelError(ChannelError),
}
pub enum DeviceError {
DeviceNotAvailable { device_name: String },
Basic(CpalDevicesError),
NameError(CpalDeviceNameError),
}
pub enum IOError {
Basic(std::io::Error),
}
pub enum HostError {
HostUnavailable(CpalHostUnavailable),
}
pub enum StreamError {
StreamError(CpalStreamError),
}
pub enum ChannelError {
ChannelRecvError(mpsc::RecvError),
}
}
// Usage example
fn cause_device_error() -> Result<(), CpalDeviceNameError> {
Err(CpalDeviceNameError)
}
fn main() {
let result: Result<(), PassiveAudioCaptureError> = (|| {
cause_device_error()?;
Ok(())
})();
match result {
Ok(_) => println!("Success"),
Err(e) => println!("Error: {}", e),
}
}
In this example:
From
implementations, allowing errors from low-level components (like CpalDeviceNameError
) to be converted into high-level errors (PassiveAudioCaptureError
) automatically.The crate includes several tests demonstrating its capabilities:
#[derive(Clone)]
properly implement the Clone
trait.PartialEq
implementations respect the #[cmp_neq]
attribute.Display
messages are formatted correctly for different variants.Debug
and Display
where appropriate.Debug
if you want the default Display
implementation to work correctly.Contributions are welcome! Please submit issues or pull requests on the GitHub repository.
This project is licensed under the MIT License. See the LICENSE file for details.
Feel free to reach out if you have any questions or need assistance using error-tree
.
To help you understand what the error_tree!
macro generates, here's an overview based on the crate's code:
The macro processes your error enums and their variants, supporting:
From
ImplementationsFor each wrapped variant, the macro generates From
implementations to convert from the wrapped type to the enum containing it. It also generates transitive From
implementations to allow direct conversion from low-level errors to top-level errors in your hierarchy.
Example generated code:
impl From<std::io::Error> for IOError {
fn from(x: std::io::Error) -> Self {
IOError::Basic(x)
}
}
impl From<IOError> for PassiveAudioCaptureError {
fn from(x: IOError) -> Self {
PassiveAudioCaptureError::IOError(x)
}
}
// Transitive conversion
impl From<std::io::Error> for PassiveAudioCaptureError {
fn from(x: std::io::Error) -> Self {
PassiveAudioCaptureError::IOError(IOError::Basic(x))
}
}
Display
ImplementationsThe macro generates Display
implementations for your enums, using the #[display("...")]
attribute if provided. If no #[display("...")]
attribute is present, it defaults to displaying the variant name.
PartialEq
ImplementationsIf you derive PartialEq
and use the #[cmp_neq]
attribute on specific variants, the macro generates a custom PartialEq
implementation that respects this attribute.
The macro carefully processes attributes to ensure that standard attributes like #[derive(...)]
are preserved and applied correctly to the generated enums.
error-tree
simplifies error handling in Rust applications by reducing boilerplate and providing a clear, declarative way to define complex error hierarchies. By automatically generating necessary implementations, it allows you to focus on your application's logic rather than repetitive error handling code.