PROLOGUE

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

QUESTION 1

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?

QUESTION 2

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:

  • main.rs: returns error with .with_context(|| "I'm the error number 4")?
    • call fn player_create() in graphql.rs: returns with .with_context(|| "I'm the error number 3")?
      • call fn new_player() in domain.rs: returns with .with_context(|| "I'm the error number 2")?
        • call 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.

2

There are 2 best solutions below

2
Field On

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(())
}
1
Nick Larsen On

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.