Skip to content

Commit ebb2eb6

Browse files
committed
Sync implementation
0 parents  commit ebb2eb6

5 files changed

Lines changed: 182 additions & 0 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/target
2+
**/*.rs.bk
3+
Cargo.lock

Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "http_client"
3+
version = "0.1.0"
4+
authors = ["Vladimir Burdukov <chippcheg@gmail.com>"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
futures = "0.3"
11+
12+
serde = "1.0"
13+
serde_json = "1.0"
14+
serde_derive = "1.0"
15+
16+
curl = "0.4.20"
17+
url = "1.7.2"

src/lib.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use curl::easy::Easy;
2+
use serde::de::DeserializeOwned;
3+
use serde_json;
4+
use std::borrow::Borrow;
5+
use url::{ParseError, Url};
6+
7+
#[derive(Debug)]
8+
pub enum Error {
9+
HttpError(u32),
10+
CurlError(curl::Error),
11+
ParseError(serde_json::Error),
12+
}
13+
14+
impl From<curl::Error> for Error {
15+
fn from(error: curl::Error) -> Error {
16+
Error::CurlError(error)
17+
}
18+
}
19+
20+
impl From<serde_json::Error> for Error {
21+
fn from(error: serde_json::Error) -> Error {
22+
Error::ParseError(error)
23+
}
24+
}
25+
26+
pub struct HttpClient {
27+
base_url: Url,
28+
}
29+
30+
impl HttpClient {
31+
// Public API
32+
33+
pub fn new(base_url: &str) -> Result<HttpClient, ParseError> {
34+
let base_url = Url::parse(base_url)?;
35+
Ok(HttpClient { base_url })
36+
}
37+
38+
pub fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T, Error> {
39+
self.do_get(self.prepare_url_with_path(path))
40+
}
41+
42+
pub fn get_with_params<T, I, K, V>(&self, path: &str, iter: I) -> Result<T, Error>
43+
where
44+
T: DeserializeOwned,
45+
I: IntoIterator,
46+
I::Item: Borrow<(K, V)>,
47+
K: AsRef<str>,
48+
V: AsRef<str>,
49+
{
50+
let mut url = self.prepare_url_with_path(path);
51+
url.query_pairs_mut().extend_pairs(iter);
52+
self.do_get(url)
53+
}
54+
55+
// Private API
56+
57+
fn prepare_url_with_path(&self, path: &str) -> Url {
58+
let mut url = self.base_url.clone();
59+
url.set_path(path);
60+
url
61+
}
62+
63+
fn do_get<T: DeserializeOwned>(&self, url: Url) -> Result<T, Error> {
64+
let mut response = Vec::new();
65+
let mut easy = Easy::new();
66+
easy.url(url.as_str()).unwrap();
67+
68+
{
69+
let mut transfer = easy.transfer();
70+
transfer
71+
.write_function(|data| {
72+
response.extend_from_slice(data);
73+
Ok(data.len())
74+
})
75+
.unwrap();
76+
transfer.perform().unwrap();
77+
}
78+
79+
let code = easy.response_code().unwrap();
80+
81+
if code >= 200 && code < 300 {
82+
let response: T = serde_json::from_slice(&response)
83+
.map_err(|err| Error::from(err))
84+
.unwrap();
85+
86+
Ok(response)
87+
} else {
88+
eprintln!("{}", String::from_utf8_lossy(&response));
89+
Err(Error::HttpError(code))
90+
}
91+
}
92+
}

tests/get.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use http_client::HttpClient;
2+
use serde_derive::Deserialize;
3+
4+
#[test]
5+
fn test_get() {
6+
#[derive(Deserialize)]
7+
struct Response {
8+
url: String,
9+
}
10+
11+
let http_client = HttpClient::new("https://httpbin.org/").unwrap();
12+
13+
assert_eq!(
14+
http_client.get::<Response>("/get").unwrap().url,
15+
"https://httpbin.org/get"
16+
);
17+
}
18+
19+
#[test]
20+
fn test_get_with_params() {
21+
use std::collections::HashMap;
22+
23+
#[derive(Deserialize)]
24+
struct Response {
25+
url: String,
26+
args: HashMap<String, String>,
27+
}
28+
29+
let http_client = HttpClient::new("https://httpbin.org/").unwrap();
30+
31+
let params = vec![("key1", "value1"), ("key2", "value2")];
32+
let response = http_client
33+
.get_with_params::<Response, _, _, _>("/get", params)
34+
.unwrap();
35+
36+
assert_eq!(
37+
response.url,
38+
"https://httpbin.org/get?key1=value1&key2=value2"
39+
);
40+
assert_eq!(
41+
response.args,
42+
[
43+
("key1".to_owned(), "value1".to_owned()),
44+
("key2".to_owned(), "value2".to_owned())
45+
]
46+
.iter()
47+
.cloned()
48+
.collect()
49+
)
50+
}

tests/http_error.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use http_client::{Error, HttpClient};
2+
use serde_derive::Deserialize;
3+
4+
#[test]
5+
fn test_404() {
6+
#[derive(Debug, Deserialize)]
7+
struct Response;
8+
9+
let http_client = HttpClient::new("https://httpbin.org/").unwrap();
10+
11+
match http_client.get::<Response>("/status/404").unwrap_err() {
12+
Error::HttpError(404) => (),
13+
error => panic!(
14+
r#"assertion failed:
15+
expected: `Error::HttpError(404)`
16+
got: `{:?}`"#,
17+
error
18+
),
19+
}
20+
}

0 commit comments

Comments
 (0)