resext 0.9.0

A simple, lightweight error handling crate for Rust
Documentation

resext

Main crate providing error handling with context chains

This is the primary interface for ResExt. It re-exports the proc-macro as well as other helpers provided by ResExt.


Installation

[dependencies]
resext = "0.9"

Quick Example

use resext::resext;

#[resext]
enum FileError {
    Io(std::io::Error),
    Parse(serde_json::Error),
}

fn load_data(path: &str) -> Res<Data> {
    let content = std::fs::read_to_string(path)
        .context("Failed to read file")?;
    
    let data = serde_json::from_str(&content)
        .with_context(format_args!("Failed to parse {}", path))?;
    
    Ok(data)
}

Proc Macro

The proc macro provides clean syntax with full customization:

#[resext(
    prefix = "ERROR: ",
    suffix = "\n",
    msg_prefix = "  at: ",
    msg_suffix = "",
    delimiter = "\n",
    source_prefix = "Caused by: ",
    include_variant = true,
    alias = MyResult
)]
enum MyError {
    Network(reqwest::Error),
    Database { error: sqlx::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: " - " (NOTE: the delimiter always includes a newline before it, e.g. if delimiter = " - ", then messages will have "\n - " between them, not just " - ")
  • 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)

Context Methods

.context(msg: &str)

Add static context to an error:

std::fs::read("file.txt")
    .context("Failed to read file")?;

.with_context(args: core::fmt::Arguments<'_>)

Add dynamic context:

std::fs::read(path)
    .with_context(format_args!("Failed to read {}", path))?;

.or_exit(code: i32)

Print error to Stderr and exit process with given code on error:

let config = load_config().or_exit(1);

.better_expect(msg: FnOnce() -> impl std::fmt::Display, code: i32)

Like or_exit but with custom message:

let data = load_critical_data()
    .better_expect(|| "FATAL: Cannot start without data", 1);

Error Display Format

Errors are displayed with context chains:

Failed to load application
 - Failed to read config file
 - Failed to open file
Error: No such file or directory

With include_variant = true:

Failed to load application
 - Failed to read config file
Error: Io: No such file or directory

Examples

Basic Error Handling

#[resext]
enum ConfigError {
    Io(std::io::Error),
    Parse(toml::de::Error),
}

fn load_config(path: &str) -> Res<Config> {
    let content = std::fs::read_to_string(path)
        .context("Failed to read config")?;
    
    toml::from_str(&content)
        .with_context(format_args!("Failed to parse {}", path))
}

Multiple Error Types

#[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("Failed to fetch user")?;
    
    let user = response.json()
        .await
        .context("Failed to parse user data")?;
    
    Ok(user)
}

Named Fields

#[resext]
enum DatabaseError {
    Connection { error: sqlx::Error },
    Query { error: sqlx::Error },
}

Contributing

See CONTRIBUTING.md for guidelines.


License

MIT - See LICENSE for details.