resext 1.4.0

A simple, lightweight error handling crate for Rust
Documentation
//! **Context-rich error handling for Rust with zero-cost abstractions and zero allocations**
//!
//! This is the primary interface for ResExt. It re-exports the proc-macro as well as other helpers
//! provided by ResExt.
//!
//! # Quick Start
//!
//! ```rust
//! use resext::resext;
//!
//! #[resext]
//! enum ConfigError {
//!     Io(std::io::Error),
//!     Utf8(std::string::FromUtf8Error),
//! }
//!
//! fn load_config(path: &str) -> Res<String> {
//!     let content = std::fs::read(path)
//!         .context("Failed to read config")?;
//!     
//!     std::string::String::from_utf8(content)
//!         .context("Failed to parse config")
//! }
//! ```
//!
//! ---
//!
//! # Proc Macro
//!
//! The proc macro provides clean syntax with full customization:
//!
//! ```rust
//! use resext::resext;
//!
//! #[resext(
//!     prefix = "ERROR: ",
//!     delimiter = " -> ",
//!     include_variant = true,
//! )]
//! enum MyError {
//!     Io(std::io::Error),
//!     Fmt { error: std::fmt::Error },
//! }
//! ```
//!
//! ## Attribute Options
//!
//! - `prefix` - String prepended to entire error message
//! - `suffix` - String appended to entire error message
//! - `msg_prefix` - String prepended to each context message
//! - `msg_suffix` - String appended to each context message
//! - `delimiter` - Separator between context messages (default: " - ")
//! - `source_prefix` - String prepended to source error (default: "Error: ")
//! - `include_variant` - Include variant name in Display output (default: false)
//! - `alias` - Custom type alias name which is used for getting the names for other items generated by the proc-macro (default: `Res`)
//! - `buf_size` - Size for the context message byte buffer (default: 64)
//! - `alloc` Enable heap-spilling if context exceeds `buf_size`
//!
//! ## `.context()` Method
//!
//! Add static context to an error.
//!
//! Accepts `&str` or `ctx!()` macro which outputs a lazily evaluated closure with usage similar to old `format_args!()` API
//!
//! ### Example
//!
//! ```rust
//! # use resext::resext;
//! # #[resext] enum Error { Io(std::io::Error) }
//! # fn doctest() -> Res<()> {
//! std::fs::read("file.txt")
//!     .context("Failed to read file")?;
//! # Ok(())
//! # }
//! ```
//!
//! ---
//!
//! # Error Display Format
//!
//! Errors are displayed with context chains:
//!
//! ```text
//! Failed to load application
//!  - Failed to read config file
//!  - Failed to open file
//! Error: No such file or directory
//! ```
//!
//! With `include_variant = true`:
//!
//! ```text
//! Failed to load application
//!  - Failed to read config file
//! Error: Io: No such file or directory
//! ```
//!
//! ---
//!
//! # Examples
//!
//! ## Error Handling
//!
//! ```rust
//! use resext::ctx;
//! use resext::resext;
//!
//! use std::io::{Error, ErrorKind};
//!
//! #[resext]
//! enum AppError {
//!     Io(Error),
//!     Parse { error: std::num::ParseIntError },
//! }
//! # trait Temp { fn from_args<F: FnOnce(ResErr, &str, &str, &str) -> ResErr>(msg: F, source: Error) -> ResErr; }
//! # impl Temp for ResErr { fn from_args<F: FnOnce(ResErr, &str, &str, &str) -> ResErr>(msg: F, source: Error) -> ResErr { ResErr::new("", Error::other("")) } }
//!
//! fn read_config(path: &str) -> Res<String> {
//!     let content: String = std::fs::read_to_string(path)
//!         .context(ctx!("Failed to read file: {}", path))?;
//!
//!     if content.is_empty() {
//!         return Err(ResErr::new(
//!             "Content is is empty",
//!             Error::new(ErrorKind::UnexpectedEof, "Data is empty"),
//!         ));
//!     }
//!
//!     let value = content
//!         .trim()
//!         .parse::<i32>()
//!         .context(ctx!("Failed to parse config value: {}", &content))?;
//!
//!     if value < 32 {
//!         return Err(ResErr::from_args(
//!             ctx!("Value: {} is less than 32", value),
//!             Error::new(ErrorKind::InvalidData, "Data is less than 32"),
//!         ));
//!     }
//!
//!     Ok(content)
//! }
//! ```
//!
//! ## Multiple Error Types
//!
//! **Note:** This example is not tested as it's an example of errors from external crates
//!
//! ```rust,ignore
//! use resext::resext;
//! use resext::ctx;
//!
//! #[resext(alias = ApiResult)]
//! enum ApiError {
//!     Network(reqwest::Error),
//!     Database(sqlx::Error),
//!     Json(serde_json::Error),
//! }
//!
//! async fn fetch_user(id: u64) -> ApiResult<User> {
//!     let response = reqwest::get(format!("/users/{}", id))
//!         .await
//!         .context(ctx!("Failed to fetch user: {}", id))?;
//!     
//!     let user = response.json()
//!         .await
//!         .context("Failed to parse user data")?;
//!     
//!     Ok(user)
//! }
//! ```
//!
pub use resext_macro::resext;

#[doc(hidden)]
pub struct Writer<W: core::fmt::Write + ?Sized>(pub W);

impl<W: core::fmt::Write + ?Sized> core::fmt::Write for Writer<W> {
    #[inline]
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        self.0.write_str(s)
    }
}

/// Creates a lazily-evaluated context message for use with `.context()`.
///
/// Takes a format string and optional arguments identical to `write!` or `format_args!`,
/// but only evaluates and writes the message if an error actually occurs.
///
/// # Examples
///
/// ```rust
/// use resext::resext;
/// use resext::ctx;
///
/// #[resext]
/// enum FileError {
///     Io(std::io::Error),
///     Utf8(std::string::FromUtf8Error),
/// }
///
/// fn read_file(path: &str) -> Res<String> {
///     let content = std::fs::read(path)
///         .context(ctx!("Failed to read file: {}", path))?;
///
///     String::from_utf8(content)
///         .context(ctx!("Failed to parse file: {}", path))
/// }
/// ```
///
/// Static messages without arguments are also supported, but it is encouraged
/// to use raw `&str`:
///
/// ```rust
/// # use resext::resext;
/// # use resext::ctx;
/// # #[resext] enum Err { Io(std::io::Error) }
/// # fn doctest() -> Res<()> {
/// std::fs::read_to_string("config.toml")
///     .context(ctx!("Failed to read config"))?;
/// # Ok(())
/// # }
/// ```
///
/// # Note
///
/// This macro must be used with `.context()` method generated by `#[resext]`.
/// It cannot be used standalone.
#[macro_export]
macro_rules! ctx {
    ($fmt:expr, $($args:tt)*) => {
        {
            |w, d, mp, ms| {
                use core::fmt::Write;

                let mut w = $crate::Writer(w);

                let _ = w.write_str(d);
                let _ = w.write_str(mp);
                let _ = write!(w, $fmt, $($args)*);
                let _ = w.write_str(ms);

                w.0
            }
        }
    };

    ($fmt:expr) => {
        {
            |w, d, mp, ms| {
                use core::fmt::Write;

                let mut w = $crate::Writer(w);

                let _ = w.write_str(d);
                let _ = w.write_str(mp);
                let _ = write!(w, $fmt);
                let _ = w.write_str(ms);

                w.0
            }
        }
    };
}