Seeking Help as a Rust Beginner : On error propagation

Hi fellows, I just started to write Rust and the error propagation issue really annoys me. Can I ask for help on a piece of my code and some advice?

I have a function of simply extracting the value from a json type with:

fn extract_access_token(&self, json_string: &str) -> Result<String, &'static str> {
        let json: Value = serde_json::from_str(json_string).map_err(|_| "Failed to parse JSON")?;
        let access_token = json["access_token"]
            .as_str()
            .ok_or("Access token not found")?
            .to_owned();
        Ok(access_token)
    }

and I want to use this in another function with calling the API below

async fn acquire_new_token(&self) -> Result<String, Error> {
        let url = format!("{}{}", self.base_url, "/token");
        let client = Client::new();
        let response = client
            .post(&url)
            .header("Content-Type", "application/json")
            .basic_auth(self.username.clone(),Some(self.password.clone()),)
            .send().await?;
        let body_text = response.text().await?;

        let access_token = self.extract_access_token(&body_text)?;
        Ok(access_token)
    }

Of course there's an error because of the error type does not match for this is a &str type.

let access_token = self.extract_access_token(&body_text)?;

May I ask how shall I change the code? feel free to blame the code also.

Also, some one told me that I shall just use unwarp + return Some + fn function -> Option, so I can bypass all the Error handler issue. May I ask is it a good advice for developers or as a beginner?

I have no idea what that means, but you definitely shouldn't just always unwrap instead of properly handling errors. That would turn every error into a panic, effectively crashing your program even on recoverable errors.

Then you need to turn the inner error into whatever the outer error's type is, likely using the .map_err() method of Result.

1 Like

When you use the ? operator, you must match the type of the Err branch of Result from the function. In your case, you can't do it because extract_access_token has &'static str in its Err branch, while acquire_new_token has Error in its Err branch.

The idiomatic way of doing error handling in Rust is to leverage the From trait to convert the foreign error types to your domain error type.

for the first one, I was just told to unwarp everything and only Return Some() instead of Ok().... yeah I think it's not a good practice.

Thanks! I thought it should be using .map_err().... but I just got stuck on this who to map it to the Error...

thanks! seems I still shall spend some time on mapping the Errors. Thanks you all for the advice!

sorry I made a mistake, the type Error was from

use reqwest::{header::AUTHORIZATION, Client, Error, Request, Response};

So the hard time for me is to convert the &str to a reqwest::Error....

Afaik, it's common practice to create your own Error model as an enum. For example:

MyError {
  Validation,
  IO,
  Internal
}

And then you implement the From trait to convert a reqwest::Error into a MyError.

I would not recommend to use &str as your error model. If you want to learn more about error handling in Rust, I recommend you to read the following article: Error Handling In Rust - A Deep Dive | Luca Palmieri.

2 Likes

thanks man!!! I think I better learn from others' code. Thanks so much for the advice!

Hey there, this is how I've been doing it:

In this module I need to handle 3 different types of errors, one is returned by the jsonwebtoken crate, another is returned from the database, which is another error type. The database might also return an Option::None in case the query returns nothing, which I also convert to another kind of error.

To do this I've created an enum describing the types of errors this module can throw:

pub enum Error {
    NotFound,
    JWT(jsonwebtoken::errors::Error),
    DB(DbErr),
}

Here's a function using it:

    pub async fn decode(
        &self,
        token: &str,
        connection: &DatabaseConnection,
    ) -> Result<(Claims, CreatedToken), Error> { // Returns the enum from before
        let Self(secret, _) = self;

        let token = AuthToken::find_by_id(token)
            .one(connection)
            .await
            .map_err(|err| Error::DB(err))? // converts from sea_orm::DBErr to Error::DB(DBErr)
            .ok_or(Error::NotFound)? // converts Option::None to Error::NotFound
            .token;

        let result = jsonwebtoken::decode::<Claims>(
            &token,
            &DecodingKey::from_secret(secret.as_ref()),
            &Validation::default(),
        );

        let claims = match result {
            Ok(token_data) => token_data.claims,
            // here i'm matching it directly but notice that I'm doing an early return that's compatible
           // with the function signature
            Err(err) => {
                AuthToken::delete_by_id(token)
                    .exec(connection)
                    .await
                    .map_err(|err| Error::DB(err))?;

                return Err(Error::JWT(err));
            }
        };

        Ok((claims, CreatedToken(token)))
    }
1 Like

wow, thanks so much bro!!! it's so good that you share the code, really appreciate it!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.