Rust handling error response bodies with Reqwest
Asked Answered
P

1

17

I'm using the reqwest (version 0.10.4) crate for the HTTP calls in my Rust application but can't find any examples of how to handle APIs calls that could return more than one possible response body, mainly for error handling.

For instance, an API call could respond with a success JSON structure, or an error structure of format:

{
    "errors": ["..."]
}

Currently I have this code for the function, but can't seem to figure out how to determine which struct I need to deserialize the response buffer into based on whether the HTTP request was successful or not.

use super::responses::{Error, Response};
use crate::clients::HttpClient;
use crate::errors::HttpError;
use reqwest::header;

pub fn call() -> Result<Response, HttpError> {
    let url = format!("{}/auth/userpass/login/{}", addr, user);
    let response = HttpClient::new()
        .post(&url)
        .header(header::ACCEPT, "application/json")
        .header(header::CONTENT_TYPE, "application/json")
        .json(&serde_json::json!({ "password": pass }))
        .send();

    match response {
        Ok(res) => {
            let payload = res.json(); // could be `Error` or `Response` but only parses to `Response`
            match payload {
                Ok(j) => Ok(j),
                Err(e) => Err(HttpError::JsonParse(e)),
            }
        }
        Err(e) => Err(HttpError::RequestFailed(e)),
    }
}

Did I miss something in the documentation for reqwest or is this a common issue?

Photosynthesis answered 25/4, 2020 at 16:23 Comment(0)
D
13

Internally, res.json() uses the serde_json crate to deserialize the from JSON to your Rust object.

In Rust, when you want a type that have multiple different variants, you use an enumeration. serde implements this behavior for you, which allows you to deserialize to an enumeration, based on the format deserialized from. For example, you might define your response enumeration as follows:

#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum ResponseType {
  Ok(/* fields */),
  Err(/* fields */),
}

There is a lot going on there, but here are the highlights: #[serde(untagged)] tells serde that the enumeration should only be differentiated by the fields in Ok and Err. In your Rust code, you can differentiate by variant, using the full range of pattern matching, etc.

For your specific use case, it looks like the standard Result<V, E> enumeration should be good enough.

Dissimilate answered 25/4, 2020 at 17:8 Comment(1)
Thanks so much for this, #[serde(untagged)] solved my issueBaculiform

© 2022 - 2024 — McMap. All rights reserved.