weeb_api 0.2.0

A Rust library for the Weeb.sh API.
Documentation
//! Bridge to provide a client implementation for the `hyper` crate.
//!
//! # Examples
//!
//! Refer to the documentation for [`WeebApiRequester`].
//!
//! [`WeebApiRequester`]: trait.WeebApiRequester.html

use futures::future;
use futures::{Future, Stream};
use hyper::client::{Client as HyperClient, Connect};
use hyper::error::Error as HyperError;
use hyper::header::Authorization;
use hyper::{Method, Request, Uri};
use serde_json;
use std::str::FromStr;
use ::model::*;
use ::{Error, ImageParams, constants};

macro_rules! try_uri {
    ($uri:ident) => {
        match Uri::from_str($uri) {
            Ok(v) => v,
            Err(why) => return Box::new(future::err(Error::Uri(why))),
        }
    };
}

/// Trait which defines the methods necessary to interact with the service.
///
/// # Examples
///
/// To bring in the implemenation for the `hyper` Client, simply use the
/// trait:
///
/// ```rust,no_run
/// use weeb_api::WeebApiHyperRequester;
/// ```
///
/// At this point, the methods will be on your Hyper Client.
pub trait WeebApiRequester {
    /// Retrieves information about the API.
    ///
    /// # Examples
    ///
    /// Get basic info about the API:
    ///
    /// ```rust,ignore
    /// extern crate hyper;
    /// extern crate hyper_tls;
    /// extern crate weeb_api;
    /// extern crate tokio_core;
    ///
    /// use hyper_tls::HttpsConnector;
    /// use weeb_api::WeebApiHyperRequester;
    /// use hyper::Client;
    /// use tokio_core::reactor::Core;
    ///
    /// let mut core = Core::new()?;
    ///
    /// let connector = HttpsConnector::new(1, &core.handle())?;
    /// let client = Client::configure()
    ///     .connector(connector)
    ///     .build(&core.handle());
    ///
    /// let runner = client.get_images()
    ///     .map(|response| {
    ///         println!("The API version is: {:?}", response.version);
    ///     })
    ///     .map_err(|why| {
    ///         println!("Error getting image API info: {:?}", why);
    ///     });
    ///
    /// core.run(runner)?;
    /// ```
    ///
    // Note: This doc example can not be tested due to the reliance on
    // tokio_core.
    fn get_images(&self) -> Box<Future<Item = ApiResponse, Error = Error>>;

    /// Get info on an image.
    ///
    /// # Examples
    ///
    /// Get info about the image with the id "ryh6x04Rb":
    ///
    /// ```rust,ignore
    /// extern crate hyper;
    /// extern crate hyper_tls;
    /// extern crate weeb_api;
    /// extern crate tokio_core;
    ///
    /// use hyper_tls::HttpsConnector;
    /// use weeb_api::WeebApiHyperRequester;
    /// use hyper::Client;
    /// use std::env;
    /// use tokio_core::reactor::Core;
    ///
    /// let mut core = Core::new()?;
    ///
    /// let connector = HttpsConnector::new(1, &core.handle())?;
    /// let client = Client::configure()
    ///     .connector(connector)
    ///     .build(&core.handle());
    ///
    /// let token = env::var("WEEB_TOKEN")?;
    /// let id = "ryh6x04Rb";
    ///
    /// let runner = client.get_image_info(token, id)
    ///     .map(|image| {
    ///         println!("The image url is: {}", image.url);
    ///     })
    ///     .map_err(|why| {
    ///         println!("Error getting image info: {:?}", why);
    ///     });
    ///
    /// core.run(runner)?;
    /// ```
    ///
    // Note: This doc example can not be tested due to the reliance on
    // tokio_core.
    fn get_image_info<S: Into<String>>(&self, auth: S, id: &str)
        -> Box<Future<Item = Image, Error = Error>>;

    /// Retrieves a random image given the matching parameters.
    ///
    /// **Note**: You must specify at least one of [`kind`] or [`tags`] in the
    /// image params.
    ///
    /// # Examples
    ///
    /// Retrieve a random image with the "sleepy" kind:
    ///
    /// ```rust,ignore
    /// extern crate hyper;
    /// extern crate hyper_tls;
    /// extern crate weeb_api;
    /// extern crate tokio_core;
    ///
    /// use hyper_tls::HttpsConnector;
    /// use weeb_api::{ImageParams, WeebApiHyperRequester};
    /// use hyper::Client;
    /// use std::env;
    /// use tokio_core::reactor::Core;
    ///
    /// let mut core = Core::new()?;
    ///
    /// let connector = HttpsConnector::new(1, &core.handle())?;
    /// let client = Client::configure()
    ///     .connector(connector)
    ///     .build(&core.handle());
    ///
    /// let token = env::var("WEEB_TOKEN")?;
    /// let params = ImageParams::kind("sleepy");
    ///
    /// let runner = client.get_image_random(token, params)
    ///     .map(|image| {
    ///         println!("The random image url is: {}", image.url);
    ///     })
    ///     .map_err(|why| {
    ///         println!("Error getting random image: {:?}", why);
    ///     });
    ///
    /// core.run(runner)?;
    /// ```
    ///
    /// Retrieve a random image with the "girl" tag:
    ///
    /// ```rust,ignore
    /// extern crate hyper;
    /// extern crate hyper_tls;
    /// extern crate weeb_api;
    /// extern crate tokio_core;
    ///
    /// use hyper_tls::HttpsConnector;
    /// use weeb_api::{ImageParams, WeebApiHyperRequester};
    /// use hyper::Client;
    /// use std::env;
    /// use tokio_core::reactor::Core;
    ///
    /// let mut core = Core::new()?;
    ///
    /// let connector = HttpsConnector::new(1, &core.handle())?;
    /// let client = Client::configure()
    ///     .connector(connector)
    ///     .build(&core.handle());
    ///
    /// let token = env::var("WEEB_TOKEN")?;
    /// let params = ImageParams::tags(vec!["girl"]);
    ///
    /// let runner = client.get_image_random(token, params)
    ///     .map(|image| {
    ///         println!("The random image url is: {}", image.url);
    ///     })
    ///     .map_err(|why| {
    ///         println!("Error getting random image: {:?}", why);
    ///     });
    ///
    /// core.run(runner)?;
    /// ```
    ///
    // Note: This doc example can not be tested due to the reliance on
    // tokio_core.
    fn get_image_random<S: Into<String>>(&self, auth: S, params: ImageParams)
        -> Box<Future<Item = Image, Error = Error>>;

    /// Retrieves all the current image tags.
    ///
    /// # Examples
    ///
    /// Retrieve all image tags including hidden ones:
    ///
    /// ```rust,ignore
    /// extern crate hyper;
    /// extern crate hyper_tls;
    /// extern crate weeb_api;
    /// extern crate tokio_core;
    ///
    /// use hyper_tls::HttpsConnector;
    /// use weeb_api::{ImageParams, WeebApiHyperRequester};
    /// use hyper::Client;
    /// use std::env;
    /// use tokio_core::reactor::Core;
    ///
    /// let mut core = Core::new()?;
    ///
    /// let connector = HttpsConnector::new(1, &core.handle())?;
    /// let client = Client::configure()
    ///     .connector(connector)
    ///     .build(&core.handle());
    ///
    /// let token = env::var("WEEB_TOKEN")?;
    ///
    /// let runner = client.get_image_tags(token, true)
    ///     .map(|tags| {
    ///         println!("The image tags are: {:?}", tags);
    ///     })
    ///     .map_err(|why| {
    ///         println!("Error getting image tags: {:?}", why);
    ///     });
    ///
    /// core.run(runner)?;
    /// ```
    ///
    // Note: This doc example can not be tested due to the reliance on
    // tokio_core.
    fn get_image_tags<S: Into<String>>(&self, auth: S, hidden: bool)
        -> Box<Future<Item = Vec<String>, Error = Error>>;

    /// Retrieves all the current image types.
    ///
    /// # Examples
    ///
    /// Retrieve all image tags including hidden ones:
    ///
    /// ```rust,ignore
    /// extern crate hyper;
    /// extern crate hyper_tls;
    /// extern crate weeb_api;
    /// extern crate tokio_core;
    ///
    /// use hyper_tls::HttpsConnector;
    /// use weeb_api::{ImageParams, WeebApiHyperRequester};
    /// use hyper::Client;
    /// use std::env;
    /// use tokio_core::reactor::Core;
    ///
    /// let mut core = Core::new()?;
    ///
    /// let connector = HttpsConnector::new(1, &core.handle())?;
    /// let client = Client::configure()
    ///     .connector(connector)
    ///     .build(&core.handle());
    ///
    /// let token = env::var("WEEB_TOKEN")?;
    ///
    /// let runner = client.get_image_types(token, true)
    ///     .map(|types| {
    ///         println!("The image types are: {:?}", types);
    ///     })
    ///     .map_err(|why| {
    ///         println!("Error getting image types: {:?}", why);
    ///     });
    ///
    /// core.run(runner)?;
    /// ```
    ///
    // Note: This doc example can not be tested due to the reliance on
    // tokio_core.
    fn get_image_types<S: Into<String>>(&self, auth: S, hidden: bool)
        -> Box<Future<Item = Vec<String>, Error = Error>>;
}

impl<B, C: Connect> WeebApiRequester for HyperClient<C, B>
    where B: Stream<Error = HyperError> + 'static, B::Item: AsRef<[u8]> {
    fn get_images(&self) -> Box<Future<Item = ApiResponse, Error = Error>> {
        let c = constants::GET_IMAGES;
        let uri = try_uri!(c);

        Box::new(self.get(uri)
            .and_then(|res| res.body().concat2())
            .map_err(From::from)
            .and_then(|body| serde_json::from_slice(&body).map_err(From::from)))
    }

    fn get_image_info<S: Into<String>>(&self, auth: S, id: &str)
        -> Box<Future<Item = Image, Error = Error>> {
        let url = format!("{}{}", constants::GET_IMAGE_INFO, id);
        let c = &url;
        let uri = try_uri!(c);

        Box::new(self.request(make_request(auth, uri))
            .and_then(|res| res.body().concat2())
            .map_err(From::from)
            .and_then(|body| serde_json::from_slice(&body).map_err(From::from)))
    }

    fn get_image_random<S: Into<String>>(&self, auth: S, params: ImageParams)
        -> Box<Future<Item = Image, Error = Error>> {
        let built: Vec<(&str, String)> = params.into_data();

        if built.is_empty() {
            return Box::new(future::err(Error::NoParamsSpecified));
        }

        let params = built.iter().map(|&(query, ref value)| {
            format!("{}={}", query, value)
        }).collect::<Vec<String>>().join("&");

        let url = format!("{}?{}", constants::GET_IMAGE_RANDOM, params);
        let url = &url;
        let uri = try_uri!(url);

        Box::new(self.request(make_request(auth, uri))
            .and_then(|res| res.body().concat2())
            .map_err(From::from)
            .and_then(|body| serde_json::from_slice(&body).map_err(From::from)))
    }

    fn get_image_tags<S: Into<String>>(&self, auth: S, hidden: bool)
        -> Box<Future<Item = Vec<String>, Error = Error>> {
        let url = format!("{}?{}",
            constants::GET_IMAGE_TAGS,
            hidden.to_string(),
        );
        let c = &url;
        let uri = try_uri!(c);

        Box::new(self.request(make_request(auth, uri))
            .and_then(|res| res.body().concat2())
            .map_err(From::from)
            .and_then(|body| {
                serde_json::from_slice::<ImageTagsResponse>(&body)
                    .map_err(From::from)
            })
            .map(|x| x.tags))
    }

    fn get_image_types<S: Into<String>>(&self, auth: S, hidden: bool)
        -> Box<Future<Item = Vec<String>, Error = Error>> {
        let url = format!("{}?{}",
            constants::GET_IMAGE_TYPES,
            hidden.to_string(),
        );
        let c = &url;
        let uri = try_uri!(c);

        Box::new(self.request(make_request(auth, uri))
            .and_then(|res| res.body().concat2())
            .map_err(From::from)
            .and_then(|body| {
                serde_json::from_slice::<ImageTypesResponse>(&body)
                    .map_err(From::from)
            })
            .map(|x| x.types))
    }
}

fn make_request<B, S>(auth: S, uri: Uri)
    -> Request<B> where B: Stream<Error = HyperError> + 'static,
                        B::Item: AsRef<[u8]>,
                        S: Into<String> {
    let mut request = Request::new(Method::Get, uri);
    request.headers_mut().set(Authorization(auth.into()));

    request
}