use std::time::Duration;
use std::sync::{Arc, Mutex};
use url::Url;
use colored::*;
use hyper::Client;
use hyper::method::Method;
use hyper::client::Response;
use hyper::status::StatusCode;
use hyper::header::{Header, Headers, HeaderFormat};
use HttpApi;
use mock::{MockResponse, MockProvider, ResponseProvider};
use resource::http::util;
use resource::http::{HttpHeader, HttpBody, HttpQueryString};
pub struct HttpRequest<A: HttpApi> {
api: A,
method: Method,
path: String,
api_timed_out: bool,
dump_response: bool,
provided_responses: Vec<Box<MockResponse + 'static>>,
provided_mocks: Vec<Box<MockProvider + 'static>>,
request_headers: Headers,
request_body: Option<HttpBody>,
expected_status: Option<StatusCode>,
expected_headers: Headers,
expected_body: Option<HttpBody>,
expected_exact_body: bool,
unexpected_headers: Vec<String>,
run_on_drop: bool
}
impl<A: HttpApi> HttpRequest<A> {
pub fn with_headers(mut self, headers: Vec<HttpHeader>) -> Self {
for header in headers {
let (name, value) = util::http_header_into_tuple(header);
self.request_headers.set_raw(name, vec![value]);
}
self
}
pub fn with_header<H: Header + HeaderFormat>(mut self, header: H) -> Self {
self.request_headers.set(header);
self
}
pub fn with_query(mut self, query: HttpQueryString) -> Self {
let url = self.api.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.request_body = Some(body.into());
self
}
pub fn expected_status(mut self, status_code: StatusCode) -> Self {
self.expected_status = Some(status_code);
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 provide(mut self, mut resources: Vec<Box<MockResponse>>) -> Self {
self.provided_responses.append(&mut resources);
self
}
pub fn dump(mut self) -> Self {
self.dump_response = true;
self
}
pub fn mocks<T: MockProvider + 'static>(mut self, mocks: Vec<T>) -> Self {
for res in mocks {
self.provided_mocks.push(Box::new(res));
}
self
}
pub fn collect(mut self) -> Result<(), String> {
self.run_on_drop = false;
self.run()
}
fn run(&mut self) -> Result<(), String> {
if self.api_timed_out {
return Err(format!(
"\n{}\n\n {} \"{}\" {} {}{}\n\n",
"Noir Api Failure:".red().bold(),
"Specified server to test at".yellow(),
self.api.url().as_str().blue().bold(),
"did not start within".yellow(),
"1000ms".cyan(),
".".yellow()
));
}
let (errors, error_count) = if let Ok(_) = REQUEST_LOCK.lock() {
self.request()
} else {
(vec![format!(
"{} {}",
"Internal Noir Error:".red().bold(),
"Request lock failed.".yellow()
)], 1)
};
if !errors.is_empty() {
let mut report = String::new();
report.push_str(format!(
"\n{} {} {} \"{}{}\" {} {} {}\n",
"Response Failure:".red().bold(),
format!("{}", self.method).cyan().bold(),
"request to".yellow(),
self.api.url().cyan().bold(),
self.path.cyan().bold(),
"returned".yellow(),
format!("{}", error_count).red().bold(),
"error(s)".yellow()
).as_str());
for (index, e) in errors.iter().enumerate() {
report.push_str(format!(
"\n{} {}\n", format!("{:2})", index + 1).red().bold(),
e
).as_str());
}
report.push_str("\n\n");
Err(report)
} else {
Ok(())
}
}
fn request(&mut self) -> (Vec<String>, usize) {
let body = if let Some(body) = self.request_body.take() {
util::http_body_into_vec(body)
} else {
Vec::new()
};
let mut client = Client::new();
client.set_read_timeout(Some(Duration::from_millis(1000)));
client.set_write_timeout(Some(Duration::from_millis(1000)));
let request = client.request(
self.method.clone(),
self.api.url_with_path(self.path.as_str()).as_str()
).headers(
self.request_headers.clone()
).body(
&body[..]
);
ResponseProvider::provide(
self.provided_responses.drain(0..).collect()
);
for mock in &mut self.provided_mocks {
mock.setup();
}
let errors = match request.send() {
Ok(response) => {
self.validate(response)
},
Err(err) => {
(vec![format!(
"{} {}",
"Internal Noir Error:".red().bold(),
err.to_string().yellow()
)], 1)
}
};
for mock in &mut self.provided_mocks {
mock.teardown();
}
errors
}
fn validate(&mut self, mut response: Response) -> (Vec<String>, usize) {
let mut errors = Vec::new();
if self.dump_response {
util::dump_http_like(
&mut errors,
&mut response,
"Response"
);
}
let status = response.status;
errors.append(&mut util::validate_http_request(
&mut response,
"Response",
Some(status),
self.expected_status,
&self.expected_headers,
&mut self.unexpected_headers,
&self.expected_body,
self.expected_exact_body
));
let mut error_count = errors.len();
let index_offset = error_count;
for (
mut response,
response_index,
request_index
) in ResponseProvider::provided_responses() {
let response_errors = response.validate(response_index, request_index);
if !response_errors.is_empty() {
let header = response.validate_header(response_errors.len());
error_count += response_errors.len();
errors.push(format_response_errors(
header,
index_offset + response_index + 1,
response_errors
));
}
}
for mut request in ResponseProvider::additional_requests() {
if let Some(error) = request.validate() {
errors.push(error);
error_count += 1;
}
}
ResponseProvider::reset();
(errors, error_count)
}
}
impl<A: HttpApi> Drop for HttpRequest<A> {
fn drop(&mut self) {
if self.run_on_drop {
if let Err(report) = self.run() {
print!("{}", report);
panic!("Request failed, see above for details.");
}
}
}
}
fn format_response_errors(header: String, offset: usize, errors: Vec<String>) -> String {
let mut formatted = format!(
"{} {}",
"Request Failure:".red().bold(),
header
);
for (index, e) in errors.iter().enumerate() {
formatted.push_str("\n\n");
formatted.push_str(
e.lines()
.enumerate()
.map(|(i, line)| {
if i == 0 {
format!(
" {} {}",
format!(
"{:2}.{})",
offset,
index + 1
).red().bold(),
line
)
} else {
format!(" {}", line)
}
}).collect::<Vec<String>>().join("\n").as_str()
);
}
formatted
}
pub fn http_request<A: HttpApi>(
api: A,
method: Method,
path: &'static str,
api_timed_out: bool
) -> HttpRequest<A> {
HttpRequest {
api: api,
method: method,
path: path.to_string(),
api_timed_out: api_timed_out,
dump_response: false,
provided_responses: Vec::new(),
provided_mocks: Vec::new(),
request_headers: Headers::new(),
request_body: None,
expected_status: None,
expected_headers: Headers::new(),
expected_body: None,
expected_exact_body: false,
unexpected_headers: Vec::new(),
run_on_drop: true
}
}
lazy_static! {
static ref REQUEST_LOCK: Arc<Mutex<()>> = {
Arc::new(Mutex::new(()))
};
}