Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust handling error response bodies with Reqwest

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?

like image 602
m_callens Avatar asked Apr 25 '20 16:04

m_callens


1 Answers

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.

like image 69
Matthew Avatar answered Nov 08 '22 11:11

Matthew