Skip to content

Commit

Permalink
Sync implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
chipp committed Nov 24, 2019
0 parents commit ebb2eb6
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock
17 changes: 17 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "http_client"
version = "0.1.0"
authors = ["Vladimir Burdukov <[email protected]>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
futures = "0.3"

serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"

curl = "0.4.20"
url = "1.7.2"
92 changes: 92 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use curl::easy::Easy;
use serde::de::DeserializeOwned;
use serde_json;
use std::borrow::Borrow;
use url::{ParseError, Url};

#[derive(Debug)]
pub enum Error {
HttpError(u32),
CurlError(curl::Error),
ParseError(serde_json::Error),
}

impl From<curl::Error> for Error {
fn from(error: curl::Error) -> Error {
Error::CurlError(error)
}
}

impl From<serde_json::Error> for Error {
fn from(error: serde_json::Error) -> Error {
Error::ParseError(error)
}
}

pub struct HttpClient {
base_url: Url,
}

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 fn get_with_params<T, I, K, V>(&self, path: &str, iter: I) -> Result<T, Error>
where
T: DeserializeOwned,
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)
}

// Private API

fn prepare_url_with_path(&self, path: &str) -> Url {
let mut url = self.base_url.clone();
url.set_path(path);
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))
}
}
}
50 changes: 50 additions & 0 deletions tests/get.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use http_client::HttpClient;
use serde_derive::Deserialize;

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

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

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

#[test]
fn test_get_with_params() {
use std::collections::HashMap;

#[derive(Deserialize)]
struct Response {
url: String,
args: HashMap<String, String>,
}

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();

assert_eq!(
response.url,
"https://httpbin.org/get?key1=value1&key2=value2"
);
assert_eq!(
response.args,
[
("key1".to_owned(), "value1".to_owned()),
("key2".to_owned(), "value2".to_owned())
]
.iter()
.cloned()
.collect()
)
}
20 changes: 20 additions & 0 deletions tests/http_error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use http_client::{Error, HttpClient};
use serde_derive::Deserialize;

#[test]
fn test_404() {
#[derive(Debug, Deserialize)]
struct Response;

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

match http_client.get::<Response>("/status/404").unwrap_err() {
Error::HttpError(404) => (),
error => panic!(
r#"assertion failed:
expected: `Error::HttpError(404)`
got: `{:?}`"#,
error
),
}
}

0 comments on commit ebb2eb6

Please sign in to comment.