A generative approach to creating fatal and non-fatal errors.
The generated source utilizes thiserror::Error
derived attributes heavily,
and any unknown annotations will be passed to that.
For large scale mono-repos, with subsystems it eventually becomes very tedious to match
against nested error variants defined with thiserror
. Using anyhow
or eyre
- while it being an application - also comes with an unmanagable amount of pain for medium-large scale code bases.
fatality
is a solution to this, by extending thiserror::Error
with annotations to declare certain variants as fatal
, or forward
the fatality extraction to an inner error type.
Read on!
#[fatality]
currently provides a trait Fatality
with a single fn is_fatal(&self) -> bool
by default.
Annotations with forward
require the inner error type to also implement trait Fatality
.
Annotating with #[fatality(splitable)]
, allows to split the type into two sub-types, a Jfyi*
and a Fatal*
one via fn split(self) -> Result<Self::Jfyi, Self::Fatal>
. If splitable
is annotated.
The derive macro implements them, and can defer calls, based on thiserror
annotations, specifically
#[source]
and #[transparent]
on enum
variants and their members.
``rust
/// Fatality only works with
enumfor now.
/// It will automatically add
#[derive(Debug, thiserror::Error)]`
/// annotations.
enum OhMy { #[error("An apple a day")] Itsgonnabefine,
/// Forwards the `is_fatal` to the `InnerError`, which has to implement `trait Fatality` as well.
#[fatal(forward)]
#[error("Dropped dead")]
ReallyReallyBad(#[source] InnerError),
/// Also works on `#[error(transparent)]
#[fatal(forward)]
#[error(transparent)]
Translucent(InnerError),
/// Will always return `is_fatal` as `true`,
/// irrespective of `#[error(transparent)]` or
/// `#[source]` annotations.
#[fatal]
#[error("So dead")]
SoDead(#[source] InnerError),
} ```
```rust
enum Yikes { #[error("An apple a day")] Orange,
#[fatal]
#[error("So dead")]
Dead,
}
fn foo() -> Result<[u8;32], Yikes> { Err(Yikes::Dead) }
fn icallfoo() -> Result<(), FatalYikes> {
// availble via a convenience trait Nested
that is implemented
// for any Result
whose error type implements Split
.
let x: Result<[u8;32], Jfyi> = foo().into_nested()?;
}
fn icallfootoo() -> Result<(), FatalYikes> { if let Err(jfyiandfatalones) = foo() { // bail if bad, otherwise just log it log::warn!("Jfyi: {:?}", jfyiandfatal_ones.split()?); } } ```
#[fatal($args)]#[error(..
with #[fatal($args;..)]
and generate the correct #[error]
annotations for thiserror
.finality
: splitable
determines if a this is the root error that shall be handled, and hence should be splitable into two enums Fatal
and Jfyi
errors, with trait Split
and fn split() -> Result<Jfyi, Fatal> {..}
.struct
s as well, to be all fatal or informational.