Skip to content

Commit

Permalink
Futures implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
chipp committed Nov 26, 2019
1 parent ebb2eb6 commit dd9f09d
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 43 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "http_client"
version = "0.1.0"
version = "0.2.0"
authors = ["Vladimir Burdukov <[email protected]>"]
edition = "2018"

Expand Down
83 changes: 46 additions & 37 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
use curl::easy::Easy;
use std::borrow::Borrow;
use std::thread;

use futures::channel::oneshot;

use serde::de::DeserializeOwned;
use serde_json;
use std::borrow::Borrow;

use curl::easy::Easy;
use url::{ParseError, Url};

#[derive(Debug)]
Expand All @@ -28,28 +33,26 @@ pub struct HttpClient {
}

impl HttpClient {
// Public API

pub fn new(base_url: &str) -> Result<HttpClient, ParseError> {
let base_url = Url::parse(base_url)?;
Ok(HttpClient { base_url })
}

pub fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T, Error> {
self.do_get(self.prepare_url_with_path(path))
pub async fn get<T: DeserializeOwned + Send + 'static>(&self, path: &str) -> Result<T, Error> {
self.do_get(self.prepare_url_with_path(path)).await
}

pub fn get_with_params<T, I, K, V>(&self, path: &str, iter: I) -> Result<T, Error>
pub async fn get_with_params<T, I, K, V>(&self, path: &str, iter: I) -> Result<T, Error>
where
T: DeserializeOwned,
T: DeserializeOwned + Send + 'static,
I: IntoIterator,
I::Item: Borrow<(K, V)>,
K: AsRef<str>,
V: AsRef<str>,
{
let mut url = self.prepare_url_with_path(path);
url.query_pairs_mut().extend_pairs(iter);
self.do_get(url)
self.do_get(url).await
}

// Private API
Expand All @@ -60,33 +63,39 @@ impl HttpClient {
url
}

fn do_get<T: DeserializeOwned>(&self, url: Url) -> Result<T, Error> {
let mut response = Vec::new();
let mut easy = Easy::new();
easy.url(url.as_str()).unwrap();

{
let mut transfer = easy.transfer();
transfer
.write_function(|data| {
response.extend_from_slice(data);
Ok(data.len())
})
.unwrap();
transfer.perform().unwrap();
}

let code = easy.response_code().unwrap();

if code >= 200 && code < 300 {
let response: T = serde_json::from_slice(&response)
.map_err(|err| Error::from(err))
.unwrap();

Ok(response)
} else {
eprintln!("{}", String::from_utf8_lossy(&response));
Err(Error::HttpError(code))
}
async fn do_get<T: DeserializeOwned + Send + 'static>(&self, url: Url) -> Result<T, Error> {
let (tx, rx) = oneshot::channel::<Result<T, Error>>();

thread::spawn(move || {
let mut response = Vec::new();
let mut easy = Easy::new();
easy.url(url.as_str()).unwrap();

{
let mut transfer = easy.transfer();
transfer
.write_function(|data| {
response.extend_from_slice(data);
Ok(data.len())
})
.unwrap();
transfer.perform().unwrap();
}

let code = easy.response_code().unwrap();

if code >= 200 && code < 300 {
let response: T = serde_json::from_slice(&response)
.map_err(|err| Error::from(err))
.unwrap();

let _ = tx.send(Ok(response));
} else {
eprintln!("{}", String::from_utf8_lossy(&response));
let _ = tx.send(Err(Error::HttpError(code)));
}
});

rx.await.unwrap()
}
}
25 changes: 21 additions & 4 deletions tests/get.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use futures::executor::block_on;
use http_client::HttpClient;
use serde_derive::Deserialize;

Expand All @@ -11,11 +12,28 @@ fn test_get() {
let http_client = HttpClient::new("https://httpbin.org/").unwrap();

assert_eq!(
http_client.get::<Response>("/get").unwrap().url,
block_on(http_client.get::<Response>("/get")).unwrap().url,
"https://httpbin.org/get"
);
}

#[test]
fn test_get_join() {
#[derive(Deserialize)]
struct Response {
url: String,
}

let http_client = HttpClient::new("https://httpbin.org/").unwrap();
let results = block_on(futures::future::join(
http_client.get::<Response>("/delay/2"),
http_client.get::<Response>("/delay/1"),
));

assert_eq!(results.0.unwrap().url, "https://httpbin.org/delay/2");
assert_eq!(results.1.unwrap().url, "https://httpbin.org/delay/1");
}

#[test]
fn test_get_with_params() {
use std::collections::HashMap;
Expand All @@ -29,9 +47,8 @@ fn test_get_with_params() {
let http_client = HttpClient::new("https://httpbin.org/").unwrap();

let params = vec![("key1", "value1"), ("key2", "value2")];
let response = http_client
.get_with_params::<Response, _, _, _>("/get", params)
.unwrap();
let response =
block_on(http_client.get_with_params::<Response, _, _, _>("/get", params)).unwrap();

assert_eq!(
response.url,
Expand Down
3 changes: 2 additions & 1 deletion tests/http_error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use futures::executor::block_on;
use http_client::{Error, HttpClient};
use serde_derive::Deserialize;

Expand All @@ -8,7 +9,7 @@ fn test_404() {

let http_client = HttpClient::new("https://httpbin.org/").unwrap();

match http_client.get::<Response>("/status/404").unwrap_err() {
match block_on(http_client.get::<Response>("/status/404")).unwrap_err() {
Error::HttpError(404) => (),
error => panic!(
r#"assertion failed:
Expand Down

0 comments on commit dd9f09d

Please sign in to comment.