Reputation: 33036
There is context
that can convert an optional value to anyhow::Error
which is very convenient.
Simplest way to unwrap an option and return Error if None (Anyhow)
However, how do we test that in unit-tests?
Let's say we have a foo
like this:
fn foo(input: i32) -> Result<i32> {
// this only keep odd numbers
let filtered = if input % 2 == 0 { Some(input) } else { None };
filtered.context("Not a valid number")
}
It is easy to test that it is valid output, or that the output is an error. But how do we test the error message from the context
?
mod test {
use super::*;
#[test]
fn test_valid_number() -> Result<()> {
let result = foo(4)?;
assert_eq!(result, 4);
Ok(())
}
#[test]
fn test_invalid_number() -> Result<()> {
let result = foo(3);
assert!(result.is_err());
Ok(())
}
// error[E0599]: no method named `message` found for struct `anyhow::Error` in the current scope
// --> src/main.rs:33:40
// |
// 33 | assert_eq!(result.unwrap_err().message(), "Not a valid number");
// | ^^^^^^^ method not found in `anyhow::Error`
#[test]
fn test_invalid_number_error_message() -> Result<()> {
let result = foo(3);
assert_eq!(result.unwrap_err().message(), "Not a valid number");
Ok(())
}
}
Upvotes: 8
Views: 3772
Reputation: 8964
You can use .chain()
and .root_cause()
to deal with levels of context and use .downcast_ref()
or format!
to handle the specific error. For example, lets say you had 2 levels of context.
use anyhow::*;
fn bar(input: i32) -> Result<i32> {
// this only keep odd numbers
let filtered = if input % 2 == 0 { Some(input) } else { None };
filtered.context("Not a valid number")
}
fn foo(input: i32) -> Result<i32> {
return bar(input).context("Handled by bar")
}
In this example the chain would be of the errors "Handled by bar"
-> "Not a valid number"
.
#[test]
fn check_top_error() -> Result<()> {
let result = foo(3);
let error = result.unwrap_err();
// Check top error or context
assert_eq!(format!("{}", error), "Handled by bar");
// Go down the error chain and inspect each error
let mut chain = error.chain();
assert_eq!(chain.next().map(|x| format!("{x}")), Some("Handled by bar".to_owned()));
assert_eq!(chain.next().map(|x| format!("{x}")), Some("Not a valid number".to_owned()));
assert_eq!(chain.next().map(|x| format!("{x}")), None);
Ok(())
}
#[test]
fn check_root_cause() -> Result<()> {
let result = foo(3);
let error = result.unwrap_err();
// Equivalent to .chain().next_back().unwrap()
let root_cause = error.root_cause();
assert_eq!(format!("{}", root_cause), "Not a valid number");
Ok(())
}
Now, you may have been wondering about my use of format!
. It turns out a better solution exists involving downcast_ref
, but it requires that your context implement std::error::Error
and str
does not. Here is an example of this taken directly from the anyhow
documentation.
use anyhow::{Context, Result};
fn do_it() -> Result<()> {
helper().context(HelperFailed)?;
...
}
fn main() {
let err = do_it().unwrap_err();
if let Some(e) = err.downcast_ref::<HelperFailed>() {
// If helper failed, this downcast will succeed because
// HelperFailed is the context that has been attached to
// that error.
}
}
As a side note, you may find it easier to use .then()
or .then_some()
for cases like if input % 2 == 0 { Some(input) } else { None }
where you create Some
based on a boolean. Simply put, if abc { Some(xyz) } else { None }
is equivalent to abc.then(|| xyz)
. .then_some()
passes by value instead of using a closure so I don't usually use it.
Upvotes: 6