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?
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.
Related
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,
}
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.
I'm having a hard time trying to figure out how to error handle this piece of code.
This is the endpoint of my actix_web api, up until now everything works:
pub async fn simple_moving_average() -> HttpResponse {
if Client::new()
.get("https://coinranking1.p.rapidapi.com/coins")
.header(
"X-RapidAPI-Key",
"MY_KEY")
.send()
.await
.is_err()
{
HttpResponse::BadRequest().finish();
}
HttpResponse::Ok().finish()
}
It's when I try to extract the data from the response that the compiler starts screaming at me:
[...]
let response = Client::new()
.get("https://coinranking1.p.rapidapi.com/coins")
.header(
"X-RapidAPI-Key",
"MY_KEY")
.send()
.await?
.json::<Root>()
.await?;
HttpResponse::Ok().finish()
}
cargo check ->
he ? operator can only be used in an async function that returns
Result or Option (or another type that implements FromResidual)
the trait FromResidual<Result<Infallible, reqwest::Error>> is not
implemented for HttpResponserustcE0277 simple_moving_average.rs(50,
65): this function should return Result or Option to accept ?
Root in .json::<Root>() is just a struct representing json payload. I generated it from this website, not sure if it's relevant
how do I solve that? My final goal here would be to return a json response containing the data I get from this request. I've tried implementing the ::thiserror crate but I cannot figure out how it works and honestly I would prefer to understand how error handling works before using some fast solution.
I've tried following what compiler suggests but I cannot seem to solve the situation.
I solved by using the dyn trait
correct code:
pub async fn simple_moving_average() ->
Result<HttpResponse, Box<dyn std::error::Error>>{ [...] }
I'm trying to write an authentication middleware for my Actix application. When validating the request in the middleware, I make a call to a database to retrieve the necessary user data to validate the incoming request. Once the request has been authorised, I want to be able to pass this user data to the handler as this will allow me to avoid having the query for the same data twice.
I can't find a solution for this. The best suggestion I could find so far was to "set a request extension". There doesn't seem to be any examples for this and there is also too little documentation around this to work out what to do here.
You can pass data from middleware (service) to handler via extensions. First of all you have to insert extension (in service).
For ServiceRequest struct is implemented HttpMessage witch hase extensions_mut() function. It must be mutable becouse you will be inserting new extension. It might look something like this:
req.extensions_mut().insert(user);
Then you have to implement FromRequest trait for your data structure.
impl FromRequest for User {
type Error = actix_web::Error;
type Future = futures::future::Ready<Result<Self, Self::Error>>;
type Config = ();
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
match req.extensions().get::<User>() {
Some(user) => return ok(user.clone()),
None => return err(actix_web::error::ErrorBadRequest("ups..."))
};
}
}
Then you're ready to use it in handler.
pub async fn get_user_handler(user: User) {}
I'm using rust + rocket + diesel (orm) + serde_derive to make a rest api. Currently, I'm dealing with error handling for the api if diesel fails to insert a user for whatever reason. It looks like this:
pub fn create(user: InsertableUser, connection: &MysqlConnection) -> ApiResponse {
let result = diesel::insert_into(users::table)
.values(&InsertableUser::hashed_user(user))
.execute(connection);
match result {
Ok(_) => ApiResponse {
json: json!({"success": true, "error": null}),
status: Status::Ok,
},
Err(error) => {
println!("Cannot create the recipe: {:?}", error);
ApiResponse {
json: json!({"success": false, "error": error}),
status: Status::UnprocessableEntity,
}
}
}
}
However, json: json!({"success": false, "error": error}), gives me this error:
the trait bound `diesel::result::Error: user::_IMPL_DESERIALIZE_FOR_User::_serde::Serialize` is not satisfied
the trait `user::_IMPL_DESERIALIZE_FOR_User::_serde::Serialize` is not implemented for `diesel::result::Error`
note: required because of the requirements on the impl of `user::_IMPL_DESERIALIZE_FOR_User::_serde::Serialize` for `&diesel::result::Error`
note: required by `serde_json::value::to_value`rustc(E0277)
<::serde_json::macros::json_internal macros>(123, 27): the trait `user::_IMPL_DESERIALIZE_FOR_User::_serde::Serialize` is not implemented for `diesel::result::Error`
By the sounds of it, diesel::result::Error does not #[derive(Serialize)], and so cannot be serialized with the json! macro. Thus, I need some way to make the diesel::result::Error implement/derive Serialize.
Thanks in advance for any help.
BTW the ApiResponse looks like:
use rocket::http::{ContentType, Status};
use rocket::request::Request;
use rocket::response;
use rocket::response::{Responder, Response};
use rocket_contrib::json::JsonValue;
#[derive(Debug)]
pub struct ApiResponse {
pub json: JsonValue,
pub status: Status,
}
impl<'r> Responder<'r> for ApiResponse {
fn respond_to(self, req: &Request) -> response::Result<'r> {
Response::build_from(self.json.respond_to(&req).unwrap())
.status(self.status)
.header(ContentType::JSON)
.ok()
}
}
Serde provides a workaround for deriving serialization implementations for external crates - see the section Derive for remote crates in their documentation.
You have to define an enum with the same definition as the one you are trying to serialize (diesel::result::Error in your case), and then identify that as a kind of proxy for the type you are trying to serialize, like this:
#[derive(Serialize, Deserialize)]
#[serde(remote = "diesel::result::Error")]
struct ErrorDef {
// Definition in here the same as the enum diesel::result::Error
// ...
}
Of course you would have to do the same for all of the types enclosed within the Error type as well (or at least any types that don't already implement Serialize).
The documentation states that Serde checks the definition you provide against the one in the 'remote' crate, and throws an error if they differ, which would help keep them in sync.
Also note that this does not result in diesel::result::Error implementing Serialize - rather you now have a stand-in type that you can use like this:
struct JsonErrorRespone {
pub success: bool,
#[serde(with = "ErrorDef")]
pub error: diesel::result::Error,
}
You would then serialize an instance of the above struct instead of your existing json! macro call.
Alternatively the document linked above also gives some tips for manually calling the correct Serialize / Deserialize implementations.
Disclaimer: I haven't used this facility yet, the above was gleaned from the documentation only.
The short answer: yes you can. Personally I've always found it a bit difficult.
As a compromise, you could pull out just the parts of the error that are relevant to you, or even do:
ApiResponse {
json: json!({"success": false, "error": error.to_string() }),
status: Status::UnprocessableEntity,
}
If you're content with just a textual representation of the error.
I'd like to have an Actix Web handler which responds to a POST request by printing the POST body to the console and constructing an HTTP response that contains the current URL from the request object.
When reading the request's POST body, futures seem to need to be involved. The closest I've gotten so far is:
fn handler(req: HttpRequest) -> FutureResponse<HttpResponse> {
req.body()
.from_err()
.and_then(|bytes: Bytes| {
println!("Body: {:?}", bytes);
let url = format!("{scheme}://{host}",
scheme = req.connection_info().scheme(),
host = req.connection_info().host());
Ok(HttpResponse::Ok().body(url).into())
}).responder()
}
This won't compile because the future outlives the handler, so my attempts to read req.connection_info() are illegal. The compiler error suggests I use the move keyword into the closure definition, i.e. .and_then(move |bytes: Bytes| {. This also won't compile because req gets moved on the req.body() call and is then captured after the move in the references constructing url.
What is a reasonable way of constructing a scope in which I have access to data attached to the request object (e.g. the connection_info) at the same time as access to the POST body?
The simplest solution is to not access it inside the future at all:
extern crate actix_web; // 0.6.14
extern crate bytes; // 0.4.8
extern crate futures; // 0.1.21
use actix_web::{AsyncResponder, FutureResponse, HttpMessage, HttpRequest, HttpResponse};
use bytes::Bytes;
use futures::future::Future;
fn handler(req: HttpRequest) -> FutureResponse<HttpResponse> {
let url = format!(
"{scheme}://{host}",
scheme = req.connection_info().scheme(),
host = req.connection_info().host(),
);
req.body()
.from_err()
.and_then(move |bytes: Bytes| {
println!("Body: {:?}", bytes);
Ok(HttpResponse::Ok().body(url).into())
})
.responder()
}
In case this is more than a quick hack for demonstration purposes, constructing URLs by concatenating strings is a terrible idea as it doesn't properly escape the values. You should be using a type that does that for you.