extern crate curl;
#[cfg(feature = "json")]
extern crate serde;
#[cfg(feature = "json")]
extern crate serde_json;
pub use curl::init;
use curl::easy::{Auth, Easy, Form, List, ReadError, Transfer, WriteError};
use std::collections::HashMap;
use std::io::Write;
use std::path::Path;
use std::string::{FromUtf8Error, ToString};
use std::time::Duration;
#[cfg(feature = "json")]
use serde::de::DeserializeOwned;
#[cfg(feature = "json")]
use serde::Serialize;
#[cfg(test)]
mod tests;
enum Method {
Get,
Post,
Put,
Patch,
Delete,
}
pub enum CertType {
P12,
PEM,
DER,
}
pub struct Request<'a> {
url: &'a str,
method: Method,
handle: Easy,
headers: List,
body: Body<'a>,
}
enum Body<'a> {
NoBody,
Form(Form),
Bytes {
content_type: &'a str,
data: &'a [u8],
},
#[cfg(feature = "json")]
Json(Vec<u8>),
}
#[derive(Debug, PartialEq)]
pub struct Response<T> {
pub status: u32,
pub headers: HashMap<String, String>,
pub body: T,
}
impl<'a> Request<'a> {
fn new(method: Method, url: &'a str) -> Self {
Request {
url,
method,
handle: Easy::new(),
headers: List::new(),
body: Body::NoBody,
}
}
pub fn get(url: &'a str) -> Self {
Request::new(Method::Get, url)
}
pub fn post(url: &'a str) -> Self {
Request::new(Method::Post, url)
}
pub fn put(url: &'a str) -> Self {
Request::new(Method::Put, url)
}
pub fn patch(url: &'a str) -> Self {
Request::new(Method::Patch, url)
}
pub fn delete(url: &'a str) -> Self {
Request::new(Method::Delete, url)
}
pub fn header(mut self, k: &str, v: &str) -> Result<Self, curl::Error> {
self.headers.append(&format!("{}: {}", k, v))?;
Ok(self)
}
pub fn user_agent(mut self, agent: &str) -> Result<Self, curl::Error> {
self.handle.useragent(agent)?;
Ok(self)
}
pub fn bearer_auth(mut self, token: &str) -> Result<Self, curl::Error> {
self.headers
.append(&format!("Authorization: Bearer {}", token))?;
Ok(self)
}
pub fn basic_auth(mut self, username: &str, password: &str) -> Result<Self, curl::Error> {
let mut auth = Auth::new();
auth.basic(true);
self.handle.username(username)?;
self.handle.password(password)?;
self.handle.http_auth(&auth)?;
Ok(self)
}
pub fn tls_client_cert<P: AsRef<Path>>(
mut self,
cert_type: CertType,
cert: P,
) -> Result<Self, curl::Error> {
self.handle.ssl_cert(cert)?;
self.handle.ssl_cert_type(match cert_type {
CertType::P12 => "P12",
CertType::PEM => "PEM",
CertType::DER => "DER",
})?;
Ok(self)
}
pub fn tls_client_key<P: AsRef<Path>>(mut self, key: P) -> Result<Self, curl::Error> {
self.handle.ssl_key(key)?;
Ok(self)
}
pub fn tls_key_password(mut self, password: &str) -> Result<Self, curl::Error> {
self.handle.key_password(password)?;
Ok(self)
}
pub fn timeout(mut self, timeout: Duration) -> Result<Self, curl::Error> {
self.handle.timeout(timeout)?;
Ok(self)
}
pub fn with_handle<F>(mut self, function: F) -> Result<Self, curl::Error>
where
F: FnOnce(&mut Easy) -> Result<(), curl::Error>,
{
function(&mut self.handle)?;
Ok(self)
}
pub fn body(mut self, content_type: &'a str, data: &'a [u8]) -> Self {
self.body = Body::Bytes { data, content_type };
self
}
pub fn form(mut self, form: Form) -> Self {
self.body = Body::Form(form);
self
}
#[cfg(feature = "json")]
pub fn json<T: Serialize>(mut self, body: &T) -> Result<Self, serde_json::Error> {
let json = serde_json::to_vec(body)?;
self.body = Body::Json(json);
Ok(self)
}
pub fn send(mut self) -> Result<Response<Vec<u8>>, curl::Error> {
self.handle.url(self.url)?;
match self.method {
Method::Get => self.handle.get(true)?,
Method::Post => self.handle.post(true)?,
Method::Put => self.handle.put(true)?,
Method::Patch => self.handle.custom_request("PATCH")?,
Method::Delete => self.handle.custom_request("DELETE")?,
}
let mut headers = HashMap::new();
let mut body = vec![];
if let Body::Form(form) = self.body {
self.handle.httppost(form)?;
self.body = Body::NoBody;
}
match self.body {
Body::Bytes { content_type, data } => {
self.handle.post_field_size(data.len() as u64)?;
self.headers
.append(&format!("Content-Type: {}", content_type))?;
}
#[cfg(feature = "json")]
Body::Json(ref data) => {
self.handle.post_field_size(data.len() as u64)?;
self.headers.append("Content-Type: application/json")?;
}
_ => (),
};
self.handle.http_headers(self.headers)?;
{
let mut transfer = self.handle.transfer();
match self.body {
Body::Bytes { data, .. } => chunked_read_function(&mut transfer, data)?,
#[cfg(feature = "json")]
Body::Json(ref json) => chunked_read_function(&mut transfer, json)?,
_ => (),
};
transfer.header_function(|header| {
let header = String::from_utf8_lossy(header);
let split = header.splitn(2, ':').collect::<Vec<_>>();
if split.len() != 2 {
return true;
}
headers.insert(split[0].trim().to_string(), split[1].trim().to_string());
true
})?;
transfer.write_function(|data| {
let len = data.len();
body.write_all(data)
.map(|_| len)
.map_err(|_| WriteError::Pause)
})?;
transfer.perform()?;
}
Ok(Response {
status: self.handle.response_code()?,
headers,
body,
})
}
}
fn chunked_read_function<'easy, 'data>(
transfer: &mut Transfer<'easy, 'data>,
data: &'data [u8],
) -> Result<(), curl::Error> {
let mut data = data;
transfer.read_function(move |mut into| {
let written = into.write(data).map_err(|_| ReadError::Abort)?;
data = &data[written..];
Ok(written)
})
}
impl<T> Response<T> {
pub fn is_success(&self) -> bool {
self.status >= 200 && self.status < 300
}
pub fn error_for_status<F, E>(self, closure: F) -> Result<Self, E>
where
F: FnOnce(Self) -> E,
{
if !self.is_success() {
return Err(closure(self));
}
Ok(self)
}
}
impl Response<Vec<u8>> {
pub fn as_string(self) -> Result<Response<String>, FromUtf8Error> {
let body = String::from_utf8(self.body)?;
Ok(Response {
body,
status: self.status,
headers: self.headers,
})
}
#[cfg(feature = "json")]
pub fn as_json<T: DeserializeOwned>(self) -> Result<Response<T>, serde_json::Error> {
let deserialized = serde_json::from_slice(&self.body)?;
Ok(Response {
body: deserialized,
status: self.status,
headers: self.headers,
})
}
}