use std::str;
use std::fmt;
use curl::easy::Easy;
#[derive(Debug, PartialEq)]
pub enum ConnectionError {
NoContent,
BadRequest,
Forbidden,
NotFound,
MethodNotAllowed,
RequestEntityTooLarge,
TooManyRequests,
UnprocessableEntity,
ServiceUnavailable,
CurlError(String),
UnknownError(u32),
}
impl fmt::Display for ConnectionError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ConnectionError::NoContent => write!(f, "204 No Content"),
ConnectionError::BadRequest => write!(f, "400 Bad Request"),
ConnectionError::Forbidden => write!(f, "403 Forbidden"),
ConnectionError::NotFound => write!(f, "404 Not Found"),
ConnectionError::MethodNotAllowed => write!(f, "405 Method Not Allowed"),
ConnectionError::RequestEntityTooLarge => write!(f, "413 Request Entity Too Large"),
ConnectionError::TooManyRequests => write!(f, "429 Too Many Requests"),
ConnectionError::UnprocessableEntity => write!(f, "456 Unprocessable Entity"),
ConnectionError::ServiceUnavailable => write!(f, "503 Service Unavailable"),
ConnectionError::CurlError(ref e) => write!(f, "Curl Error: {}", e),
ConnectionError::UnknownError(code) => write!(f, "Unknown Error: {}", code),
}
}
}
fn make_post_session(url: String, post_data: String) -> Result<Easy, String> {
let mut easy = Easy::new();
easy.url(url.as_str()).map_err(|e| e.to_string())?;
easy.post(true).map_err(|e| e.to_string())?;
easy.post_fields_copy(post_data.as_bytes()).map_err(|e| e.to_string())?;
Ok(easy)
}
fn make_get_session(url: String) -> Result<Easy, String> {
let mut easy = Easy::new();
easy.url(url.as_str()).map_err(|e| e.to_string())?;
Ok(easy)
}
fn make_delete_session(url: String) -> Result<Easy, String> {
let mut easy = Easy::new();
easy.url(url.as_str()).map_err(|e| e.to_string())?;
easy.custom_request("DELETE").map_err(|e| e.to_string())?;
Ok(easy)
}
fn make_patch_session(url: String, patch_data: String) -> Result<Easy, String> {
let mut easy = Easy::new();
easy.url(url.as_str()).map_err(|e| e.to_string())?;
easy.custom_request("PATCH").map_err(|e| e.to_string())?;
easy.post_fields_copy(patch_data.as_bytes()).map_err(|e| e.to_string())?;
Ok(easy)
}
fn transfer(mut easy: Easy) -> Result<(Vec<u8>, u32), String> {
let mut dst = Vec::new();
{
let mut transfer = easy.transfer();
transfer.write_function(|data| {
dst.extend_from_slice(data);
Ok(data.len())
}).map_err(|e| e.to_string())?;
transfer.perform().map_err(|e| e.to_string())?;
}
let response_code = easy.response_code().map_err(|e| e.to_string())?;
Ok((dst, response_code))
}
fn handle_error(response_code: u32) -> ConnectionError {
match response_code {
204 => ConnectionError::NoContent,
400 => ConnectionError::BadRequest,
403 => ConnectionError::Forbidden,
404 => ConnectionError::NotFound,
405 => ConnectionError::MethodNotAllowed,
413 => ConnectionError::RequestEntityTooLarge,
429 => ConnectionError::TooManyRequests,
456 => ConnectionError::UnprocessableEntity,
503 => ConnectionError::ServiceUnavailable,
code => ConnectionError::UnknownError(code),
}
}
pub fn post_with_headers(url: String, post_data: String, header: &Vec<String>) -> Result<String, ConnectionError> {
let mut easy = match make_post_session(url, post_data) {
Ok(easy) => easy,
Err(e) => return Err(ConnectionError::CurlError(e)),
};
{
let mut list = curl::easy::List::new();
for h in header {
list.append(h).map_err(|e| ConnectionError::CurlError(e.to_string()))?;
}
easy.http_headers(list).map_err(|e| ConnectionError::CurlError(e.to_string()))?;
}
let (dst, response_code) = match transfer(easy) {
Ok((dst, response_code)) => (dst, response_code),
Err(e) => return Err(ConnectionError::CurlError(e)),
};
if dst.len() > 0 {
let s = str::from_utf8(&dst).expect("Invalid UTF-8");
Ok(s.to_string())
} else {
Err(handle_error(response_code))
}
}
pub fn get_with_headers(url: String, header: &Vec<String>) -> Result<String, ConnectionError> {
let mut easy = match make_get_session(url) {
Ok(easy) => easy,
Err(e) => return Err(ConnectionError::CurlError(e)),
};
{
let mut list = curl::easy::List::new();
for h in header {
list.append(h).map_err(|e| ConnectionError::CurlError(e.to_string()))?;
}
easy.http_headers(list).map_err(|e| ConnectionError::CurlError(e.to_string()))?;
}
let (dst, response_code) = match transfer(easy) {
Ok((dst, response_code)) => (dst, response_code),
Err(e) => return Err(ConnectionError::CurlError(e)),
};
if dst.len() > 0 {
let s = str::from_utf8(&dst).expect("Invalid UTF-8");
Ok(s.to_string())
} else {
Err(handle_error(response_code))
}
}
pub fn delete_with_headers(url: String, header: &Vec<String>) -> Result<(), ConnectionError> {
let mut easy = match make_delete_session(url) {
Ok(easy) => easy,
Err(e) => return Err(ConnectionError::CurlError(e)),
};
{
let mut list = curl::easy::List::new();
for h in header {
list.append(h).map_err(|e| ConnectionError::CurlError(e.to_string()))?;
}
easy.http_headers(list).map_err(|e| ConnectionError::CurlError(e.to_string()))?;
}
let response_code = match transfer(easy) {
Ok((_, response_code)) => response_code,
Err(e) => return Err(ConnectionError::CurlError(e)),
};
if response_code == 200 || response_code == 204 {
Ok(())
} else {
Err(handle_error(response_code))
}
}
pub fn patch_with_headers(url: String, patch_data: String, header: &Vec<String>) -> Result<String, ConnectionError> {
let mut easy = match make_patch_session(url, patch_data) {
Ok(easy) => easy,
Err(e) => return Err(ConnectionError::CurlError(e)),
};
{
let mut list = curl::easy::List::new();
for h in header {
list.append(h).map_err(|e| ConnectionError::CurlError(e.to_string()))?;
}
easy.http_headers(list).map_err(|e| ConnectionError::CurlError(e.to_string()))?;
}
let (dst, response_code) = match transfer(easy) {
Ok((dst, response_code)) => (dst, response_code),
Err(e) => return Err(ConnectionError::CurlError(e)),
};
if dst.len() > 0 {
let s = str::from_utf8(&dst).expect("Invalid UTF-8");
Ok(s.to_string())
} else {
Err(handle_error(response_code))
}
}
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn access_post_with_headers_test() {
let url = "http://localhost:8000/".to_string();
let post_data = "test".to_string();
let headers = vec!["Content-Type: application/json".to_string()];
let result = post_with_headers(url, post_data, &headers);
assert!(result.is_ok());
}
#[test]
fn access_delete_test() {
let url = "http://localhost:8000/".to_string();
let headers = vec!["Content-Type: application/json".to_string()];
let result = delete_with_headers(url, &headers);
assert!(result.is_ok());
}
#[test]
fn access_patch_with_headers_test() {
let url = "http://localhost:8000/".to_string();
let patch_data = "test".to_string();
let headers = vec!["Content-Type: application/json".to_string()];
let result = patch_with_headers(url, patch_data, &headers);
assert!(result.is_ok());
}
#[test]
fn impl_handling_error_test() {
let error_code = 204;
let error = handle_error(error_code);
assert_eq!(error, ConnectionError::NoContent);
let error_code = 400;
let error = handle_error(error_code);
assert_eq!(error, ConnectionError::BadRequest);
let error_code = 403;
let error = handle_error(error_code);
assert_eq!(error, ConnectionError::Forbidden);
let error_code = 404;
let error = handle_error(error_code);
assert_eq!(error, ConnectionError::NotFound);
let error_code = 413;
let error = handle_error(error_code);
assert_eq!(error, ConnectionError::RequestEntityTooLarge);
let error_code = 429;
let error = handle_error(error_code);
assert_eq!(error, ConnectionError::TooManyRequests);
let error_code = 456;
let error = handle_error(error_code);
assert_eq!(error, ConnectionError::UnprocessableEntity);
let error_code = 503;
let error = handle_error(error_code);
assert_eq!(error, ConnectionError::ServiceUnavailable);
let error_code = 999;
let error = handle_error(error_code);
assert_eq!(error, ConnectionError::UnknownError(999));
}
}