use std::str;
use std::io::Read;
use json;
use url::Url;
use colored::*;
use hyper::client::Response;
use hyper::header::{Header, Headers, HeaderFormat, ContentType};
use hyper::mime::{Mime, TopLevel, SubLevel};
mod endpoint;
mod request;
mod response;
pub mod util;
pub use self::request::HttpRequest;
pub use self::endpoint::HttpEndpoint;
pub use self::response::HttpResponse;
pub struct HttpBody {
data: Vec<u8>
}
#[doc(hidden)]
impl<'a> From<&'a mut Response> for HttpBody {
fn from(res: &mut Response) -> HttpBody {
let mut body: Vec<u8> = Vec::new();
res.read_to_end(&mut body).unwrap();
HttpBody {
data: body
}
}
}
impl From<Vec<u8>> for HttpBody {
fn from(vec: Vec<u8>) -> HttpBody {
HttpBody {
data: vec
}
}
}
impl From<&'static str> for HttpBody {
fn from(string: &'static str) -> HttpBody {
HttpBody {
data: string.into()
}
}
}
impl From<String> for HttpBody {
fn from(string: String) -> HttpBody {
HttpBody {
data: string.into()
}
}
}
impl From<json::JsonValue> for HttpBody {
fn from(json: json::JsonValue) -> HttpBody {
HttpBody {
data: json::stringify(json).into()
}
}
}
pub struct HttpHeader {
name: String,
value: Vec<u8>
}
impl<H: Header + HeaderFormat> From<H> for HttpHeader {
fn from(header: H) -> HttpHeader {
let mut headers = Headers::new();
headers.set(header);
let name = {
headers.iter().next().unwrap().name()
};
HttpHeader {
name: name.to_string(),
value: headers.get_raw(name).unwrap()[0].clone()
}
}
}
pub struct HttpQueryString {
items: Vec<HttpQueryStringItem>
}
#[doc(hidden)]
impl HttpQueryString {
pub fn new(items: Vec<HttpQueryStringItem>) -> HttpQueryString {
HttpQueryString {
items: items
}
}
}
#[doc(hidden)]
impl Into<Option<String>> for HttpQueryString {
fn into(self) -> Option<String> {
let mut uri = Url::parse("http://query.string/").unwrap();
for item in self.items {
match item {
HttpQueryStringItem::Value(key, value) => {
uri.query_pairs_mut().append_pair(
key.as_str(),
value.as_str()
);
},
HttpQueryStringItem::Array(key, values) => {
for value in values {
uri.query_pairs_mut().append_pair(
key.as_str(),
value.as_str()
);
}
}
}
}
match uri.query() {
Some(query) => Some(query.to_string()),
None => None
}
}
}
pub enum HttpQueryStringItem {
Value(String, String),
Array(String, Vec<String>)
}
macro_rules! impl_query_string_item_type {
($T:ty) => (
impl From<(&'static str, $T)> for HttpQueryStringItem {
fn from(item: (&'static str, $T)) -> HttpQueryStringItem {
HttpQueryStringItem::Value(
item.0.to_string(),
item.1.to_string()
)
}
}
impl From<(&'static str, Vec<$T>)> for HttpQueryStringItem {
fn from(item: (&'static str, Vec<$T>)) -> HttpQueryStringItem {
HttpQueryStringItem::Array(
item.0.to_string(),
item.1.iter().map(|s| s.to_string()).collect()
)
}
}
)
}
impl_query_string_item_type!(&'static str);
impl_query_string_item_type!(String);
impl_query_string_item_type!(bool);
impl_query_string_item_type!(f64);
impl_query_string_item_type!(i64);
impl_query_string_item_type!(u64);
impl_query_string_item_type!(i32);
impl_query_string_item_type!(u32);
pub trait HttpLike {
fn headers(&self) -> &Headers;
fn into_http_body(&mut self) -> HttpBody where Self: Sized;
}
impl HttpLike for Response {
fn headers(&self) -> &Headers {
&self.headers
}
fn into_http_body(&mut self) -> HttpBody where Self: Sized {
self.into()
}
}
enum ParsedHttpBody<'a> {
Text(&'a str),
Json(json::JsonValue),
Raw(&'a [u8])
}
fn parse_http_body<'a>(headers: &Headers, body: &'a HttpBody) -> Result<ParsedHttpBody<'a>, String> {
let content_type = headers.get::<ContentType>().and_then(|content_type| {
Some(content_type.clone())
}).unwrap_or_else(|| {
ContentType(Mime(TopLevel::Application, SubLevel::OctetStream, vec![]))
});
match content_type.0 {
Mime(TopLevel::Text, _, _) => {
match str::from_utf8(body.data.as_slice()) {
Ok(text) => Ok(ParsedHttpBody::Text(text)),
Err(err) => Err(format!(
"{}\n\n {}",
"text body contains invalid UTF-8:".yellow(),
format!("{:?}", err).red().bold()
))
}
},
Mime(TopLevel::Application, SubLevel::Json, _) => {
match parse_json(body.data.as_slice()) {
Ok(json) => Ok(ParsedHttpBody::Json(json)),
Err(err) => Err(err)
}
},
_ => {
Ok(ParsedHttpBody::Raw(&body.data[..]))
}
}
}
fn parse_json(data: &[u8]) -> Result<json::JsonValue, String> {
match str::from_utf8(data) {
Ok(text) => match json::parse(text) {
Ok(value) => Ok(value),
Err(err) => Err(format!(
"{}\n\n {}",
"body contains invalid json:".yellow(),
format!("{:?}", err).red().bold()
))
},
Err(err) => Err(format!(
"{}\n\n {}",
"json body contains invalid UTF-8:".yellow(),
format!("{:?}", err).red().bold()
))
}
}