Any easy way to add common implicit data to an error enum?
currntly, if i want to get a err enum with some same detail info ,i must define them in every enum variants.
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum MyErrorEnum {
Err1 {
#[snafu(implicit)]
backtrace: Backtrace,
#[snafu(implicit)]
loc: Location,
#[snafu(implicit)]
span: Span,
},
Err2 {
detail: String,
#[snafu(implicit)]
backtrace: Backtrace,
#[snafu(implicit)]
loc: Location,
#[snafu(implicit)]
span: Span,
},
// ...
}
pub type Result<T> = std::error::Result<T, MyError>;
or like this
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub struct MyError {
#[snafu(implicit)]
backtrace: Backtrace,
#[snafu(implicit)]
loc: Location,
#[snafu(implicit)]
span: Span,
source: MyErrorEnum,
}
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum MyErrorEnum {
Err1,
Err2(String),
// ...
}
pub type Result<T> = std::error::Result<T, MyError>;
impl<E> From<E> for MetaError
where
E: Into<MetaErrorEnum>,
{
#[track_caller]
fn from(error: E) -> Self {
Self {
source: error.into(),
loc: GenerateImplicitData::generate(),
span: GenerateImplicitData::generate(),
backtrace: GenerateImplicitData::generate(),
}
}
}
impl MyError {
pub fn inner(&self) -> &MyErrorEnum {
// ...
}
}
Is there an easier way to do this without having to write frustratingly repetitive property values?
Those would be the two approaches that I know of as well. Perhaps there could be a way to automatically fill in MetaError upon context insertion, but it's worth pointing out that some of the implicit data might only be desirable in leaf error variants.
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum MyErrorEnum {
Err1 {
#[snafu(implicit)]
backtrace: Backtrace,
#[snafu(implicit)]
loc: Location,
#[snafu(implicit)]
span: Span,
},
// has a source with the backtrace
Err2 {
#[snafu(backtrace)]
source: BarError,
#[snafu(implicit)]
loc: Location,
#[snafu(implicit)]
span: Span,
},
// ...
}
do this without [writing] repetitive property values?
I appreciate this question because it's in the small set of things I can answer definitely: no. SNAFU is currently implemented as a derive macro and derive macros cannot modify the type they are attached to, they may only output additional code.
It's actually on my long-term roadmap to rewrite to an attribute macro [^1] because SNAFU produces additional types (the context selectors) and people generally don't expect a derive macro to do anything beyond implement a trait. Even in that future world, I don't know how I'd feel about SNAFU modifying the fields of the type.
Perhaps some crate out there provides an attribute macro that will automatically add an attribute + field pair to every enum? If so, then you could use it in addition to SNAFU.
In your case, it appears you have multiple implicit fields, so you could create a composite type that implements GenerateImplicitData by delegating off to its children. That way your error definitions would only need one field each, simplifying things a small bit.
Another possibility is to have a wrapping error that contains the implicit data and then always convert the inner error to the wrapping error:
use snafu::prelude::*;
#[derive(Debug, Snafu)]
enum Inner {
#[snafu(display("Bad thing 1"))]
Alfa,
#[snafu(display("Bad thing 2"))]
Beta,
}
#[derive(Debug, Snafu)]
#[snafu(transparent)]
struct Outer {
source: Inner,
#[snafu(implicit)]
location: snafu::Location,
}
fn inner_main(value: bool) -> Result<(), Outer> {
if value {
AlfaSnafu.fail()?;
} else {
BetaSnafu.fail()?;
}
Ok(())
}
#[snafu::report]
fn main() -> Result<(), Outer> {
let v = inner_main(std::env::args().count() > 1);
if let Err(e) = &v {
eprintln!("It happened at {}", e.location);
}
v
}
This will run into some ergonomic problems around type inference sometimes though, so I wouldn't recommend it as a first solution.
#[snafu(implicit)] backtrace: Backtrace,
Note that you don't need implicit on a backtrace field — it's implicit.
#[snafu(implicit)] backtrace: Backtrace, #[snafu(implicit)] loc: Location,
It seems odd to have both of these as the backtrace should contain the location, no?
[^1]: This would look like #[snafu] enum Error {} instead of #[derive(Snafu)] enum Error {}.
do this without [writing] repetitive property values?
I appreciate this question because it's in the small set of things I can answer definitely: no. SNAFU is currently implemented as a derive macro and derive macros cannot modify the type they are attached to, they may only output additional code.
It's actually on my long-term roadmap to rewrite to an attribute macro 1 because SNAFU produces additional types (the context selectors) and people generally don't expect a derive macro to do anything beyond implement a trait. Even in that future world, I don't know how I'd feel about SNAFU modifying the fields of the type.
Perhaps some crate out there provides an attribute macro that will automatically add an attribute + field pair to every enum? If so, then you could use it in addition to SNAFU.
In your case, it appears you have multiple implicit fields, so you could create a composite type that implements
GenerateImplicitDataby delegating off to its children. That way your error definitions would only need one field each, simplifying things a small bit.Another possibility is to have a wrapping error that contains the implicit data and then always convert the inner error to the wrapping error:
use snafu::prelude::*; #[derive(Debug, Snafu)] enum Inner { #[snafu(display("Bad thing 1"))] Alfa, #[snafu(display("Bad thing 2"))] Beta, } #[derive(Debug, Snafu)] #[snafu(transparent)] struct Outer { source: Inner, #[snafu(implicit)] location: snafu::Location, } fn inner_main(value: bool) -> Result<(), Outer> { if value { AlfaSnafu.fail()?; } else { BetaSnafu.fail()?; } Ok(()) } #[snafu::report] fn main() -> Result<(), Outer> { let v = inner_main(std::env::args().count() > 1); if let Err(e) = &v { eprintln!("It happened at {}", e.location); } v }This will run into some ergonomic problems around type inference sometimes though, so I wouldn't recommend it as a first solution.
#[snafu(implicit)] backtrace: Backtrace,Note that you don't need
impliciton a backtrace field — it's implicit.#[snafu(implicit)] backtrace: Backtrace, #[snafu(implicit)] loc: Location,It seems odd to have both of these as the backtrace should contain the location, no?
Footnotes
- This would look like
#[snafu] enum Error {}instead of#[derive(Snafu)] enum Error {}. ↩
yep, maybe attribute macro is more flexible. I use the second way to wrap the common implict data, but I write a lot of other convert function manaually, it's a littele bit weird, how I wish they were automatically generated😁
currntly, if i want to get a err enum with some same detail info ,i must define them in every enum variants.
#[derive(Debug, Snafu)] #[snafu(visibility(pub(crate)))] pub enum MyErrorEnum { Err1 { #[snafu(implicit)] backtrace: Backtrace, #[snafu(implicit)] loc: Location, #[snafu(implicit)] span: Span, }, Err2 { detail: String, #[snafu(implicit)] backtrace: Backtrace, #[snafu(implicit)] loc: Location, #[snafu(implicit)] span: Span, }, // ... } pub type Result<T> = std::error::Result<T, MyError>;or like this
#[derive(Debug, Snafu)] #[snafu(visibility(pub(crate)))] pub struct MyError { #[snafu(implicit)] backtrace: Backtrace, #[snafu(implicit)] loc: Location, #[snafu(implicit)] span: Span, source: MyErrorEnum, } #[derive(Debug, Snafu)] #[snafu(visibility(pub(crate)))] pub enum MyErrorEnum { Err1, Err2(String), // ... } pub type Result<T> = std::error::Result<T, MyError>; impl<E> From<E> for MetaError where E: Into<MetaErrorEnum>, { #[track_caller] fn from(error: E) -> Self { Self { source: error.into(), loc: GenerateImplicitData::generate(), span: GenerateImplicitData::generate(), backtrace: GenerateImplicitData::generate(), } } } impl MyError { pub fn inner(&self) -> &MyErrorEnum { // ... } }Is there an easier way to do this without having to write frustratingly repetitive property values?
https://github.com/yuanyan3060/enum_expand
Maybe you can try this crate, and if it works, I'll upload it to crate.io
We at number0 have written a macro that has nothing to do with snafu whatsoever that just adds common fields to each case of an enum.
Usage, for example:
/// Error that you can get from [`AtConnected::next`]
#[common_fields({
backtrace: Option<Backtrace>,
#[snafu(implicit)]
span_trace: SpanTrace,
})]
#[allow(missing_docs)]
#[derive(Debug, Snafu)]
#[non_exhaustive]
pub enum ConnectedNextError {
/// Error when serializing the request
#[snafu(display("postcard ser: {source}"))]
PostcardSer { source: postcard::Error },
/// The serialized request is too long to be sent
#[snafu(display("request too big"))]
RequestTooBig {},
/// Error when writing the request to the [`SendStream`].
#[snafu(display("write: {source}"))]
Write { source: quinn::WriteError },
/// Quic connection is closed.
#[snafu(display("closed"))]
Closed { source: quinn::ClosedStream },
/// A generic io error
#[snafu(transparent)]
Io { source: io::Error },
}
it is published in https://docs.rs/nested_enum_utils/latest/nested_enum_utils/attr.common_fields.html for now. If there was interest we could also make a PR to snafu.