Reputation: 1560
I'm attempting to send a request off with Hyper, then deserialize it via JSON through Serde, but I cannot seem to wrap my head around futures and I'm receiving type mismatch errors stating expected (), found struct [put some odd struct here]
. Nor can I wrap my head around the incredibly long and confusing error messages they spit out on each change. Here's my code:
extern crate futures;
extern crate hyper;
extern crate serde;
extern crate serde_json;
use futures::{
Future,
Stream,
future
};
use hyper::{
Body,
Client,
Response,
StatusCode,
Uri,
client::HttpConnector,
};
use serde::{ Deserialize };
use std::error::{ Error };
enum JsonError
{
RequestError(hyper::Error),
ResponseError(StatusCode),
DeserializeError(serde_json::Error),
}
fn get_json
<'t, T, F>
(client: &Client<HttpConnector>, uri: Uri)
-> impl Future<Item = T, Error = JsonError>
where
T : Deserialize<'t>
{
let f = client
.get(uri)
.map(|response|
{
let (parts, body) = response.into_parts();
if parts.status.is_success()
{
body
.fold(
vec![],
|mut accum, chunk|
{
accum.extend_from_slice(&*chunk);
Ok(accum)
}
)
.map(|v|
{
serde_json::from_slice::<T>(&v)
.map_err(|err| JsonError::DeserializeError(err))
})
}
future::err(JsonError::ResponseError(parts.status))
})
.map_err(|err| JsonError::RequestError(err));
return f;
}
I am completely lost and I think any advice could help at this point.
Upvotes: 2
Views: 1241
Reputation: 19672
You have multiple errors stemming from logic issues when chaining futures. I have fixed its implementation, and it is available on the playground but I would strongly recommend you walk with me through what I changed.
But first, a recurring trend in your code:
Future::map()
's return typeFuture::map()
allows you to change a future result of type T
into type R
, and it does so with the assumption that the transformation cannot fail (i.e. Fn(T) -> R
). You've used map()
multiple times in your code while returning either another Future
, or a Result
. Both are incorrect.
For Future
chaining, and_then()
allows you to perform the mapping fn(T) -> IntoFuture<Item = R>
with error types remaining the same
For Result
, convert them via future::result()
into an already-executed future so you can also and_then()
Errors do not convert themselves, especially not if you do not define their conversion methods. For this purpose, I've implemented From<hyper::Error>
for your error type:
impl From<hyper::Error> for JsonError {
fn from(s: hyper::Error) -> JsonError {
JsonError::RequestError(s)
}
}
This allows you to use into()
wherever the typechecker notices that it is possible to do so.
Do note, however, that because of hyper
's own response type, err_into()
gets the type checker confused, hence the explicit map(|r| r.into())
.
Deserialize
and lifetimesDeserialize<'t>
is not the exact trait you were looking for. This trait implies that the entire object needs to live for lifetime 't
. In a non-futures world, this would probably pass, but not in this case where the return object needs to be owned (hence the lifetime error you are getting).
for<'t> Deserialize<'t>
is a completely different beast, and tells the compiler that this trait will have a lifetime 't
, but will then be an owned object, or in other words, that the slice used to create the object will need to live for lifetime 't
, not the entire returned object. Just what we need!
An additional nitpick: you really ought to separate the response parsing from the HTTP extraction in this function. As it stands right now, if I make a request over HTTPS, I will not be able to use your get_json()
function, as my connector for hyper
would then be TlsConnector<HttpConnector>
. Problematic ;-)
The code:
use futures::{future, Future, Stream};
use hyper::{client::HttpConnector, Client, StatusCode, Uri};
use serde::Deserialize;
enum JsonError {
RequestError(hyper::Error),
ResponseError(StatusCode),
DeserializeError(serde_json::Error),
}
impl From<hyper::Error> for JsonError {
fn from(s: hyper::Error) -> JsonError {
JsonError::RequestError(s)
}
}
fn get_json<T, F>(
client: &Client<HttpConnector>,
uri: Uri,
) -> impl Future<Item = T, Error = JsonError>
where
T: for<'t> Deserialize<'t> + 'static,
{
client.get(uri).map_err(|e| e.into()).and_then(
|response| -> Box<dyn Future<Item = T, Error = JsonError>> {
let (parts, body) = response.into_parts();
match parts.status.is_success() {
true => Box::new(body
.map_err(|e| e.into())
.fold(
vec![],
|mut accum, chunk| -> Box<dyn Future<Item = Vec<u8>, Error = JsonError>>
{
accum.extend_from_slice(&*chunk);
Box::new(future::ok(accum))
}
)
.and_then(|v|
{
future::result(serde_json::from_slice::<T>(&v))
.map_err(|err| JsonError::DeserializeError(err))
})),
false => Box::new(future::err(JsonError::ResponseError(parts.status)))
}
},
)
}
Upvotes: 5