| Crates.io | rich-error |
| lib.rs | rich-error |
| version | 0.1.0 |
| created_at | 2025-12-11 17:05:09.478843+00 |
| updated_at | 2025-12-11 17:05:09.478843+00 |
| description | Location-aware erorrs made easy |
| homepage | |
| repository | |
| max_upload_size | |
| id | 1980143 |
| size | 24,498 |
This crate exposes a single derive, #[derive(RichError)], which turns an enum error type into a location-aware outer
error wrapper.
By default RichError generates an Error wrapper that stores both the inner enum and
the std::panic::Location where the error was constructed. This keeps log
lines actionable without needing to thread location metadata yourself:
database error: connection refused (at crates/my-crate/src/db.rs:42)
Add the crate as a procedural-macro dependency. The crate follows the rest of
the hummingbird bridge workspace, so you normally depend on it via the
workspace Cargo.toml, but for illustration:
[dependencies]
rich-error = { path = "crates/rich-error" }
thiserror = "1"
Derive RichError on an enum that already implements std::error::Error
(e.g. via thiserror::Error). Each enum lives inside its module; the derive
will place the generated Error wrapper in the same module.
use thiserror::Error;
use rich_error::RichError;
#[derive(Debug, Error, RichError)]
pub enum DbInner {
#[error("database error: {0}")]
Database(#[from] sqlx::Error),
#[error("invalid transition from {kind}")]
InvalidTransition { kind: StatusKind },
#[error("simple unit error")]
SimpleUnit,
}
Now you can return Result<T, Error> from the module and construct errors with
location-aware helpers:
pub type Result<T> = std::result::Result<T, Error>;
fn do_work() -> Result<()> {
some_inner_operation()?; // From<DbInner> for Error
Err(Error::invalid_transition(kind))?; // struct variant ctor
}
If you prefer a module-specific name for the generated wrapper, add the
#[rich_error(outer = YourType)] helper attribute alongside the derive:
#[derive(Debug, Error, RichError)]
#[rich_error(outer = BridgeError)]
pub enum BridgeInner {
#[error("bridge error: {0}")]
Tuple(sqlx::Error),
}
pub type Result<T> = std::result::Result<T, BridgeError>;
fn do_work() -> Result<()> {
Err(BridgeError::tuple(sqlx::Error::Protocol("oops".into())))?
}
RichError emits the following items next to the enum:
#[derive(Debug)] pub struct Error (or your configured outer type) wrapping the inner enum plus a
&'static Location.impl Display that prints <inner display> (at file:line).impl std::error::Error delegating source() to the inner enum.impl Error { #[track_caller] pub fn new(inner: Inner) -> Self }.fn database(...), fn invalid_transition(...), …),
each marked #[track_caller] so they record the caller’s location.impl From<Inner> for Error so ? converts automatically.impl From<FieldTy> for Error, meaning external error types can be ?-propagated
straight into the outer error.Error and is generated in the enum’s module, but you can override it
with #[rich_error(outer = MyError)].Result::map_err(Error::database) records the standard
library callsite. Prefer ? or closures (e.g. map_err(|e| Error::database(e))).cargo test is sufficient: the crate contains only compile-time code and
relies on doc-tests for examples.