Reputation: 4136
I'm using async-graphql and I have hundreds of resolvers and for each resolver I would like to trace all the possible errors.
In each method of my app I'm using anyhow::{Error}
.
Right now I have code similar to this for each resolver:
#[Object]
impl MutationRoot {
async fn player_create(&self, ctx: &Context<'_>, input: PlayerInput) -> Result<Player> {
let services = ctx.data_unchecked::<Services>();
let player = services
.player_create(input)
.await?;
Ok(player)
}
}
So I thought about using the below code (note the added line with .map_err()
):
#[Object]
impl MutationRoot {
async fn player_create(&self, ctx: &Context<'_>, input: PlayerInput) -> Result<Player> {
let services = ctx.data_unchecked::<Services>();
let player = services
.player_create(input)
.await
.map_err(errorify)?;
Ok(player)
}
}
fn errorify(err: anyhow::Error) -> async_graphql::Error {
tracing::error!("{:?}", err);
err.into()
}
Now the error is traced along with all the error chain:
ERROR example::main:10: I'm the error number 4
Caused by:
0: I'm the error number 3
1: I'm the error number 2
2: I'm the error number 1
in example::async_graphql
Is there a way to avoid the .map_err()
on each resolver?
I would like to use the ?
alone.
Should I use a custom error?
Can we have a global "hook"/callback/fn to call on each error?
As you can see above the chain of the error is the inverse.
In my graphql response I'm getting as message
the "I'm the error number 4" but I need to get the "I'm the error number 2" instead.
The error chain is created using anyhow
like this:
.with_context(|| "I'm the error number 4")?
fn player_create()
in graphql.rs
: returns with .with_context(|| "I'm the error number 3")?
fn new_player()
in domain.rs
: returns with .with_context(|| "I'm the error number 2")?
fn save_player()
in database.rs
: returns with .with_context(|| "I'm the error number 1")?
How can I accomplish this?
I'm really new to Rust. I come from Golang where I was using a struct like:
type Error struct {
error error
public_message string
}
chaining it easily with:
return fmt.Errorf("this function is called but the error was: %w", previousError)
How to do it in Rust?
Do I necessarily have to use anyhow
?
Can you point me to a good handling error tutorial/book for applications?
Thank you very much.
Upvotes: 2
Views: 926
Reputation: 459
I would suggest you define your own error for your library and handle them properly by using thiserror
crate.
It's like Go defining var ErrNotFound = errors.New(...)
and use fmt.Errorf(..., err)
to add context.
With the powerful tool enum
in Rust, so you can handle every error properly by match
arms. It also provides really convenient derive macro like #[from]
or #[error(transparent)]
to make error conversion/forwarding easy.
Edit 1: If you want to separate public message and tracing log, you may consider defining you custom error struct like this:
#[derive(Error, Debug)]
pub struct MyError {
msg: String,
#[source] // optional if field name is `source`
source: anyhow::Error,
}
and implement Display
trait for formatting its inner msg
field.
Finally, you could use macro in tracing-attributes
crate:
#[instrument(err(Debug))]
fn my_function(arg: usize) -> Result<(), std::io::Error> {
Ok(())
}
Upvotes: 3
Reputation: 1
Is there a way to avoid the .map_err() on each resolver?
Yes, you should be able to remove it unless you really need to convert to async_graphql::Error
.
Do I necessarily have to use anyhow?
No, but it makes this easier when using ?
on different error types.
I'm really new to Rust. I come from Golang where I was using a struct like:
Take a look at thiserror
which lets you build you own enum
of error variants.
Upvotes: 0