use std::str;
use std::io::Read;
use json;
use colored::*;
use hyper::client::Response;
use hyper::header::{Headers, ContentType};
use hyper::mime::{Mime, TopLevel, SubLevel};
use util;
use Options;
use super::{HttpLike, HttpFormData};
use super::form::{
http_form_into_body_parts,
http_form_into_fields,
parse_form_data
};
pub struct HttpBody {
data: Vec<u8>,
mime: Option<Mime>
}
pub fn http_body_from_parts(data: Vec<u8>, headers: &Headers) -> HttpBody {
let mime = if let Some(&ContentType(ref mime)) = headers.get::<ContentType>() {
mime.clone()
} else {
Mime(TopLevel::Application, SubLevel::OctetStream, vec![])
};
HttpBody {
data: data,
mime: Some(mime)
}
}
pub fn http_body_into_parts(body: HttpBody) -> (Option<Mime>, Option<Vec<u8>>) {
(body.mime, Some(body.data))
}
#[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();
http_body_from_parts(body, &res.headers)
}
}
impl From<Vec<u8>> for HttpBody {
fn from(vec: Vec<u8>) -> HttpBody {
HttpBody {
data: vec,
mime: Some(Mime(TopLevel::Application, SubLevel::OctetStream, vec![]))
}
}
}
impl From<&'static str> for HttpBody {
fn from(string: &'static str) -> HttpBody {
HttpBody {
data: string.into(),
mime: Some(Mime(TopLevel::Text, SubLevel::Plain, vec![]))
}
}
}
impl From<String> for HttpBody {
fn from(string: String) -> HttpBody {
HttpBody {
data: string.into(),
mime: Some(Mime(TopLevel::Text, SubLevel::Plain, vec![]))
}
}
}
impl From<json::JsonValue> for HttpBody {
fn from(json: json::JsonValue) -> HttpBody {
HttpBody {
data: json::stringify(json).into(),
mime: Some(Mime(TopLevel::Application, SubLevel::Json, vec![]))
}
}
}
impl From<HttpFormData> for HttpBody {
fn from(form: HttpFormData) -> HttpBody {
let (mime_type, body) = http_form_into_body_parts(form);
HttpBody {
data: body,
mime: Some(mime_type)
}
}
}
pub fn validate_http_request_body<T: HttpLike>(
errors: &mut Vec<String>,
options: &Options,
result: &mut T,
context: &str,
expected_body: &&HttpBody,
expected_exact_body: bool
) {
let body = result.into_http_body();
if body.data == expected_body.data {
return;
}
let body = parse_http_body(&body);
errors.push(match body {
Ok(actual) => match actual {
ParsedHttpBody::Text(actual) => {
match str::from_utf8(expected_body.data.as_slice()) {
Ok(expected) => {
let (expected, actual, diff) = util::diff::text(
expected,
actual
);
format!(
"{} {}\n\n \"{}\"\n\n {}\n\n \"{}\"\n\n {}\n\n \"{}\"",
context.yellow(),
"does not match, expected:".yellow(),
expected.green().bold(),
"but got:".yellow(),
actual.red().bold(),
"difference:".yellow(),
diff
)
},
Err(err) => format!(
"{} {}\n\n {}",
context.yellow(),
"body, expected text provided by test contains invalid UTF-8:".yellow(),
format!("{:?}", err).red().bold()
)
}
},
ParsedHttpBody::Json(actual) => {
let expected_json = util::json::parse(
expected_body.data.as_slice(),
"body json provided by test"
);
match expected_json {
Ok(expected) => {
let errors = util::json::compare(
&expected,
&actual,
options.json_compare_depth,
expected_exact_body
);
if errors.is_ok() {
return;
}
format!(
"{} {}\n\n {}",
context.yellow(),
"body json does not match, expected:".yellow(),
util::json::format(errors.unwrap_err())
)
},
Err(err) => {
format!(
"{} {} {}",
context.yellow(),
"body, expected".yellow(),
err
)
}
}
},
ParsedHttpBody::Form(actual) => {
match parse_http_body(expected_body) {
Ok(ParsedHttpBody::Form(expected)) => {
let expected = http_form_into_fields(expected);
let actual = http_form_into_fields(actual);
let errors = util::form::compare(
&expected,
&actual,
expected_exact_body
);
if errors.is_ok() {
return;
}
format!(
"{} {}\n\n {}",
context.yellow(),
"body form data does not match, expected:".yellow(),
util::form::format(errors.unwrap_err())
)
},
_ => unreachable!()
}
},
ParsedHttpBody::Raw(actual) => {
format!(
"{} {}\n\n [{}]\n\n {}\n\n [{}]",
context.yellow(),
format!(
"{} {}{}",
"raw body data does not match, expected the following".yellow(),
format!("{} bytes", expected_body.data.len()).green().bold(),
":".yellow()
),
util::raw::format_green(expected_body.data.as_slice()),
format!(
"{} {} {}",
"but got the following".yellow(),
format!("{} bytes", actual.len()).red().bold(),
"instead:".yellow()
),
util::raw::format_red(actual),
)
}
},
Err(err) => {
format!("{} {}", context.yellow(), err)
}
});
}
pub enum ParsedHttpBody<'a> {
Text(&'a str),
Json(json::JsonValue),
Form(HttpFormData),
Raw(&'a [u8])
}
pub fn parse_http_body(body: &HttpBody) -> Result<ParsedHttpBody, String> {
if let Some(mime) = body.mime.as_ref() {
match mime.clone() {
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 util::json::parse(body.data.as_slice(), "body json") {
Ok(json) => Ok(ParsedHttpBody::Json(json)),
Err(err) => Err(err)
}
},
Mime(TopLevel::Application, SubLevel::FormData, attrs) |
Mime(TopLevel::Application, SubLevel::WwwFormUrlEncoded, attrs) => {
let boundary = attrs.get(0).map(|b| {
b.1.as_str().to_string()
});
match parse_form_data(body.data.as_slice(), boundary) {
Ok(data) => Ok(ParsedHttpBody::Form(data)),
Err(err) => Err(format!(
"{}\n\n {}",
"form body could not be parsed:".yellow(),
err.red().bold()
))
}
},
_ => {
Ok(ParsedHttpBody::Raw(&body.data[..]))
}
}
} else {
Ok(ParsedHttpBody::Raw(&body.data[..]))
}
}