Returning futures from functions was made a little ugly by some aspects of the
type system, prior to impl Trait.

I'll be showing an example of how this would be done prior to 1.26, and
then show how the code would be refactored to leverage impl Trait instead.

There are also significant changes to async strategies in Rust coming to the
language (later this year?) which will likely impact the design of the crates
we'll employ, but those changes are further out, potentially warranting a
follow-up post when the new features are stable.

In addition to being aware of the Rust version being targeted, it's worth
remembering that many of the crates being used here are pre-1.0, meaning
their APIs can be highly volatile with each release.

When creating a new client instance, we'll ask the caller to supply the
public and private keys, then the new() method takes care of the rest.

Inside, we pass the keys along to a new UriMaker (the struct we defined in
the previous post), as well as supplying the prefix for the URIs we will build.

Although the Marvel Comics API seems to offer access over both HTTP and HTTPS,
generally speaking, HTTPS should always be preferred. Hyper works with HTTP out
of the box, but to get HTTPS working, we actually need the help of another
crate.

To enable hyper to make HTTPS requests, hyper-tls is one such option, though
seemingly there are others. hyper-tls provides an HttpsConnector struct
which we can hand off to the hyper Client via its builder interface.
Since most of the pieces required for the construction of the Client are only
needed by the client itself, we can build the whole thing within a block to
hide the internals (the connector, and core handle) from prying eyes.

The core is a piece of the tokio landscape, and worth noting. The core is
essentially an "event loop" which may be a term you recognize from the async
APIs provided by other languages. The core will start up a background thread
which will monitor the status of additional "tasks" (threads) submitted to the
core for execution.

The futures crate pairs with tokio-core, and provides the primitives for
building the "tasks" we submit to the core. Building individual futures for
execution in the core is the simplest case, but the API allows for the more
advanced usage of chaining futures together as a graph of tasks.

Next, we'll add a method that will run a basic HTTP request (as a future) and
parse the response body as JSON. Since this method will return a
futures::Future, we will be able to chain it to extract more meaningful
values from the resulting JSON response.

The serde crate is the core library which provides traits and types that the
various format-providing crates are built on top of.

The serde_derive crate is used for the auto-generation the Serialize and
Deserialize traits based on a given struct.
This is a nice convenience since the appropriate JSON types corresponding to
many primitive types in Rust are well known.

Finally, the serde_json crate adds in the format-specific logic to allow
serde to parse and produce JSON data.

So long as the types listed for each field are compatible with the types found
for the corresponding keys in the JSON object, we can parse this JSON and
unpack it into an instance of the struct. This is normally done by using one of
the many serde_json::from_* methods.

In our case, the data we receive from our HTTP requests will be a slice
of bytes. In Rust this is represented as &[u8], so we can
use serde_json::from_slice() to parse these responses.

view in playground
In the above example the type hint of Character tells serde how to collect
the data it's trying to parse. If the raw JSON does not conform to the shape,
the Result return from serde_json::from_slice() will be a
serde_json::Error.

Since we're going to be making different requests resulting in differently
shaped JSON structures, it might be nice to not specialize right away.

For this, serde has an intermediate representation of the parsed data
structure known as serde_json::Value. This generic Value allows for
indexing, so you can do some basic traversal through the JSON data to retrieve
a single value rather than building many intermediate structs to get down to a
specific branch of the tree.
You can also take unpack them into a struct with serde_json::from_value()
just as you would when parsing string or byte JSON data.

Doing the initial JSON parsing (as a Value), then specializing later, can be
especially helpful if you want to have overlapping "views" of the same data.
For the purpose of our project, doing an initial Value parse will help us to
fail fast if the response body is not even valid JSON and give us a nice
generic type we can use in our signatures for the futures we'll return when
making HTTP requests to the Web API.

Futures can succeed or fail, just like a Result. As such they are defined
with two associated types. In the next code sample, you'll see the future type
of the fn get_json() method defined as
Future<Item = JsValue, Error = io::Error>, meaning if the future succeeds
we'll end up with a serde_json::Value, or otherwise we'll get a
std::io::Error.

hyper expects the types of Error for futures to conform to some specific
kinds, and their docs hint towards using std::io::Error as a convenient path
to working with those expectations. This is because they handle the conversion
between std::io::Error and their own Error types. In light of this, we can
use a small helper function like the following:

use std::io;
fn to_io_error<E>(err: E) -> io::Error
where
E: Into<Box<std::error::Error + Send + Sync>>,
{
// We can create a new IO Error with an ErrorKind of "other", then
// pass in the actual error as data inside the wrapper type.
io::Error::new(io::ErrorKind::Other, err)
}

This helper will allow us to effectively wrap any Error type within a
std::io::Error, thus conforming to hyper's error handling demands.

In addition to succeeding and failing, futures can be combined in interesting
ways. You can chain futures together using the and_then() combinator meaning
each step in the chain will execute after the previous has completed.

Additionally, you can execute several futures in parallel and then operate on
the respective returns when they are all complete. In this way, we can define
complex graphs of async execution, all working towards producing a final
result.

Since hyper uses futures for making HTTP requests, and we'll ultimately be
making requests to various API endpoints, and
expecting JSON responses in each case, I opted to structure my client code
around the idea of doing this initial step in one function, returning
a future of a JSON value. Other functions can then be written to use this and
transform the value by chaining as needed.

The above code sample defines our new private function, get_json(), which
accepts a hyper::Uri to make a request to, then builds that request as a
future.

It is worth noting that calling this function will not cause any network
connections to be made. The future does not start execution until scheduled
in a tokio core. The return of this function is simply planned work to be
executed later.

As described in the tokio docs on returning futures, we
are returning a Box of our future type, which is what I'd consider the
most straightforward approach in Rust versions prior to 1.26.

For Rust 1.26 and above, impl Trait offers a simplified syntax which
does not require us to put the future in a Box thus making it faster, and
more memory efficient.

You can see the more simple, and more efficient version in the impl-trait
branch of the the marvel-explorer repo (diff).