1extern crate curl;
2extern crate failure;
3#[macro_use]
4extern crate failure_derive;
5extern crate serde;
6extern crate serde_json;
7
8use failure::{Backtrace, Context, Fail, ResultExt};
11use serde::de::DeserializeOwned;
12use serde::Serialize;
13use std::cell::{RefCell, RefMut};
14use std::fmt;
15use std::io::{Read, Write};
16
17pub type Result<T> = std::result::Result<T, failure::Error>;
19
20#[derive(PartialEq, Debug)]
22pub enum Method {
23 Get,
24 Head,
25 Post,
26 Put,
27 Delete,
28}
29
30impl fmt::Display for Method {
31 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32 match *self {
33 Method::Get => write!(f, "GET"),
34 Method::Head => write!(f, "HEAD"),
35 Method::Post => write!(f, "POST"),
36 Method::Put => write!(f, "PUT"),
37 Method::Delete => write!(f, "DELETE"),
38 }
39 }
40}
41
42pub struct Client {
46 shared_handle: RefCell<curl::easy::Easy>,
47 base_url: String,
48 user_agent: String,
49}
50
51impl Client {
52 pub fn new(base_url: &str) -> Client {
54 Client {
55 shared_handle: RefCell::new(curl::easy::Easy::new()),
56 base_url: base_url.to_string(),
57 user_agent: "curl-http".to_string(),
58 }
59 }
60
61 pub fn set_user_agent(&mut self, user_agent: &str) {
63 self.user_agent = user_agent.to_string();
64 }
65
66 pub fn request(&self, method: Method, endpoint: &str) -> Result<Request> {
68 let url = format!("{}{}", self.base_url, endpoint);
69 let mut handle = self.shared_handle.borrow_mut();
70 handle.reset();
71 Request::new(handle, method, &url)?.with_user_agent(&self.user_agent)
72 }
73
74 pub fn get(&self, endpoint: &str) -> Result<Response> {
76 self.request(Method::Get, endpoint)?.send()
77 }
78
79 pub fn post<S: Serialize>(&self, endpoint: &str, body: &S) -> Result<Response> {
81 self.request(Method::Post, endpoint)?
82 .with_json_body(body)?
83 .send()
84 }
85
86 pub fn put<S: Serialize>(&self, endpoint: &str, body: &S) -> Result<Response> {
88 self.request(Method::Put, endpoint)?
89 .with_json_body(body)?
90 .send()
91 }
92
93 pub fn delete(&self, endpoint: &str) -> Result<Response> {
95 self.request(Method::Delete, endpoint)?.send()
96 }
97}
98
99pub struct Request<'a> {
101 handle: RefMut<'a, curl::easy::Easy>,
102 headers: curl::easy::List,
103 url: String,
104 body: Option<Vec<u8>>,
105}
106
107impl<'a> Request<'a> {
108 pub fn new(
109 mut handle: RefMut<'a, curl::easy::Easy>,
110 method: Method,
111 url: &str,
112 ) -> Result<Request<'a>> {
113 match method {
114 Method::Get => handle.get(true)?,
115 Method::Head => {
116 handle.get(true)?;
117 handle.custom_request("HEAD")?;
118 handle.nobody(true)?;
119 }
120 Method::Post => handle.custom_request("POST")?,
121 Method::Put => handle.custom_request("PUT")?,
122 Method::Delete => handle.custom_request("DELETE")?,
123 }
124
125 Ok(Request {
126 handle,
127 headers: curl::easy::List::new(),
128 url: url.to_string(),
129 body: None,
130 })
131 }
132
133 pub fn with_header(mut self, key: &str, value: &str) -> Result<Request<'a>> {
135 self.headers.append(&format!("{}: {}", key, value))?;
136 Ok(self)
137 }
138
139 pub fn with_user_agent(mut self, ua: &str) -> Result<Request<'a>> {
141 self.headers.append(&format!("User-Agent: {}", ua))?;
142 Ok(self)
143 }
144
145 pub fn with_arguments(mut self, args: &str) -> Result<Request<'a>> {
147 self.url = format!("{}?{}", self.url, args);
148 Ok(self)
149 }
150
151 pub fn with_json_body<S: Serialize>(mut self, body: &S) -> Result<Request<'a>> {
153 let mut body_bytes: Vec<u8> = vec![];
154 serde_json::to_writer(&mut body_bytes, &body)
156 .context(ErrorKind::InvalidJsonBody)?;
157
158 self.body = Some(body_bytes);
159 self.headers.append("Content-Type: application/json")?;
160 Ok(self)
161 }
162
163 pub fn send(mut self) -> Result<Response> {
165 self.handle.http_headers(self.headers)?;
166 self.handle.url(&self.url)?;
167
168 match self.body {
169 Some(ref body) => {
170 let mut body: &[u8] = &body[..];
171 self.handle.upload(true)?;
172 self.handle.in_filesize(body.len() as u64)?;
173 handle_request(&mut self.handle, &mut |buffer| {
174 body.read(buffer).unwrap_or(0)
175 })
176 }
177 None => handle_request(&mut self.handle, &mut |_| 0)
178 }
179 }
180}
181
182fn handle_request(
183 handle: &mut curl::easy::Easy,
184 read: &mut FnMut(&mut [u8]) -> usize) -> Result<Response> {
185 let mut response_body = vec![];
186 let mut response_headers = vec![];
187
188 {
189 let mut handle = handle.transfer();
190
191 handle.read_function(move |buffer| Ok(read(buffer)))?;
192
193 handle.write_function(|data| {
194 Ok(match response_body.write_all(data) {
195 Ok(_) => data.len(),
196 Err(_) => 0,
197 })
198 })?;
199
200 handle.header_function(|data| {
201 response_headers.push(String::from_utf8_lossy(data).into_owned());
202 true
203 })?;
204 handle.perform()?;
205 }
206
207 Ok(Response {
208 status: handle.response_code()?,
209 headers: response_headers,
210 body: Some(response_body),
211 })
212}
213
214pub type HttpStatus = u32;
216
217#[derive(Clone, Debug)]
219pub struct Response {
220 status: HttpStatus,
221 headers: Vec<String>,
222 body: Option<Vec<u8>>,
223}
224
225impl Response {
226 pub fn status(&self) -> HttpStatus {
227 self.status
228 }
229
230 pub fn failed(&self) -> bool {
231 self.status >= 400 && self.status <= 600
232 }
233
234 pub fn ok(&self) -> bool {
235 !self.failed()
236 }
237
238 pub fn deserialize<T: DeserializeOwned>(&self) -> Result<T> {
240 if self.ok() {
241 Ok(serde_json::from_reader(match self.body {
242 Some(ref body) => body,
243 None => &b""[..],
244 }).context(ErrorKind::InvalidJson)?)
245 } else {
246 Err(ErrorKind::RequestFailed.into())
247 }
248 }
249}
250
251#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
252pub enum ErrorKind {
253 #[fail(display = "Request failed")]
254 RequestFailed,
255 #[fail(display = "Could not serialize value as JSON")]
256 InvalidJsonBody,
257 #[fail(display = "Could not parse JSON response")]
258 InvalidJson,
259}
260
261#[derive(Debug)]
263pub struct Error {
264 inner: Context<ErrorKind>,
265}
266
267impl self::Error {
268 pub fn kind(&self) -> ErrorKind {
269 *self.inner.get_context()
270 }
271}
272
273impl Fail for self::Error {
274 fn cause(&self) -> Option<&Fail> {
275 self.inner.cause()
276 }
277
278 fn backtrace(&self) -> Option<&Backtrace> {
279 self.inner.backtrace()
280 }
281}
282
283impl fmt::Display for self::Error {
284 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
285 fmt::Display::fmt(&self.inner, f)
286 }
287}
288
289impl From<ErrorKind> for self::Error {
290 fn from(kind: ErrorKind) -> self::Error {
291 self::Error { inner: Context::new(kind) }
292 }
293}
294
295impl From<Context<ErrorKind>> for self::Error {
296 fn from(inner: Context<ErrorKind>) -> self::Error {
297 self::Error { inner }
298 }
299}
300
301impl From<curl::Error> for self::Error {
302 fn from(error: curl::Error) -> self::Error {
303 failure::Error::from(error).context(ErrorKind::RequestFailed).into()
304 }
305}