Skip to content

Commit

Permalink
Merge pull request #23 from indiv0/dev
Browse files Browse the repository at this point in the history
Implement Async API
  • Loading branch information
indiv0 authored Nov 21, 2017
2 parents bfaa16b + 0476562 commit eafd37a
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 49 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ env:
global:
- RUST_BACKTRACE=1
# override the default '--features unstable' used for the nightly branch
- TRAVIS_CARGO_NIGHTLY_FEATURE="nightly-testing"
- TRAVIS_CARGO_NIGHTLY_FEATURE="nightly"
# encrypted Github token for doc upload
- secure: DHSbYBAuYcv4bZUF5Rnf85dvHQ1tlpphj/TBfwr4EL/FcZufjllo2G8MwBnofeh1aB5rH2ZtLLSJx/2MPh7ShKM36NhtYZvbpjWyuocazce5VyzdjaZtXQ25F8CNUzG48iItFHLgLWCGff047SJwplVIXytkG8bDwN6jp3BHWTmkXtvCJH3WS34ZuVZovagwuv9iEBr+QOOxyX5qGzWIhtjTVSd4aA0g04gfdSqbCYbhSWF12LiFEP/dNYEmVm7wIMm59kfrh1hGUPJ27tiIwXww3lmLXrY3y4vqEKYE+Zmu+VVAb0gDDnZ5REBHlAnPk1R8gQXgTULkkKNAm45q2aKU2/glRl3dKq1yG1XB3/OdbwlJXX5wKOS58/kqsSaO4Jw5/8dryvlM5pympqRpAlvtTEno+c3d25Km1GxozxJ4C2xNZniduccaO3BTLnIZPs9fTKlZb3Q1+cr+vfOpyf2Gvhvbc9q0k4l0uYfsET/o/tD8yAScymAAm49u4WDQjqldUAUk1dtPC+FssN3gwURSibWP8K5LAkCjBc1xlxdRXz18h8Qg8bgANm/yifCzPm8YGeDBZLD8He/36l4T+xdua/zIWSRlE8W2nhuaT5u+uuLCZj5eygmggGbnCwTXCeGPjZVcQ4C8RfDIKJ/oltIs9lzX1XUqcpN4IBbhg+c=
branches:
Expand Down
14 changes: 8 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,28 +30,30 @@ optional = true
version = "0.8.23"

[dependencies]
futures = "0.1.17"
hyper = "0.11.7"
log = "0.3.6"
serde = "0.8.23"
serde_xml = "0.8.1"
tokio-core = "0.1.10"

[dependencies.clippy]
optional = true
version = "0.0"

[dependencies.hyper]
optional = true
version = "0.10.4"

[dependencies.serde_derive]
optional = true
version = "0.8.23"

[dependencies.url]
features = ["serde"]
version = "1.4.0"
version = "1.6.0"

[dev-dependencies]
hyper-tls = "0.1.2"

[features]
default = ["hyper", "with-syntex"]
default = ["with-syntex"]
nightly = ["serde_derive"]
nightly-testing = ["clippy", "nightly"]
with-syntex = ["serde_codegen"]
40 changes: 40 additions & 0 deletions examples/simple.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) 2017 Nikita Pekin and the wolfram_alpha_rs contributors
// See the README.md file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

extern crate futures;
extern crate hyper;
extern crate hyper_tls;
extern crate tokio_core;
extern crate wolfram_alpha;

use futures::future::Future;
use hyper::Client;
use hyper_tls::HttpsConnector;
use tokio_core::reactor::Core;
use wolfram_alpha::query::query;

const APP_ID: &str = "YOUR_APP_ID";

fn main() {
let mut core = Core::new().unwrap();
let client = Client::configure()
.connector(HttpsConnector::new(4, &core.handle()).unwrap())
.build(&core.handle());

let input = "distance from the Sun to the Earth";
let work = query(&client, APP_ID, input, None)
.and_then(|res| {
let subpod = &res.pod.unwrap()[1].subpod[0];
let plaintext = subpod.plaintext.as_ref().unwrap();
println!("Distance from the Sun to the Earth: {:?}", plaintext);
Ok(())
});

core.run(work).unwrap();
}
27 changes: 24 additions & 3 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use hyper::error::UriError;
use serde_xml;
use std::error::Error as StdError;
use std::fmt;
use std::io;
use std::result::Result as StdResult;
use std::str::Utf8Error;

/// A convenient alias type for results for `wolfram_alpha`.
pub type Result<T> = StdResult<T, Error>;
Expand Down Expand Up @@ -87,12 +89,13 @@ impl PartialEq<Error> for Error {
}
}

/// A convenient alias type for results of HTTP requests.
pub type HttpRequestResult<T> = StdResult<T, HttpRequestError>;

/// Represents errors which occur when sending an HTTP request to Wolfram|Alpha.
#[derive(Debug)]
pub enum HttpRequestError {
/// Error parsing a URL string into a `hyper::Uri`.
UriError(UriError),
/// Error parsing a bytes into a UTF-8 string.
Utf8Error(Utf8Error),
/// An error occuring during network IO operations.
Io(io::Error),
/// Any other error occuring during an HTTP request.
Expand All @@ -103,6 +106,8 @@ impl fmt::Display for HttpRequestError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
HttpRequestError::Io(ref e) => e.fmt(f),
HttpRequestError::UriError(ref e) => e.fmt(f),
HttpRequestError::Utf8Error(ref e) => e.fmt(f),
HttpRequestError::Other(ref e) => e.fmt(f),
}
}
Expand All @@ -112,18 +117,34 @@ impl StdError for HttpRequestError {
fn description(&self) -> &str {
match *self {
HttpRequestError::Io(ref e) => e.description(),
HttpRequestError::UriError(ref e) => e.description(),
HttpRequestError::Utf8Error(ref e) => e.description(),
HttpRequestError::Other(ref e) => e.description(),
}
}

fn cause(&self) -> Option<&StdError> {
match *self {
HttpRequestError::Io(ref e) => e.cause(),
HttpRequestError::UriError(ref e) => e.cause(),
HttpRequestError::Utf8Error(ref e) => e.cause(),
HttpRequestError::Other(ref e) => e.cause(),
}
}
}

impl From<UriError> for HttpRequestError {
fn from(error: UriError) -> HttpRequestError {
HttpRequestError::UriError(error)
}
}

impl From<Utf8Error> for HttpRequestError {
fn from(error: Utf8Error) -> HttpRequestError {
HttpRequestError::Utf8Error(error)
}
}

impl From<io::Error> for HttpRequestError {
fn from(error: io::Error) -> HttpRequestError {
HttpRequestError::Io(error)
Expand Down
73 changes: 43 additions & 30 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![deny(missing_docs, non_camel_case_types, warnings)]
#![deny(missing_docs, non_camel_case_types)]
#![cfg_attr(feature = "clippy", feature(plugin))]
#![cfg_attr(feature = "clippy", plugin(clippy))]
#![cfg_attr(feature = "nightly", feature(custom_derive, proc_macro))]
Expand All @@ -18,34 +18,30 @@
//! implemented by various request senders. These implementations may then be
//! used to execute requests to the API.
//!
//! If the `hyper` feature is enabled during compilation, then this library
//! provides an implementation of the `WolframAlphaRequestSender` trait for
//! the `hyper::Client` of the [`hyper`](https://github.com/hyperium/hyper)
//! library.
//!
//! Response bodies are deserialized from XML into structs via the
//! [`serde_xml`](https://github.com/serde-rs/xml) library.
#[cfg(feature = "hyper")]
extern crate futures;
extern crate hyper;

#[macro_use]
extern crate log;
extern crate serde;
#[cfg(feature = "nightly")]
#[macro_use]
extern crate serde_derive;
extern crate serde_xml;
extern crate tokio_core;
extern crate url;

mod error;

pub use self::error::{Error, HttpRequestError, HttpRequestResult, Result};
pub use self::error::{Error, HttpRequestError, Result};

pub mod model;
pub mod query;
// TODO: implement the `validate_query` function.

use futures::Future;
use serde::Deserialize;
use std::collections::HashMap;
use std::fmt::Debug;
Expand All @@ -66,44 +62,62 @@ pub trait WolframAlphaRequestSender {
///
/// Takes a map of parameters which get appended to the request as query
/// parameters. Returns the response body string.
fn send<'a>(&self, method: &str, params: &mut HashMap<&str, &'a str>)
-> HttpRequestResult<String>;
fn send<'a>(&'a self, method: &str, params: HashMap<&'a str, &'a str>)
-> Box<'a + Future<Item = String, Error = HttpRequestError>>;

/// Make an API call to Wolfram|Alpha that contains the configured App ID.
///
/// Takes a map of parameters which get appended to the request as query
/// parameters. Returns the response body string.
fn send_authed<'a>(&self, method: &str, app_id: &'a str, params: &mut HashMap<&str, &'a str>)
-> HttpRequestResult<String> {
fn send_authed<'a>(&'a self, method: &str, app_id: &'a str, mut params: HashMap<&'a str, &'a str>)
-> Box<'a + Future<Item = String, Error = HttpRequestError>>
{
params.insert("appid", app_id);
self.send(method, params)
}
}

#[cfg(feature = "hyper")]
mod hyper_support {
use error::{HttpRequestError, HttpRequestResult};
use hyper;
use error::HttpRequestError;
use futures::{self, Future, Stream};
use hyper::{self, Uri};
use hyper::client::Connect;
use std::collections::HashMap;
use std::io::Read;
use std::str::{self, FromStr};
use super::WolframAlphaRequestSender;
use url::Url;

impl WolframAlphaRequestSender for hyper::Client {
fn send<'a>(&self, method: &str, params: &mut HashMap<&str, &'a str>)
-> HttpRequestResult<String> {
impl<C> WolframAlphaRequestSender for hyper::Client<C>
where C: Connect,
{
fn send<'a>(&'a self, method: &str, mut params: HashMap<&'a str, &'a str>)
-> Box<'a + Future<Item = String, Error = HttpRequestError>>
{
let url_string = format!("https://api.wolframalpha.com/v2/{}", method);
let mut url = url_string.parse::<Url>().expect("Unable to parse URL");

url.query_pairs_mut().extend_pairs(params.into_iter());

trace!("Sending query \"{:?}\" to url: {}", params, url);
let mut response = self.get(url).send()?;
let mut result = String::new();
response.read_to_string(&mut result)?;
trace!("Query result: {}", result);

Ok(result)
url.query_pairs_mut().extend_pairs((&mut params).into_iter());

let uri = Uri::from_str(url.as_ref()).map_err(From::from);
let res = futures::done(uri)
.and_then(move |uri| {
trace!("Sending query \"{:?}\" to URL: {}", params, url.as_ref());
self.get(uri)
})
.and_then(|res| {
trace!("Response: {}", res.status());

res.body().concat2()
})
.map_err(From::from)
.map(|body| {
str::from_utf8(&body)
.map_err(From::from)
.map(|string| string.to_string())
})
.and_then(|string| string);

Box::new(res)
}
}

Expand All @@ -117,5 +131,4 @@ mod hyper_support {
}
}

#[cfg(feature = "hyper")]
pub use hyper_support::*;
4 changes: 2 additions & 2 deletions src/model.in.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ impl Deserialize for States {
if state.is_none() {
state = Some(more_state);
} else {
if let Some(mut state) = state.as_mut() {
if let Some(state) = state.as_mut() {
state.append(&mut more_state);
}
}
Expand All @@ -595,7 +595,7 @@ impl Deserialize for States {
if statelist.is_none() {
statelist = Some(Some(more_statelist));
} else {
if let Some(mut statelist) = statelist.as_mut() {
if let Some(statelist) = statelist.as_mut() {
if let Some(statelist) = statelist.as_mut() {
statelist.append(&mut more_statelist);
}
Expand Down
20 changes: 13 additions & 7 deletions src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
//! For more information, see [Wolfram|Alpha's API
//! documentation](http://products.wolframalpha.com/api/documentation.html).
use error::Result;
use error::Error;
use futures::Future;
use model::QueryResult;
use std::collections::HashMap;
use super::{WolframAlphaRequestSender, parse_wolfram_alpha_response};
Expand Down Expand Up @@ -49,10 +50,10 @@ pub struct OptionalQueryParameters<'a> {
}

/// Performs a query to the Wolfram|Alpha API.
pub fn query<R>(
client: &R, appid: &str, input: &str,
optional_query_parameters: Option<OptionalQueryParameters>
) -> Result<QueryResult>
pub fn query<'a, R>(
client: &'a R, appid: &'a str, input: &'a str,
optional_query_parameters: Option<OptionalQueryParameters<'a>>
) -> Box<'a + Future<Item = QueryResult, Error = Error>>
where R: WolframAlphaRequestSender,
{
let mut params = HashMap::new();
Expand Down Expand Up @@ -90,6 +91,11 @@ pub fn query<R>(
}
}

let response = client.send_authed("query", appid, &mut params)?;
parse_wolfram_alpha_response(&response)
let res = client.send_authed("query", appid, params)
.map_err(From::from)
.and_then(|res| {
parse_wolfram_alpha_response(&res)
});

Box::new(res)
}

0 comments on commit eafd37a

Please sign in to comment.