| Crates.io | error-tree |
| lib.rs | error-tree |
| version | 0.6.0 |
| created_at | 2024-05-05 21:32:30.915319+00 |
| updated_at | 2024-12-02 07:11:31.63346+00 |
| 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.