Search code examples
rustreqwest

Rust: Response error handling using reqwest


Ok, so I'm very new to Rust and I'm trying to clumsily piece together a little CLI tool that makes http requests and handles the responses, by using tokio, clap, reqwest and serde.

The tool accepts a customer number as input and then it tries to fetch information about the customer. The customer may or may not have a FooBar in each country.

My code currently only works if I get a nice 200 response containing a FooBar. If I don't, the deserialization fails (naturally). (Edit: Actually, this initial assumption about the problem seems to be false, see comments below)

My aim is to only attempt the deserialization if I actually get a valid response.

How would I do that? I feel the need to see the code of a valid approach to understand this better.

Below is the entirety of my program.


use clap::Parser;
use reqwest::Response;
use serde::{Deserialize, Serialize};

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let args: Cli = Cli::parse();

    let client = reqwest::Client::new();

    let countries = vec!["FR", "GB", "DE", "US"];

    for country in countries.iter() {
        let foo_bar : FooBar = client.get(
            format!("http://example-service.com/countries/{}/customers/{}/foo_bar", country, args.customer_number))
            .send()
            .await?
            .json()
            .await?;

        println!("{}", foo_bar.a_value);
    }

    Ok(())
}

#[derive(Debug, Serialize, Deserialize)]
struct FooBar {
    a_value: String,
}

#[derive(Parser, Debug)]
struct Cli {
    customer_number: i32,
}

Solution

  • There are a few ways to approach this issue, first of all you can split the json() deserialization from send().await, i.e.:

        for country in countries.iter() {
            let resp: reqwest::Response = client.get(
                format!("http://example-service.com/countries/{}/customers/{}/foo_bar", country, args.customer_number))
                .send()
                .await?;
            if resp.status() != reqwest::StatusCode::OK {
                 eprintln!("didn't get OK status: {}", resp.status());
            } else {
                 let foo_bar = resp.json().await?;
                 println!("{}", foo_bar.a_value);
            }
        }
    

    If you want to keep the response body around, you can extract it through let bytes = resp.bytes().await?; and pass bytes to serde_json::from_slice(&*bytes) for the deserialization attempt.

    This can be useful if you have a set of expected error response bodies.