Is there any better way to write this api(too many match statements)

Hello i've this code which simply updates user data in the DB but i'm not sure if this is idiomatic rust, i used 3 match statements is there any way to write this a better way? I also have 3 unwrap() s left so that's another 3 match statements.
So this api does these checks:
-Check if the JWT token is valid
-Check if the user exists
-Check if the hashed password inside the JWT token equals to the hashed password inside DB, This is to check if the password is changed,
-Check current password, if it's true proceed to updated the user with the new data and return a new JWT token.
So is there any better way to write this code?

match auth_token {
        Some(t) => {
            let token = t.to_str().unwrap();
            let decoded_token = decode_jwt(token).unwrap();

            let db = connect().await.unwrap();
            let users_collection = db.collection::<User>("users");

            let user_exists =
                find_document(&users_collection, doc! {"user_id":&decoded_token.user_id})
                    .await
                    .unwrap();
            // CHECK if user_exists
            match user_exists {
                Some(u) => {
                    // CHECK Token password
                    if u.password != decoded_token.password {
                        Ok(HttpResponse::Unauthorized().body("Password Changed"))
                    } else {
                        // CHECK password
                        let correct_password = check_password(&u.password, &user.password);
                        match correct_password {
                            true => {
                                // Update user
                                let update_user = UpdateUser {
                                    password: hash_password(user.new_password.clone()),
                                    country: user.country.clone(),
                                    phone_number: user.phone_number.clone(),
                                };
                                let document = bson::to_document(&update_user).unwrap();
                                let update_result = update_document(
                                    &users_collection,
                                    doc! {"user_id":&u.user_id},
                                    doc! {"$set":document},
                                )
                                .await
                                .unwrap()
                                .unwrap();
                                let token = Token {
                                    user_id: u.user_id,
                                    password: hash_password(Some(update_result.password)).unwrap(),
                                    iat: chrono::Utc::now().timestamp_millis() as u32,
                                    exp: chrono::Utc::now().timestamp_millis() as u32 + 999999,
                                };
                                let token = create_jwt(token).unwrap();
                                Ok(HttpResponse::Ok().body(token))
                            }
                            false => Ok(HttpResponse::Unauthorized().body("Incorrect Password")),
                        }
                    }
                }
                None => Ok(HttpResponse::Forbidden().body("Invalid email")),
            }
        }
        None => Ok(HttpResponse::Forbidden().body("Invalid token")),
    }

This is exactly what early returns and the ? operator are for. You could wrap the whole code in a function returning Result and then write something along the lines of

let t = auth_token.ok_or("missing token")?;
let decoded_token = decode_jwt(&t)?;
let u = find_document(&decoded_token.user_id).ok_or("user not found")?;

if u.password != decoded_token.password {
    return Err("invalid password");
}

// etc.

(You will probably have to create a suitable error type to accomodate all possible errors instead of just returning string literals, but that's the gist of it anyway.)

2 Likes

To add to @H2CO3's answer, this is how you can do it with early returns when you can't use ? (which is by the way just syntax sugar for a match and a early return):

let t = match auth_token {
    Some(t) => t,
    None => return Ok(HttpResponse::Forbidden().body("Invalid token")),
};

let token = t.to_str().unwrap();
let decoded_token = decode_jwt(token).unwrap();

let db = connect().await.unwrap();
let users_collection = db.collection::<User>("users");

let user_exists =
    find_document(&users_collection, doc! {"user_id":&decoded_token.user_id})
        .await
        .unwrap();

let u = match user_exists {
    Some(u) => u,
    None => return Ok(HttpResponse::Forbidden().body("Invalid email")),
};

// ...

Note that this assumes you originally returned those Ok(HttpResponse...) immediately.

3 Likes