Using unwrap() while already inside error handling

Hi!

In a small project I'm currently developing I use reqwest to make a few http requests.

Since it is a simple cli script, the error handling does not need to be so elaborate (e.g. custom errors using thiserror or something simliar).
Basically all errors exit the script by using ".expect(...)" with a proper error message of my own choosing - so the requirement for error handling is just that I control the messages the user gets.

Is there a nice and clean way to handle a case, where my own error message includes a call, that itself can error out?

Currently I'm doing it like this:

use reqwest::StatusCode;

#[tokio::main]
async fn main() {
    let request_url = "https://httpstat.us/401";

    let client = reqwest::Client::new();
    let response = client
        .get(request_url)
        .send()
        .await
        .expect("unable to fetch document data");

    // check http return code
    match response.status() {
        StatusCode::OK => (),
        StatusCode::UNAUTHORIZED => panic!(
            "got a 401 response - it seems the api token does not work: {:#}",
            response.text().await.unwrap()
        ),
        _ => panic!(
            "something unexpected happened: {:#}",
            response.text().await.unwrap()
        ),
    }
}

I just want to display what the server returned. But calling unwrap() while already in error handling seems a bit off.
So if text() itself produces an error result, the unwrap would end the script (which is ok, I'm panic'ing anyway) - but not with my own error message.

I already thought of moving the text() outside of the match and handling it separately with a let-else.
But then I'd need to call it not matter what the http return code is.

And I'm not using the text for anything but the error message - later on in my script there is another call to json() using serde for deserializing the response in my own struct.

Is there a smart way to handle this without doubling the code needed for it?

Thanks for your insights!

Write a function to do the error handling. Note that, for example, you probably don't want to print the entire content of the response.text() anyway — if something in the genre of "the URL is defunct or mistyped" happens, it's plausible you'd get a big HTML error response you don't want to print. So, there are assorted complexities here anyway, if you want to give users good helpful messages, and it's reasonable to dedicate part of your code to that work, while keeping it separate to declutter the successful path. A sketch:

    ...

    match response.status() {
        StatusCode::OK => (),
        StatusCode::UNAUTHORIZED => {
            handle_wrong_status_code(response, "it seems the api token does not work").await
        }

        _ => handle_wrong_status_code(response, "something unexpected happened").await,
    }
}

async fn handle_wrong_status_code(response: reqwest::Response, notes: &str) -> ! {
    let status = response.status();
    match response.text().await {
        Ok(mut text) => {
            if text.len() > 1000 {
                // Note: It would be more efficient to cut off the download after a
                // certain length rather than accepting the whole thing and then
                // truncating it. This is a quick example.
                text.truncate(1000);
                text.push_str("...");
            }
            panic!("got a {status} response - {notes}: {text:#}",);
        }
        Err(e) => {
            panic!("got a {status} response - {notes} - and also failed to download body: {e}");
        }
    }
}
2 Likes

Thanks!

Yes, I think I was looking for "one-line-solutions" too much :slight_smile: