Fred Hors
Fred Hors

Reputation: 4136

Error handling for applications: how to return a public message error instead of all the chain of errors and tracing it at the same time?

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:

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

Answers (2)

Field
Field

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

Nick Larsen
Nick Larsen

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

Related Questions