How to combine errors? #424
-
The documentation talks about splitting a source error into two separate errors. In my experience the converse also often happens, sometimes you want to combine several source errors into a single one. However as a newbie, I've been unable to make this happen. I've attached an MRE of one attempt, but I'm not necessarily asking for how to make my MRE compile -- something completely different that matches my use case would be awesome. For example, I use the InnerError pattern and source(false) but a less verbose solution that doesn't need one or both of those likely would be even better. use rand::Rng;
use snafu::prelude::*;
#[derive(Debug, Snafu)]
pub enum Error {
/// Error in the construction of the NFT
#[snafu(display("Parse Error on {field}: {source}"))]
ParseError {
field: String,
#[snafu(source(false))]
source: InnerParseError,
},
}
#[derive(Debug, Snafu)]
pub enum InnerParseError {
#[snafu(display("YAMLError {source}"))]
YamlError {
#[snafu(source(false))]
source: serde_yaml::Error,
},
#[snafu(display("JSONError {source}"))]
JsonError {
#[snafu(source(false))]
source: serde_json::Error,
},
#[snafu(display("{message}"))]
MessageError { message: String },
}
fn main_wrapped() -> Result<String, Error> {
let r: i32 = rand::thread_rng().gen_range(0..3);
Ok(match r {
0 => {
let _ = serde_yaml::from_str("invalid yaml").with_context(|e| ParseSnafu {
field: "0",
source: YamlSnafu { source: e },
})?;
"0".to_string()
}
1 => {
let _ = serde_json::from_str("invalid json").with_context(|e| ParseSnafu {
field: "0",
source: JsonSnafu { source: e },
})?;
"1".to_string()
}
_ => {
ensure!(
false,
ParseSnafu {
field: "2",
source: InnerParseError::MessageError {
message: "blah".to_owned()
}
}
);
"2".to_string()
}
})
}
fn main() {
match main_wrapped() {
Ok(s) => {
panic!("#{s:?}");
}
Err(e) => {
println!("#{e:?}");
}
}
} [package]
name = "mre"
version = "0.1.0"
edition = "2021"
[dependencies]
rand = "0.8"
serde_yaml = "0.8"
serde_json = "1"
snafu = "0.7"
|
Beta Was this translation helpful? Give feedback.
Replies: 3 comments
-
If I understand what you want, I'd probably end up with #[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("Parse Error on {field}: {source}"))]
ParseError {
field: String,
source: InnerParseError,
},
}
#[derive(Debug, Snafu)]
pub enum InnerParseError {
#[snafu(display("YAMLError {source}"))]
YamlError { source: serde_yaml::Error },
#[snafu(display("JSONError {source}"))]
JsonError { source: serde_json::Error },
#[snafu(display("{message}"))]
MessageError { message: String },
}
fn main_wrapped() -> Result<String, Error> {
let r: i32 = rand::thread_rng().gen_range(0..3);
Ok(match r {
0 => {
let _ = serde_yaml::from_str("invalid yaml")
.context(YamlSnafu)
.context(ParseSnafu { field: "0" })?;
"0".to_string()
}
1 => {
let _ = serde_json::from_str("invalid json")
.context(JsonSnafu)
.context(ParseSnafu { field: "0" })?;
"1".to_string()
}
_ => {
return MessageSnafu { message: "blah" }
.fail()
.context(ParseSnafu { field: "2" })
}
})
} Thoughts I had while reading your code:
Another option, which avoids the repetition of the fn main_wrapped() -> Result<String, Error> {
let field: i32 = rand::thread_rng().gen_range(0..3);
let tmp = (|| {
Ok(match field {
0 => {
let _ = serde_yaml::from_str("invalid yaml").context(YamlSnafu)?;
"0".to_string()
}
1 => {
let _ = serde_json::from_str("invalid json").context(JsonSnafu)?;
"1".to_string()
}
_ => return MessageSnafu { message: "blah" }.fail(),
})
})();
tmp.context(ParseSnafu { field })
} If you really don't have anything to add to the JSON / YAML errors and each can only occur exactly once, then you can remove all context from them: #[derive(Debug, Snafu)]
pub enum InnerParseError {
#[snafu(context(false), display("{source}"))]
YamlError { source: serde_yaml::Error },
#[snafu(context(false), display("{source}"))]
JsonError { source: serde_json::Error },
#[snafu(display("{message}"))]
MessageError { message: String },
}
fn main_wrapped() -> Result<String, Error> {
let field: i32 = rand::thread_rng().gen_range(0..3);
let tmp = (|| {
Ok(match field {
0 => {
let _ = serde_yaml::from_str("invalid yaml")?;
"0".to_string()
}
1 => {
let _ = serde_json::from_str("invalid json")?;
"1".to_string()
}
_ => return MessageSnafu { message: "blah" }.fail(),
})
})();
tmp.context(ParseSnafu { field })
} |
Beta Was this translation helpful? Give feedback.
-
What an awesome answer! It would be nice to see it in the examples. Calling context twice makes a lot of sense.
That's an artifact of me flailing trying to get something to work. Just reducing the poorly-understood magic. I'm happy to ditch that.
That's just an artifact of the MRE reductions. There are several more meaningful errors.
MRE cut & pasta
Another simplification from my code, although I have had problems with fail. But that'd probably go away once I fix the other problems...
Good point. I'll test that.
That's a good segue into my next decision point. Do I make InnerParseError opaque or not? Your documentation on that point is pretty good, I'm not sure which is best. Obviously if it's opaque then the fields need to be in the parent. Otherwise flattening makes sense.
Ooooh, that's a useful trick. Not sure it'll work where I MRE'd from, but it'll definitely be nice elsewhere in my code! |
Beta Was this translation helpful? Give feedback.
-
I'm trying out snafu, and something I ran into that seems potentially related to this issue is how to represent errors encountered while handling other errors, where I want to retain context about both. Currently I've wound up with something like: #[derive(Debug, Snafu)]
enum MyInnerError {
SomeVariant,
}
#[derive(Debug, Snafu)]
enum MyOuterError {
WrapsInnerError {
during: MyInnerError,
source: std::io::Error,
},
}
fn returns_my_error() -> std::result::Result<(), MyInnerError> {
todo!()
}
fn returns_io_error() -> std::result::Result<(), std::io::Error> {
todo!()
}
fn calls_stuff_that_errors() -> std::result::Result<(), MyOuterError> {
match returns_my_error() {
Ok(_) => todo!(),
Err(e) => {
// handle error by calling some other code that potentially errors
returns_io_error().context(WrapsInnerSnafu { during: e })
}
}
} But I was just wondering if there was a more standard way to do this. I feel like there should be something I can do by chaining errors I hope you don't mind me hijacking this issue! Let me know if this is too different from the original question and I should open a new one. |
Beta Was this translation helpful? Give feedback.
If I understand what you want, I'd probably end up with