# Error Tree `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. ## Features - **Simplified Error Definitions**: Define multiple error enums with nested relationships in a concise syntax. - **Automatic `From` Implementations**: Automatically generates `From` implementations for error conversions between different error types, even across multiple layers. - **Customizable `Display` Implementations**: Use the `#[display("...")]` attribute to define custom `Display` messages for your error variants. - **Custom `PartialEq` Implementations**: Control equality comparisons using the `#[cmp_neq]` attribute for specific variants. - **Support for Common Traits**: Supports deriving common traits like `Clone` and `PartialEq`. ## Installation Add `error-tree` to your `Cargo.toml`: ```toml [dependencies] error-tree = "0.4.0" ``` ## Usage Import the `error_tree` macro and start defining your error enums using the `error_tree!` macro: ```rust 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: - The `MyAppError` enum with the specified variants. - Implementations of the `From` trait for converting from wrapped error types to `MyAppError`. - A `Display` implementation based on the `#[display("...")]` attributes. ### Example: Defining Nested Error Enums You can define multiple error enums and specify relationships between them. The macro will automatically generate the necessary `From` implementations for error conversion. ```rust 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`: ```rust 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), } } ``` ### Automatic `From` Implementations The macro generates `From` implementations that allow seamless conversion between your error types: ```rust impl From for InnerError { fn from(inner: io::Error) -> Self { InnerError::IoError(inner) } } impl From for OuterError { fn from(inner: InnerError) -> Self { OuterError::InnerError(inner) } } // This allows: impl From for OuterError { fn from(inner: io::Error) -> Self { OuterError::InnerError(InnerError::IoError(inner)) } } ``` ### Customizing `Display` Messages Use the `#[display("...")]` attribute to define custom messages for your error variants: ```rust 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 }, } } ``` ### Controlling `PartialEq` Behavior You can derive `PartialEq` for your error enums and control comparison behavior for specific variants using the `#[cmp_neq]` attribute: ```rust 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 ``` ## Advanced Usage ### Complex Error Hierarchies `error-tree` excels at handling complex error hierarchies. Here's an example adapted from the crate's tests: ```rust 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: - Multiple error enums are defined, representing different components of an audio capture system. - The macro generates `From` implementations, allowing errors from low-level components (like `CpalDeviceNameError`) to be converted into high-level errors (`PassiveAudioCaptureError`) automatically. - This simplifies error handling in functions that may return errors from different layers. ## Tests and Examples The crate includes several tests demonstrating its capabilities: - **Clone Derivation Test**: Ensures that enums defined with `#[derive(Clone)]` properly implement the `Clone` trait. - **PartialEq Implementation Test**: Verifies that custom `PartialEq` implementations respect the `#[cmp_neq]` attribute. - **Display Trait Implementation Test**: Checks that custom `Display` messages are formatted correctly for different variants. ## Limitations - The macro assumes that the types used in your error variants are valid Rust types that implement necessary traits like `Debug` and `Display` where appropriate. - For custom types used in wrapped variants, ensure that they implement `Debug` if you want the default `Display` implementation to work correctly. ## Contributing Contributions are welcome! Please submit issues or pull requests on the [GitHub repository](https://github.com/yourusername/error-tree). ## License This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. --- Feel free to reach out if you have any questions or need assistance using `error-tree`. ## Appendix: Understanding the Macro's Generated Code To help you understand what the `error_tree!` macro generates, here's an overview based on the crate's code: ### Error Enums and Variants The macro processes your error enums and their variants, supporting: - **Basic Variants**: Simple variants without data. - **Wrapped Variants**: Variants that wrap another error type. - **Struct Variants**: Variants with named fields. ### `From` Implementations For 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: ```rust impl From for IOError { fn from(x: std::io::Error) -> Self { IOError::Basic(x) } } impl From for PassiveAudioCaptureError { fn from(x: IOError) -> Self { PassiveAudioCaptureError::IOError(x) } } // Transitive conversion impl From for PassiveAudioCaptureError { fn from(x: std::io::Error) -> Self { PassiveAudioCaptureError::IOError(IOError::Basic(x)) } } ``` ### `Display` Implementations The 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. ### Custom `PartialEq` Implementations If you derive `PartialEq` and use the `#[cmp_neq]` attribute on specific variants, the macro generates a custom `PartialEq` implementation that respects this attribute. ### Attribute Handling The macro carefully processes attributes to ensure that standard attributes like `#[derive(...)]` are preserved and applied correctly to the generated enums. ## Conclusion `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.