Liam Mueller
Liam Mueller

Reputation: 1560

Futures - expected (), found struct futures::Map

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

Answers (1)

S&#233;bastien Renauld
S&#233;bastien Renauld

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:

#1: Future::map()'s return type

Future::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()

#2: Errors

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()).

#3: Deserialize and lifetimes

Deserialize<'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

Related Questions