extern crate chrono;
#[macro_use]
extern crate error_chain;
extern crate futures;
#[macro_use]
extern crate hyper;
extern crate hyper_tls;
extern crate native_tls;
extern crate num_cpus;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate tokio_core;
mod article;
pub mod error;
use std::rc::Rc;
use futures::{stream, Future, IntoFuture, Poll, Stream};
use hyper_tls::HttpsConnector;
use hyper::{Get, Request, Uri};
use hyper::client::{Client, HttpConnector};
use tokio_core::reactor::Handle;
pub use article::*;
pub use error::Error;
const ENDPOINT: &'static str = "https://mercury.postlight.com/parser";
type Connect = HttpsConnector<HttpConnector>;
#[derive(Debug)]
pub struct Mercury(Rc<Inner>);
impl Mercury {
pub fn new(handle: &Handle, key: String) -> Result<Mercury, Error> {
Inner::new(handle, key).map(Rc::new).map(Mercury)
}
pub fn handle(&self) -> &Handle {
self.client().handle()
}
pub fn key(&self) -> &str {
&self.0.key
}
pub fn parse(&self, resource: &str) -> Response {
let merc = Mercury::clone(self);
let f = build_url(resource).into_future().and_then(move |url| {
let mut req = Request::new(Get, url);
header!{ (XApiKey, "X-Api-Key") => [String] }
req.headers_mut().set(XApiKey(merc.key().to_owned()));
merc.client()
.request(req)
.and_then(|resp| resp.body().map(stream::iter_ok).flatten().collect())
.map_err(Error::from)
.and_then(|body| match serde_json::from_slice(&body)? {
ParserResult::Ok(article) => Ok(article),
ParserResult::Err { msg, msgs } => bail!(msg.unwrap_or(msgs)),
})
});
Response::new(Box::new(f))
}
fn client(&self) -> &Client<Connect> {
&self.0.client
}
}
impl Clone for Mercury {
fn clone(&self) -> Mercury {
Mercury(Rc::clone(&self.0))
}
}
#[must_use = "futures do nothing unless polled"]
pub struct Response(Box<Future<Item = Article, Error = Error>>);
impl Response {
fn new(f: Box<Future<Item = Article, Error = Error>>) -> Response {
Response(f)
}
}
impl Future for Response {
type Item = Article;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
self.0.poll()
}
}
#[derive(Debug)]
struct Inner {
client: Client<Connect>,
key: String,
}
impl Inner {
fn new(handle: &Handle, key: String) -> Result<Inner, Error> {
let conn = Connect::new(num_cpus::get(), handle)?;
let client = Client::configure().connector(conn).build(handle);
Ok(Inner { client, key })
}
}
#[cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))]
#[derive(Deserialize)]
#[serde(untagged)]
enum ParserResult {
Ok(Article),
Err {
#[serde(rename = "message")] msg: Option<String>,
#[serde(default, rename = "messages")] msgs: String,
},
}
fn build_url(resource: &str) -> Result<Uri, Error> {
let mut raw = String::with_capacity(ENDPOINT.len() + resource.len() + 5);
raw.push_str(ENDPOINT);
raw.push_str("?url=");
raw.push_str(resource);
Ok(raw.parse()?)
}