diff --git a/api/src/client.rs b/api/src/client.rs index 6a49b6de..4ae5d58f 100644 --- a/api/src/client.rs +++ b/api/src/client.rs @@ -23,10 +23,12 @@ use serde::{Deserialize, Serialize}; use serde_json; use futures::future::{err, ok, Either}; -use tokio_core::reactor::Core; +use tokio::runtime::Runtime; use rest::{Error, ErrorKind}; +pub type ClientResponseFuture = Box + Send>; + /// Helper function to easily issue a HTTP GET request against a given URL that /// returns a JSON object. Handles request building, JSON deserialization and /// response code checking. @@ -34,19 +36,20 @@ pub fn get<'a, T>(url: &'a str) -> Result where for<'de> T: Deserialize<'de>, { - let uri = url.parse::().map_err::(|e: InvalidUri| { - e.context(ErrorKind::Argument(format!("Invalid url {}", url))) - .into() - })?; - let req = Request::builder() - .method("GET") - .uri(uri) - .header(USER_AGENT, "grin-client") - .header(ACCEPT, "application/json") - .body(Body::empty()) - .map_err(|_e| ErrorKind::RequestError("Bad request".to_owned()))?; + handle_request(build_request(url, "GET", None)?) +} - handle_request(req) +/// Helper function to easily issue an async HTTP GET request against a given +/// URL that returns a future. Handles request building, JSON deserialization +/// and response code checking. +pub fn get_async<'a, T>(url: &'a str) -> ClientResponseFuture +where + for<'de> T: Deserialize<'de> + Send + 'static, +{ + match build_request(url, "GET", None) { + Ok(req) => Box::new(handle_request_async(req)), + Err(e) => Box::new(err(e)), + } } /// Helper function to easily issue a HTTP POST request with the provided JSON @@ -62,6 +65,22 @@ where handle_request(req) } +/// Helper function to easily issue an async HTTP POST request with the +/// provided JSON object as body on a given URL that returns a future. Handles +/// request building, JSON serialization and deserialization, and response code +/// checking. +pub fn post_async(url: &str, input: &IN) -> ClientResponseFuture +where + IN: Serialize, + OUT: Send + 'static, + for<'de> OUT: Deserialize<'de>, +{ + match create_post_request(url, input) { + Ok(req) => Box::new(handle_request_async(req)), + Err(e) => Box::new(err(e)), + } +} + /// Helper function to easily issue a HTTP POST request with the provided JSON /// object as body on a given URL that returns nothing. Handles request /// building, JSON serialization, and response code @@ -75,6 +94,43 @@ where Ok(()) } +/// Helper function to easily issue an async HTTP POST request with the +/// provided JSON object as body on a given URL that returns a future. Handles +/// request building, JSON serialization and deserialization, and response code +/// checking. +pub fn post_no_ret_async(url: &str, input: &IN) -> ClientResponseFuture<()> +where + IN: Serialize, +{ + match create_post_request(url, input) { + Ok(req) => Box::new(send_request_async(req).and_then(|_| ok(()))), + Err(e) => Box::new(err(e)), + } +} + +fn build_request<'a>( + url: &'a str, + method: &str, + body: Option, +) -> Result, Error> { + let uri = url.parse::().map_err::(|e: InvalidUri| { + e.context(ErrorKind::Argument(format!("Invalid url {}", url))) + .into() + })?; + Request::builder() + .method(method) + .uri(uri) + .header(USER_AGENT, "grin-client") + .header(ACCEPT, "application/json") + .body(match body { + None => Body::empty(), + Some(json) => json.into(), + }) + .map_err(|e| { + ErrorKind::RequestError(format!("Bad request {} {}: {}", method, url, e)).into() + }) +} + fn create_post_request(url: &str, input: &IN) -> Result, Error> where IN: Serialize, @@ -82,20 +138,7 @@ where let json = serde_json::to_string(input).context(ErrorKind::Internal( "Could not serialize data to JSON".to_owned(), ))?; - let uri = url.parse::().map_err::(|e: InvalidUri| { - e.context(ErrorKind::Argument(format!("Invalid url {}", url))) - .into() - })?; - Request::builder() - .method("POST") - .uri(uri) - .header(USER_AGENT, "grin-client") - .header(ACCEPT, "application/json") - .body(json.into()) - .map_err::(|e| { - e.context(ErrorKind::RequestError("Bad request".to_owned())) - .into() - }) + build_request(url, "POST", Some(json)) } fn handle_request(req: Request) -> Result @@ -109,29 +152,46 @@ where }) } -fn send_request(req: Request) -> Result { - let mut event_loop = Core::new().unwrap(); - let client = Client::new(); - - let task = client - .request(req) - .map_err(|_e| ErrorKind::RequestError("Cannot make request".to_owned())) - .and_then(|resp| { - if !resp.status().is_success() { - Either::A(err(ErrorKind::RequestError( - "Wrong response code".to_owned(), - ))) - } else { - Either::B( - resp.into_body() - .map_err(|_e| { - ErrorKind::RequestError("Cannot read response body".to_owned()) - }) - .concat2() - .and_then(|ch| ok(String::from_utf8_lossy(&ch.to_vec()).to_string())), - ) - } - }); - - Ok(event_loop.run(task)?) +fn handle_request_async(req: Request) -> ClientResponseFuture +where + for<'de> T: Deserialize<'de> + Send + 'static, +{ + Box::new(send_request_async(req).and_then(|data| { + serde_json::from_str(&data).map_err(|e| { + e.context(ErrorKind::ResponseError("Cannot parse response".to_owned())) + .into() + }) + })) +} + +fn send_request_async(req: Request) -> Box + Send> { + let client = Client::new(); + Box::new( + client + .request(req) + .map_err(|e| ErrorKind::RequestError(format!("Cannot make request: {}", e)).into()) + .and_then(|resp| { + if !resp.status().is_success() { + Either::A(err(ErrorKind::RequestError( + "Wrong response code".to_owned(), + ).into())) + } else { + Either::B( + resp.into_body() + .map_err(|e| { + ErrorKind::RequestError(format!("Cannot read response body: {}", e)) + .into() + }) + .concat2() + .and_then(|ch| ok(String::from_utf8_lossy(&ch.to_vec()).to_string())), + ) + } + }), + ) +} + +fn send_request(req: Request) -> Result { + let task = send_request_async(req); + let mut rt = Runtime::new().unwrap(); + Ok(rt.block_on(task)?) }