use std::io::Error;
use url::Url;
use colored::*;
use hyper::method::Method;
use hyper::server::Response as ServerResponse;
use hyper::status::StatusCode;
use hyper::header::{Header, Headers, HeaderFormat};
use super::request::HttpRequest;
use super::endpoint::HttpEndpoint;
use mock::{MockRequest, MockResponse};
use resource::http::util;
use resource::http::{HttpHeader, HttpBody, HttpQueryString};
pub struct HttpResponse<E: HttpEndpoint> {
endpoint: E,
method: Method,
path: String,
dump_request: bool,
response_status: Option<StatusCode>,
response_headers: Headers,
response_body: Option<HttpBody>,
response_error: Option<Error>,
expected_headers: Headers,
unexpected_headers: Vec<String>,
expected_body: Option<HttpBody>,
expected_exact_body: bool,
request: Option<Box<MockRequest>>
}
impl<E: HttpEndpoint> HttpResponse<E> {
pub fn with_status(mut self, status_code: StatusCode) -> Self {
self.response_status = Some(status_code);
self
}
pub fn with_headers(mut self, headers: Vec<HttpHeader>) -> Self {
for header in headers {
let (name, value) = util::http_header_into_tuple(header);
self.response_headers.set_raw(name, vec![value]);
}
self
}
pub fn with_header<H: Header + HeaderFormat>(mut self, header: H) -> Self {
self.response_headers.set(header);
self
}
pub fn with_query(mut self, query: HttpQueryString) -> Self {
let url = self.endpoint.url_with_path(self.path.as_str());
let mut uri = Url::parse(url.as_str()).unwrap();
let query: Option<String> = query.into();
match query {
Some(query) => uri.set_query(Some(query.as_str())),
None => uri.set_query(None)
}
self.path = if let Some(query) = uri.query() {
format!("{}?{}", uri.path(), query)
} else {
uri.path().to_string()
};
self
}
pub fn with_body<S: Into<HttpBody>>(mut self, body: S) -> Self {
self.response_body = Some(body.into());
self
}
pub fn with_error(mut self, error: Error) -> Self {
self.response_error = Some(error);
self
}
pub fn expected_header<H: Header + HeaderFormat>(mut self, header: H) -> Self {
self.expected_headers.set(header);
self
}
pub fn unexpected_header<H: Header + HeaderFormat>(mut self) -> Self {
self.unexpected_headers.push(<H>::header_name().to_string());
self
}
pub fn expected_headers(mut self, headers: Vec<HttpHeader>) -> Self {
for header in headers {
let (name, value) = util::http_header_into_tuple(header);
self.expected_headers.set_raw(name, vec![value]);
}
self
}
pub fn expected_body<S: Into<HttpBody>>(mut self, body: S) -> Self {
self.expected_body = Some(body.into());
self.expected_exact_body = false;
self
}
pub fn expected_exact_body<S: Into<HttpBody>>(mut self, body: S) -> Self {
self.expected_body = Some(body.into());
self.expected_exact_body = true;
self
}
pub fn dump(mut self) -> Self {
self.dump_request = true;
self
}
}
impl<E: HttpEndpoint> MockResponse for HttpResponse<E> {
fn matches(&self, request: &Box<MockRequest>) -> bool {
if let Some(request) = HttpRequest::downcast_ref(request) {
self.request.is_none()
&& self.method == request.method
&& self.endpoint.hostname() == request.hostname
&& self.endpoint.port() == request.port
&& self.path == request.path
} else {
false
}
}
fn respond(
&mut self,
request: Box<MockRequest>
) -> Result<Vec<u8>, Error> {
self.request = Some(request);
if let Some(err) = self.response_error.take() {
Err(err)
} else {
let mut data: Vec<u8> = Vec::new();
{
let mut res = ServerResponse::new(
&mut data, &mut self.response_headers
);
if let Some(status) = self.response_status {
*res.status_mut() = status;
}
if let Some(body) = self.response_body.take() {
res.send(&util::http_body_into_vec(body)[..]).ok();
} else {
res.start().unwrap().end().ok();
}
}
Ok(data)
}
}
fn validate(
&mut self,
response_index: usize,
request_index: usize
) -> Vec<String> {
if let Some(request) = self.request.as_mut() {
let mut errors = Vec::new();
let request = HttpRequest::downcast_mut(request).unwrap();
if self.dump_request {
util::dump_http_like(
&mut errors, request, "Request"
);
}
if response_index != request_index {
errors.push(format!(
"{} {} {}{} {} {}{}",
"Response fetched out of order,".yellow(),
"provided for request".green().bold(),
format!("{}", response_index + 1).blue().bold(),
",".yellow(),
"fetched by request".red().bold(),
format!("{}", request_index + 1).blue().bold(),
".".yellow()
));
}
errors.append(&mut util::validate_http_request(
request,
"Request",
None,
None,
&self.expected_headers,
&mut self.unexpected_headers,
&self.expected_body,
self.expected_exact_body
));
errors
} else {
vec![format!(
"{} {} {} {}{}",
"Expected".yellow(),
"a request".green().bold(),
"for the response, but got".yellow(),
"none".red().bold(),
".".yellow()
)]
}
}
fn validate_header(
&self,
error_count: usize
) -> String {
format!(
"{} {} \"{}{}\" {} {} {}",
format!("{}", self.method).cyan().bold(),
"response provided for".yellow(),
self.endpoint.url().cyan().bold(),
self.path.cyan().bold(),
"returned".yellow(),
format!("{}", error_count).red().bold(),
"error(s)".yellow()
)
}
}
pub fn http_response<E: HttpEndpoint>(
endpoint: E,
method: Method,
path: &'static str
) -> HttpResponse<E> {
HttpResponse {
endpoint: endpoint,
method: method,
path: path.to_string(),
dump_request: false,
response_status: None,
response_headers: Headers::new(),
response_body: None,
response_error: None,
unexpected_headers: Vec::new(),
expected_headers: Headers::new(),
expected_body: None,
expected_exact_body: false,
request: None
}
}