mirror of
https://github.com/Orange-OpenSource/hurl.git
synced 2024-12-25 12:05:32 +03:00
Merge pull request #202 from Orange-OpenSource/feature/add-real-request-sent
Return "real" HTTP request sent from HTTP client
This commit is contained in:
commit
45a3ab480e
File diff suppressed because one or more lines are too long
@ -56,5 +56,5 @@ span.line:before {
|
||||
color: dimgray;
|
||||
}
|
||||
</style></head><body>
|
||||
<div class="hurl-file"><div class="hurl-entry"><div class="request"><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/predicate/error/type</span></span></div><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span><span class="line section-header">[Asserts]</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.status"</span> <span class="predicate-type">equals</span> <span class="string">"true"</span></span><span class="line"><span class="comment">#jsonpath "$.count" equals "0"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.count"</span> <span class="predicate-type">equals</span> <span class="number">0</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.message"</span> <span class="predicate-type">equals</span> <span class="number">0</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.empty"</span> <span class="predicate-type">equals</span> <span class="number">0</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.number"</span> <span class="predicate-type">equals</span> <span class="number">1.1</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.message"</span> <span class="predicate-type">startsWith</span> <span class="string">"hi"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.message"</span> <span class="predicate-type">contains</span> <span class="string">"hi"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.message"</span> <span class="predicate-type">matches</span> <span class="string">"hi"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.message"</span> <span class="predicate-type">equals</span> <span class="number">1</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.toto"</span> <span class="predicate-type">exists</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.message"</span> not <span class="predicate-type">exists</span></span></div></div></div>
|
||||
<div class="hurl-file"><div class="hurl-entry"><div class="request"><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/predicate/error/type</span></span></div><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span><span class="line section-header">[Asserts]</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.status"</span> <span class="predicate-type">equals</span> <span class="string">"true"</span></span><span class="line"><span class="comment">#jsonpath "$.count" equals "0"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.count"</span> <span class="predicate-type">equals</span> <span class="number">0</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.message"</span> <span class="predicate-type">equals</span> <span class="number">0</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.empty"</span> <span class="predicate-type">equals</span> <span class="number">0</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.number"</span> <span class="predicate-type">equals</span> <span class="number">1.1</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.message"</span> <span class="predicate-type">startsWith</span> <span class="string">"hi"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.message"</span> <span class="predicate-type">contains</span> <span class="string">"hi"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.message"</span> <span class="predicate-type">matches</span> <span class="string">"hi"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.message"</span> <span class="predicate-type">equals</span> <span class="number">1</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.toto"</span> <span class="predicate-type">exists</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.message"</span> not <span class="predicate-type">exists</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.list"</span> <span class="predicate-type">equals</span> <span class="number">2</span></span></div></div></div>
|
||||
</body></html>
|
File diff suppressed because it is too large
Load Diff
@ -26,6 +26,7 @@ use std::time::Instant;
|
||||
use super::core::*;
|
||||
use super::options::ClientOptions;
|
||||
use super::request::*;
|
||||
use super::request_spec::*;
|
||||
use super::response::*;
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
@ -51,7 +52,7 @@ pub struct Client {
|
||||
pub redirect_count: usize,
|
||||
// unfortunately, follow-location feature from libcurl can not be used
|
||||
// libcurl returns a single list of headers for the 2 responses
|
||||
// hurl needs the return the headers only for the second (last) response)
|
||||
// hurl needs to keep everything
|
||||
}
|
||||
|
||||
impl Client {
|
||||
@ -85,15 +86,50 @@ impl Client {
|
||||
///
|
||||
/// Execute an http request
|
||||
///
|
||||
pub fn execute(
|
||||
pub fn execute_with_redirect(
|
||||
&mut self,
|
||||
request: &Request,
|
||||
redirect_count: usize,
|
||||
) -> Result<Response, HttpError> {
|
||||
request: &RequestSpec,
|
||||
) -> Result<Vec<(Request, Response)>, HttpError> {
|
||||
let mut calls = vec![];
|
||||
|
||||
let mut request_spec = request.clone();
|
||||
self.redirect_count = 0;
|
||||
loop {
|
||||
let (request, response) = self.execute(&request_spec)?;
|
||||
calls.push((request, response.clone()));
|
||||
if let Some(url) = self.get_follow_location(response.clone()) {
|
||||
request_spec = RequestSpec {
|
||||
method: Method::Get,
|
||||
url,
|
||||
headers: vec![],
|
||||
querystring: vec![],
|
||||
form: vec![],
|
||||
multipart: vec![],
|
||||
cookies: vec![],
|
||||
body: Body::Binary(vec![]),
|
||||
content_type: None,
|
||||
};
|
||||
|
||||
self.redirect_count += 1;
|
||||
if let Some(max_redirect) = self.options.max_redirect {
|
||||
if self.redirect_count > max_redirect {
|
||||
return Err(HttpError::TooManyRedirect);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(calls)
|
||||
}
|
||||
|
||||
///
|
||||
/// Execute an http request
|
||||
///
|
||||
pub fn execute(&mut self, request: &RequestSpec) -> Result<(Request, Response), HttpError> {
|
||||
// set handle attributes
|
||||
// that have not been set or reset
|
||||
|
||||
self.handle.verbose(self.options.verbose).unwrap();
|
||||
self.handle.verbose(true).unwrap();
|
||||
self.handle.ssl_verify_host(!self.options.insecure).unwrap();
|
||||
self.handle.ssl_verify_peer(!self.options.insecure).unwrap();
|
||||
if let Some(proxy) = self.options.proxy.clone() {
|
||||
@ -107,7 +143,8 @@ impl Client {
|
||||
.connect_timeout(self.options.connect_timeout)
|
||||
.unwrap();
|
||||
|
||||
self.set_url(&request.url, &request.querystring);
|
||||
let url = self.generate_url(&request.url, &request.querystring);
|
||||
self.handle.url(url.as_str()).unwrap();
|
||||
self.set_method(&request.method);
|
||||
|
||||
self.set_cookies(&request.cookies);
|
||||
@ -120,23 +157,8 @@ impl Client {
|
||||
|
||||
self.set_headers(&request);
|
||||
|
||||
self.handle
|
||||
.debug_function(|info_type, data| match info_type {
|
||||
// return all request headers (not one by one)
|
||||
easy::InfoType::HeaderOut => {
|
||||
let lines = split_lines(data);
|
||||
for line in lines {
|
||||
eprintln!("> {}", line);
|
||||
}
|
||||
}
|
||||
easy::InfoType::HeaderIn => {
|
||||
if let Some(s) = decode_header(data) {
|
||||
eprint!("< {}", s);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.unwrap();
|
||||
let verbose = self.options.verbose;
|
||||
let mut request_headers: Vec<Header> = vec![];
|
||||
|
||||
let start = Instant::now();
|
||||
let mut status_lines = vec![];
|
||||
@ -149,7 +171,35 @@ impl Client {
|
||||
.read_function(|buf| Ok(data.read(buf).unwrap_or(0)))
|
||||
.unwrap();
|
||||
}
|
||||
transfer
|
||||
.debug_function(|info_type, data| match info_type {
|
||||
// return all request headers (not one by one)
|
||||
easy::InfoType::HeaderOut => {
|
||||
let mut lines = split_lines(data);
|
||||
if verbose {
|
||||
for line in lines.clone() {
|
||||
eprintln!("> {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
lines.pop().unwrap();
|
||||
lines.remove(0); // method/url
|
||||
for line in lines {
|
||||
if let Some(header) = Header::parse(line) {
|
||||
request_headers.push(header);
|
||||
}
|
||||
}
|
||||
}
|
||||
easy::InfoType::HeaderIn => {
|
||||
if let Some(s) = decode_header(data) {
|
||||
if verbose {
|
||||
eprint!("< {}", s);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.unwrap();
|
||||
transfer
|
||||
.header_function(|h| {
|
||||
if let Some(s) = decode_header(h) {
|
||||
@ -196,46 +246,28 @@ impl Client {
|
||||
Some(status_line) => self.parse_response_version(status_line.clone())?,
|
||||
};
|
||||
let headers = self.parse_response_headers(&headers);
|
||||
|
||||
if let Some(url) = self.get_follow_location(headers.clone()) {
|
||||
let request = Request {
|
||||
method: Method::Get,
|
||||
url,
|
||||
headers: vec![],
|
||||
querystring: vec![],
|
||||
form: vec![],
|
||||
multipart: vec![],
|
||||
cookies: vec![],
|
||||
body: Body::Binary(vec![]),
|
||||
content_type: None,
|
||||
};
|
||||
|
||||
let redirect_count = redirect_count + 1;
|
||||
if let Some(max_redirect) = self.options.max_redirect {
|
||||
if redirect_count > max_redirect {
|
||||
return Err(HttpError::TooManyRedirect);
|
||||
}
|
||||
}
|
||||
|
||||
return self.execute(&request, redirect_count);
|
||||
}
|
||||
let duration = start.elapsed();
|
||||
self.redirect_count = redirect_count;
|
||||
self.handle.reset();
|
||||
|
||||
Ok(Response {
|
||||
let request = Request {
|
||||
url,
|
||||
method: (&request.method).to_string(),
|
||||
headers: request_headers,
|
||||
};
|
||||
let response = Response {
|
||||
version,
|
||||
status,
|
||||
headers,
|
||||
body,
|
||||
duration,
|
||||
})
|
||||
};
|
||||
Ok((request, response))
|
||||
}
|
||||
|
||||
///
|
||||
/// set url
|
||||
/// generate url
|
||||
///
|
||||
fn set_url(&mut self, url: &str, params: &[Param]) {
|
||||
fn generate_url(&mut self, url: &str, params: &[Param]) -> String {
|
||||
let url = if params.is_empty() {
|
||||
url.to_string()
|
||||
} else {
|
||||
@ -249,7 +281,7 @@ impl Client {
|
||||
let s = self.encode_params(params);
|
||||
format!("{}{}", url, s)
|
||||
};
|
||||
self.handle.url(url.as_str()).unwrap();
|
||||
url
|
||||
}
|
||||
|
||||
///
|
||||
@ -272,7 +304,7 @@ impl Client {
|
||||
///
|
||||
/// set request headers
|
||||
///
|
||||
fn set_headers(&mut self, request: &Request) {
|
||||
fn set_headers(&mut self, request: &RequestSpec) {
|
||||
let mut list = easy::List::new();
|
||||
|
||||
for header in request.headers.clone() {
|
||||
@ -426,17 +458,15 @@ impl Client {
|
||||
/// 2. a 3xx response code
|
||||
/// 3. a header Location
|
||||
///
|
||||
fn get_follow_location(&mut self, headers: Vec<Header>) -> Option<String> {
|
||||
fn get_follow_location(&mut self, response: Response) -> Option<String> {
|
||||
if !self.options.follow_location {
|
||||
return None;
|
||||
}
|
||||
|
||||
let response_code = self.handle.response_code().unwrap();
|
||||
let response_code = response.status;
|
||||
if !(300..400).contains(&response_code) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let location = match get_header_values(headers, "Location".to_string()).get(0) {
|
||||
let location = match get_header_values(response.headers, "Location".to_string()).get(0) {
|
||||
None => return None,
|
||||
Some(value) => value.clone(),
|
||||
};
|
||||
@ -490,7 +520,7 @@ impl Client {
|
||||
///
|
||||
/// return curl command-line for the http request run by the client
|
||||
///
|
||||
pub fn curl_command_line(&mut self, http_request: &Request) -> String {
|
||||
pub fn curl_command_line(&mut self, http_request: &RequestSpec) -> String {
|
||||
let mut arguments = vec!["curl".to_string()];
|
||||
arguments.append(&mut http_request.curl_args(self.options.context_dir.clone()));
|
||||
|
||||
@ -514,7 +544,7 @@ impl Client {
|
||||
///
|
||||
/// return cookies from both cookies from the cookie storage and the request
|
||||
///
|
||||
pub fn all_cookies(cookie_storage: Vec<Cookie>, request: &Request) -> Vec<RequestCookie> {
|
||||
pub fn all_cookies(cookie_storage: Vec<Cookie>, request: &RequestSpec) -> Vec<RequestCookie> {
|
||||
let mut cookies = request.cookies.clone();
|
||||
cookies.append(
|
||||
&mut cookie_storage
|
||||
|
@ -37,6 +37,18 @@ pub struct Cookie {
|
||||
pub http_only: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RequestCookie {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Param {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for Header {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}: {}", self.name, self.value)
|
||||
@ -60,6 +72,18 @@ impl fmt::Display for Cookie {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RequestCookie {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}={}", self.name, self.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Param {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}: {}", self.name, self.value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ParseCookieError {}
|
||||
|
||||
|
@ -17,11 +17,12 @@
|
||||
*/
|
||||
|
||||
pub use self::client::{Client, HttpError};
|
||||
pub use self::core::{Cookie, Header};
|
||||
pub use self::core::{Cookie, Header, Param, RequestCookie};
|
||||
pub use self::options::ClientOptions;
|
||||
pub use self::request::Request;
|
||||
#[cfg(test)]
|
||||
pub use self::request::tests::*;
|
||||
pub use self::request::{Body, FileParam, Method, MultipartParam, Param, Request, RequestCookie};
|
||||
pub use self::request_spec::tests::*;
|
||||
pub use self::request_spec::{Body, FileParam, Method, MultipartParam, RequestSpec};
|
||||
#[cfg(test)]
|
||||
pub use self::response::tests::*;
|
||||
pub use self::response::{Response, Version};
|
||||
@ -30,4 +31,5 @@ mod client;
|
||||
mod core;
|
||||
mod options;
|
||||
mod request;
|
||||
mod request_spec;
|
||||
mod response;
|
||||
|
@ -16,564 +16,170 @@
|
||||
*
|
||||
*/
|
||||
|
||||
use core::fmt;
|
||||
|
||||
use super::core::*;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Request {
|
||||
pub method: Method,
|
||||
pub url: String,
|
||||
pub method: String,
|
||||
pub headers: Vec<Header>,
|
||||
pub querystring: Vec<Param>,
|
||||
pub form: Vec<Param>,
|
||||
pub multipart: Vec<MultipartParam>,
|
||||
pub cookies: Vec<RequestCookie>,
|
||||
pub body: Body,
|
||||
pub content_type: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Method {
|
||||
Get,
|
||||
Head,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
Connect,
|
||||
Options,
|
||||
Trace,
|
||||
Patch,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Param {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MultipartParam {
|
||||
Param(Param),
|
||||
FileParam(FileParam),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct FileParam {
|
||||
pub name: String,
|
||||
pub filename: String,
|
||||
pub data: Vec<u8>,
|
||||
pub content_type: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RequestCookie {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Body {
|
||||
Text(String),
|
||||
Binary(Vec<u8>),
|
||||
File(Vec<u8>, String),
|
||||
}
|
||||
|
||||
impl Body {
|
||||
pub fn bytes(&self) -> Vec<u8> {
|
||||
match self {
|
||||
Body::Text(s) => s.as_bytes().to_vec(),
|
||||
Body::Binary(bs) => bs.clone(),
|
||||
Body::File(bs, _) => bs.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Method {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let value = match self {
|
||||
Method::Get => "GET",
|
||||
Method::Head => "HEAD",
|
||||
Method::Post => "POST",
|
||||
Method::Put => "PUT",
|
||||
Method::Delete => "DELETE",
|
||||
Method::Connect => "CONNECT",
|
||||
Method::Options => "OPTIONS",
|
||||
Method::Trace => "TRACE",
|
||||
Method::Patch => "PATCH",
|
||||
};
|
||||
write!(f, "{}", value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Param {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}: {}", self.name, self.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MultipartParam {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
MultipartParam::Param(param) => write!(f, "{}", param.to_string()),
|
||||
MultipartParam::FileParam(param) => write!(f, "{}", param.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FileParam {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}: file,{}; {}",
|
||||
self.name, self.filename, self.content_type
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RequestCookie {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}={}", self.name, self.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Request {
|
||||
///
|
||||
/// return request as curl arguments
|
||||
/// It does not contain the requests cookies (they will be accessed from the client)
|
||||
///
|
||||
pub fn curl_args(&self, context_dir: String) -> Vec<String> {
|
||||
let querystring = if self.querystring.is_empty() {
|
||||
"".to_string()
|
||||
} else {
|
||||
let params = self
|
||||
.querystring
|
||||
.iter()
|
||||
.map(|p| p.curl_arg_escape())
|
||||
.collect::<Vec<String>>();
|
||||
params.join("&")
|
||||
};
|
||||
let url = if querystring.as_str() == "" {
|
||||
self.url.to_string()
|
||||
} else if self.url.to_string().contains('?') {
|
||||
format!("{}&{}", self.url.to_string(), querystring)
|
||||
} else {
|
||||
format!("{}?{}", self.url.to_string(), querystring)
|
||||
};
|
||||
let mut arguments = vec![format!("'{}'", url)];
|
||||
|
||||
let data =
|
||||
!self.multipart.is_empty() || !self.form.is_empty() || !self.body.bytes().is_empty();
|
||||
arguments.append(&mut self.method.curl_args(data));
|
||||
|
||||
for header in self.headers.clone() {
|
||||
arguments.append(&mut header.curl_args());
|
||||
pub fn query_string_params(self) -> Vec<Param> {
|
||||
let u = Url::parse(self.url.as_str()).expect("valid url");
|
||||
let mut params = vec![];
|
||||
for (name, value) in u.query_pairs() {
|
||||
let param = Param {
|
||||
name: name.to_string(),
|
||||
value: value.to_string(),
|
||||
};
|
||||
params.push(param);
|
||||
}
|
||||
params
|
||||
}
|
||||
|
||||
let has_explicit_content_type = self
|
||||
.headers
|
||||
pub fn cookies(self) -> Vec<RequestCookie> {
|
||||
self.headers
|
||||
.iter()
|
||||
.map(|h| h.name.clone())
|
||||
.any(|n| n.as_str() == "Content-Type");
|
||||
if !has_explicit_content_type {
|
||||
if let Some(content_type) = self.content_type.clone() {
|
||||
if content_type.as_str() != "application/x-www-form-urlencoded"
|
||||
&& content_type.as_str() != "multipart/form-data"
|
||||
{
|
||||
arguments.push("-H".to_string());
|
||||
arguments.push(format!("'Content-Type: {}'", content_type));
|
||||
}
|
||||
} else if !self.body.bytes().is_empty() {
|
||||
match self.body.clone() {
|
||||
Body::Text(_) => {
|
||||
arguments.push("-H".to_string());
|
||||
arguments.push("'Content-Type:'".to_string())
|
||||
}
|
||||
Body::Binary(_) => {
|
||||
arguments.push("-H".to_string());
|
||||
arguments.push("'Content-Type: application/octet-stream'".to_string())
|
||||
}
|
||||
Body::File(_, _) => {
|
||||
arguments.push("-H".to_string());
|
||||
arguments.push("'Content-Type:'".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for param in self.form.clone() {
|
||||
arguments.push("--data".to_string());
|
||||
arguments.push(format!("'{}'", param.curl_arg_escape()));
|
||||
}
|
||||
for param in self.multipart.clone() {
|
||||
arguments.push("-F".to_string());
|
||||
arguments.push(format!("'{}'", param.curl_arg(context_dir.clone())));
|
||||
}
|
||||
|
||||
if !self.body.bytes().is_empty() {
|
||||
arguments.push("--data".to_string());
|
||||
match self.body.clone() {
|
||||
Body::Text(s) => {
|
||||
let prefix = if s.contains('\n') { "$" } else { "" };
|
||||
arguments.push(format!("{}'{}'", prefix, s.replace("\n", "\\n")))
|
||||
}
|
||||
Body::Binary(bytes) => arguments.push(format!("$'{}'", encode_bytes(bytes))),
|
||||
Body::File(_, filename) => {
|
||||
let prefix = if context_dir.as_str() == "." {
|
||||
"".to_string()
|
||||
} else {
|
||||
format!("{}/", context_dir)
|
||||
};
|
||||
arguments.push(format!("'@{}{}'", prefix, filename))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
arguments
|
||||
.filter(|h| h.name.as_str() == "Cookie")
|
||||
.flat_map(|h| parse_cookies(h.value.as_str().trim()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_byte(b: u8) -> String {
|
||||
format!("\\x{:02x}", b)
|
||||
fn parse_cookies(s: &str) -> Vec<RequestCookie> {
|
||||
s.split(';').map(|t| parse_cookie(t.trim())).collect()
|
||||
}
|
||||
|
||||
fn encode_bytes(b: Vec<u8>) -> String {
|
||||
b.iter().map(|b| encode_byte(*b)).collect()
|
||||
}
|
||||
|
||||
impl Method {
|
||||
pub fn curl_args(&self, data: bool) -> Vec<String> {
|
||||
match self {
|
||||
Method::Get => {
|
||||
if data {
|
||||
vec!["-X".to_string(), "GET".to_string()]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
Method::Head => vec!["-X".to_string(), "HEAD".to_string()],
|
||||
Method::Post => {
|
||||
if data {
|
||||
vec![]
|
||||
} else {
|
||||
vec!["-X".to_string(), "POST".to_string()]
|
||||
}
|
||||
}
|
||||
Method::Put => vec!["-X".to_string(), "PUT".to_string()],
|
||||
Method::Delete => vec!["-X".to_string(), "DELETE".to_string()],
|
||||
Method::Connect => vec!["-X".to_string(), "CONNECT".to_string()],
|
||||
Method::Options => vec!["-X".to_string(), "OPTIONS".to_string()],
|
||||
Method::Trace => vec!["-X".to_string(), "TRACE".to_string()],
|
||||
Method::Patch => vec!["-X".to_string(), "PATCH".to_string()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Header {
|
||||
pub fn curl_args(&self) -> Vec<String> {
|
||||
let name = self.name.clone();
|
||||
let value = self.value.clone();
|
||||
vec![
|
||||
"-H".to_string(),
|
||||
encode_value(format!("{}: {}", name, value)),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl Param {
|
||||
pub fn curl_arg_escape(&self) -> String {
|
||||
let name = self.name.clone();
|
||||
let value = escape_url(self.value.clone());
|
||||
format!("{}={}", name, value)
|
||||
}
|
||||
|
||||
pub fn curl_arg(&self) -> String {
|
||||
let name = self.name.clone();
|
||||
let value = self.value.clone();
|
||||
format!("{}={}", name, value)
|
||||
}
|
||||
}
|
||||
|
||||
impl MultipartParam {
|
||||
pub fn curl_arg(&self, context_dir: String) -> String {
|
||||
match self {
|
||||
MultipartParam::Param(param) => param.curl_arg(),
|
||||
MultipartParam::FileParam(FileParam {
|
||||
name,
|
||||
filename,
|
||||
content_type,
|
||||
..
|
||||
}) => {
|
||||
let prefix = if context_dir.as_str() == "." {
|
||||
"".to_string()
|
||||
} else {
|
||||
format!("{}/", context_dir)
|
||||
};
|
||||
let value = format!("@{}{};type={}", prefix, filename, content_type);
|
||||
format!("{}={}", name, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn escape_single_quote(s: String) -> String {
|
||||
s.chars()
|
||||
.map(|c| {
|
||||
if c == '\'' {
|
||||
"\\'".to_string()
|
||||
} else {
|
||||
c.to_string()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
fn escape_url(s: String) -> String {
|
||||
percent_encoding::percent_encode(s.as_bytes(), percent_encoding::NON_ALPHANUMERIC).to_string()
|
||||
}
|
||||
|
||||
// special encoding for the shell
|
||||
// $'...'
|
||||
fn encode_value(s: String) -> String {
|
||||
if s.contains('\'') {
|
||||
let s = escape_single_quote(s);
|
||||
format!("$'{}'", s)
|
||||
} else {
|
||||
format!("'{}'", s)
|
||||
fn parse_cookie(s: &str) -> RequestCookie {
|
||||
match s.find('=') {
|
||||
Some(i) => RequestCookie {
|
||||
name: s.split_at(i).0.to_string(),
|
||||
value: s.split_at(i + 1).1.to_string(),
|
||||
},
|
||||
None => RequestCookie {
|
||||
name: s.to_string(),
|
||||
value: "".to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::http::RequestCookie;
|
||||
|
||||
pub fn hello_http_request() -> Request {
|
||||
pub fn hello_request() -> Request {
|
||||
Request {
|
||||
method: Method::Get,
|
||||
method: "GET".to_string(),
|
||||
url: "http://localhost:8000/hello".to_string(),
|
||||
querystring: vec![],
|
||||
headers: vec![],
|
||||
cookies: vec![],
|
||||
body: Body::Binary(vec![]),
|
||||
multipart: vec![],
|
||||
form: vec![],
|
||||
content_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn custom_http_request() -> Request {
|
||||
Request {
|
||||
method: Method::Get,
|
||||
url: "http://localhost/custom".to_string(),
|
||||
querystring: vec![],
|
||||
headers: vec![
|
||||
Header {
|
||||
name: String::from("User-Agent"),
|
||||
value: String::from("iPhone"),
|
||||
name: "Host".to_string(),
|
||||
value: "localhost:8000".to_string(),
|
||||
},
|
||||
Header {
|
||||
name: String::from("Foo"),
|
||||
value: String::from("Bar"),
|
||||
name: "Accept".to_string(),
|
||||
value: "*/*".to_string(),
|
||||
},
|
||||
Header {
|
||||
name: "User-Agent".to_string(),
|
||||
value: "hurl/1.0".to_string(),
|
||||
},
|
||||
],
|
||||
cookies: vec![
|
||||
RequestCookie {
|
||||
name: String::from("theme"),
|
||||
value: String::from("light"),
|
||||
},
|
||||
RequestCookie {
|
||||
name: String::from("sessionToken"),
|
||||
value: String::from("abc123"),
|
||||
},
|
||||
],
|
||||
body: Body::Binary(vec![]),
|
||||
multipart: vec![],
|
||||
form: vec![],
|
||||
content_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query_http_request() -> Request {
|
||||
pub fn query_string_request() -> Request {
|
||||
Request {
|
||||
method: Method::Get,
|
||||
url: "http://localhost:8000/querystring-params".to_string(),
|
||||
querystring: vec![
|
||||
Param {
|
||||
name: String::from("param1"),
|
||||
value: String::from("value1"),
|
||||
},
|
||||
Param {
|
||||
name: String::from("param2"),
|
||||
value: String::from("a b"),
|
||||
},
|
||||
],
|
||||
method: "GET".to_string(),
|
||||
url: "http://localhost:8000/querystring-params?param1=value1¶m2=¶m3=a%3Db¶m4=1%2C2%2C3".to_string(),
|
||||
headers: vec![],
|
||||
cookies: vec![],
|
||||
body: Body::Binary(vec![]),
|
||||
multipart: vec![],
|
||||
form: vec![],
|
||||
content_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn form_http_request() -> Request {
|
||||
pub fn cookies_request() -> Request {
|
||||
Request {
|
||||
method: Method::Post,
|
||||
url: "http://localhost/form-params".to_string(),
|
||||
querystring: vec![],
|
||||
method: "GET".to_string(),
|
||||
url: "http://localhost:8000/cookies".to_string(),
|
||||
headers: vec![Header {
|
||||
name: String::from("Content-Type"),
|
||||
value: String::from("application/x-www-form-urlencoded"),
|
||||
name: "Cookie".to_string(),
|
||||
value: "cookie1=value1; cookie2=value2".to_string(),
|
||||
}],
|
||||
cookies: vec![],
|
||||
body: Body::Binary(vec![]),
|
||||
multipart: vec![],
|
||||
form: vec![
|
||||
Param {
|
||||
name: String::from("param1"),
|
||||
value: String::from("value1"),
|
||||
},
|
||||
Param {
|
||||
name: String::from("param2"),
|
||||
value: String::from("a b"),
|
||||
},
|
||||
],
|
||||
content_type: Some("multipart/form-data".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_byte() {
|
||||
assert_eq!(encode_byte(1), "\\x01".to_string());
|
||||
assert_eq!(encode_byte(32), "\\x20".to_string());
|
||||
fn test_query_string() {
|
||||
assert!(hello_request().query_string_params().is_empty());
|
||||
assert_eq!(
|
||||
query_string_request().query_string_params(),
|
||||
vec![
|
||||
Param {
|
||||
name: "param1".to_string(),
|
||||
value: "value1".to_string()
|
||||
},
|
||||
Param {
|
||||
name: "param2".to_string(),
|
||||
value: "".to_string()
|
||||
},
|
||||
Param {
|
||||
name: "param3".to_string(),
|
||||
value: "a=b".to_string()
|
||||
},
|
||||
Param {
|
||||
name: "param4".to_string(),
|
||||
value: "1,2,3".to_string()
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method_curl_args() {
|
||||
assert!(Method::Get.curl_args(false).is_empty());
|
||||
fn test_cookies() {
|
||||
assert!(hello_request().cookies().is_empty());
|
||||
assert_eq!(
|
||||
Method::Get.curl_args(true),
|
||||
vec!["-X".to_string(), "GET".to_string()]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Method::Post.curl_args(false),
|
||||
vec!["-X".to_string(), "POST".to_string()]
|
||||
);
|
||||
assert!(Method::Post.curl_args(true).is_empty());
|
||||
|
||||
assert_eq!(
|
||||
Method::Put.curl_args(false),
|
||||
vec!["-X".to_string(), "PUT".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
Method::Put.curl_args(true),
|
||||
vec!["-X".to_string(), "PUT".to_string()]
|
||||
);
|
||||
cookies_request().cookies(),
|
||||
vec![
|
||||
RequestCookie {
|
||||
name: "cookie1".to_string(),
|
||||
value: "value1".to_string()
|
||||
},
|
||||
RequestCookie {
|
||||
name: "cookie2".to_string(),
|
||||
value: "value2".to_string()
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_curl_args() {
|
||||
fn test_parse_cookies() {
|
||||
assert_eq!(
|
||||
Header {
|
||||
name: "Host".to_string(),
|
||||
value: "example.com".to_string()
|
||||
}
|
||||
.curl_args(),
|
||||
vec!["-H".to_string(), "'Host: example.com'".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
Header {
|
||||
name: "If-Match".to_string(),
|
||||
value: "\"e0023aa4e\"".to_string()
|
||||
}
|
||||
.curl_args(),
|
||||
vec!["-H".to_string(), "'If-Match: \"e0023aa4e\"'".to_string()]
|
||||
);
|
||||
parse_cookies("cookie1=value1; cookie2=value2"),
|
||||
vec![
|
||||
RequestCookie {
|
||||
name: "cookie1".to_string(),
|
||||
value: "value1".to_string()
|
||||
},
|
||||
RequestCookie {
|
||||
name: "cookie2".to_string(),
|
||||
value: "value2".to_string()
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn param_curl_args() {
|
||||
fn test_parse_cookie() {
|
||||
assert_eq!(
|
||||
Param {
|
||||
name: "param1".to_string(),
|
||||
parse_cookie("cookie1=value1"),
|
||||
RequestCookie {
|
||||
name: "cookie1".to_string(),
|
||||
value: "value1".to_string()
|
||||
}
|
||||
.curl_arg(),
|
||||
"param1=value1".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
Param {
|
||||
name: "param2".to_string(),
|
||||
value: "".to_string()
|
||||
}
|
||||
.curl_arg(),
|
||||
"param2=".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
Param {
|
||||
name: "param3".to_string(),
|
||||
value: "a=b".to_string()
|
||||
}
|
||||
.curl_arg_escape(),
|
||||
"param3=a%3Db".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
Param {
|
||||
name: "param4".to_string(),
|
||||
value: "1,2,3".to_string()
|
||||
}
|
||||
.curl_arg_escape(),
|
||||
"param4=1%2C2%2C3".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requests_curl_args() {
|
||||
assert_eq!(
|
||||
hello_http_request().curl_args(".".to_string()),
|
||||
vec!["'http://localhost:8000/hello'".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
custom_http_request().curl_args(".".to_string()),
|
||||
vec![
|
||||
"'http://localhost/custom'".to_string(),
|
||||
"-H".to_string(),
|
||||
"'User-Agent: iPhone'".to_string(),
|
||||
"-H".to_string(),
|
||||
"'Foo: Bar'".to_string(),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
query_http_request().curl_args(".".to_string()),
|
||||
vec![
|
||||
"'http://localhost:8000/querystring-params?param1=value1¶m2=a%20b'".to_string()
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
form_http_request().curl_args(".".to_string()),
|
||||
vec![
|
||||
"'http://localhost/form-params'".to_string(),
|
||||
"-H".to_string(),
|
||||
"'Content-Type: application/x-www-form-urlencoded'".to_string(),
|
||||
"--data".to_string(),
|
||||
"'param1=value1'".to_string(),
|
||||
"--data".to_string(),
|
||||
"'param2=a%20b'".to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_value() {
|
||||
assert_eq!(
|
||||
encode_value("Header1: x".to_string()),
|
||||
"'Header1: x'".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
encode_value("Header1: '".to_string()),
|
||||
"$'Header1: \\''".to_string()
|
||||
);
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
555
packages/hurl/src/http/request_spec.rs
Normal file
555
packages/hurl/src/http/request_spec.rs
Normal file
@ -0,0 +1,555 @@
|
||||
/*
|
||||
* hurl (https://hurl.dev)
|
||||
* Copyright (C) 2020 Orange
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
use core::fmt;
|
||||
|
||||
use super::core::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RequestSpec {
|
||||
pub method: Method,
|
||||
pub url: String,
|
||||
pub headers: Vec<Header>,
|
||||
pub querystring: Vec<Param>,
|
||||
pub form: Vec<Param>,
|
||||
pub multipart: Vec<MultipartParam>,
|
||||
pub cookies: Vec<RequestCookie>,
|
||||
pub body: Body,
|
||||
pub content_type: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Method {
|
||||
Get,
|
||||
Head,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
Connect,
|
||||
Options,
|
||||
Trace,
|
||||
Patch,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MultipartParam {
|
||||
Param(Param),
|
||||
FileParam(FileParam),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct FileParam {
|
||||
pub name: String,
|
||||
pub filename: String,
|
||||
pub data: Vec<u8>,
|
||||
pub content_type: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Body {
|
||||
Text(String),
|
||||
Binary(Vec<u8>),
|
||||
File(Vec<u8>, String),
|
||||
}
|
||||
|
||||
impl Body {
|
||||
pub fn bytes(&self) -> Vec<u8> {
|
||||
match self {
|
||||
Body::Text(s) => s.as_bytes().to_vec(),
|
||||
Body::Binary(bs) => bs.clone(),
|
||||
Body::File(bs, _) => bs.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Method {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let value = match self {
|
||||
Method::Get => "GET",
|
||||
Method::Head => "HEAD",
|
||||
Method::Post => "POST",
|
||||
Method::Put => "PUT",
|
||||
Method::Delete => "DELETE",
|
||||
Method::Connect => "CONNECT",
|
||||
Method::Options => "OPTIONS",
|
||||
Method::Trace => "TRACE",
|
||||
Method::Patch => "PATCH",
|
||||
};
|
||||
write!(f, "{}", value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MultipartParam {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
MultipartParam::Param(param) => write!(f, "{}", param.to_string()),
|
||||
MultipartParam::FileParam(param) => write!(f, "{}", param.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FileParam {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}: file,{}; {}",
|
||||
self.name, self.filename, self.content_type
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestSpec {
|
||||
///
|
||||
/// return request as curl arguments
|
||||
/// It does not contain the requests cookies (they will be accessed from the client)
|
||||
///
|
||||
pub fn curl_args(&self, context_dir: String) -> Vec<String> {
|
||||
let querystring = if self.querystring.is_empty() {
|
||||
"".to_string()
|
||||
} else {
|
||||
let params = self
|
||||
.querystring
|
||||
.iter()
|
||||
.map(|p| p.curl_arg_escape())
|
||||
.collect::<Vec<String>>();
|
||||
params.join("&")
|
||||
};
|
||||
let url = if querystring.as_str() == "" {
|
||||
self.url.to_string()
|
||||
} else if self.url.to_string().contains('?') {
|
||||
format!("{}&{}", self.url.to_string(), querystring)
|
||||
} else {
|
||||
format!("{}?{}", self.url.to_string(), querystring)
|
||||
};
|
||||
let mut arguments = vec![format!("'{}'", url)];
|
||||
|
||||
let data =
|
||||
!self.multipart.is_empty() || !self.form.is_empty() || !self.body.bytes().is_empty();
|
||||
arguments.append(&mut self.method.curl_args(data));
|
||||
|
||||
for header in self.headers.clone() {
|
||||
arguments.append(&mut header.curl_args());
|
||||
}
|
||||
|
||||
let has_explicit_content_type = self
|
||||
.headers
|
||||
.iter()
|
||||
.map(|h| h.name.clone())
|
||||
.any(|n| n.as_str() == "Content-Type");
|
||||
if !has_explicit_content_type {
|
||||
if let Some(content_type) = self.content_type.clone() {
|
||||
if content_type.as_str() != "application/x-www-form-urlencoded"
|
||||
&& content_type.as_str() != "multipart/form-data"
|
||||
{
|
||||
arguments.push("-H".to_string());
|
||||
arguments.push(format!("'Content-Type: {}'", content_type));
|
||||
}
|
||||
} else if !self.body.bytes().is_empty() {
|
||||
match self.body.clone() {
|
||||
Body::Text(_) => {
|
||||
arguments.push("-H".to_string());
|
||||
arguments.push("'Content-Type:'".to_string())
|
||||
}
|
||||
Body::Binary(_) => {
|
||||
arguments.push("-H".to_string());
|
||||
arguments.push("'Content-Type: application/octet-stream'".to_string())
|
||||
}
|
||||
Body::File(_, _) => {
|
||||
arguments.push("-H".to_string());
|
||||
arguments.push("'Content-Type:'".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for param in self.form.clone() {
|
||||
arguments.push("--data".to_string());
|
||||
arguments.push(format!("'{}'", param.curl_arg_escape()));
|
||||
}
|
||||
for param in self.multipart.clone() {
|
||||
arguments.push("-F".to_string());
|
||||
arguments.push(format!("'{}'", param.curl_arg(context_dir.clone())));
|
||||
}
|
||||
|
||||
if !self.body.bytes().is_empty() {
|
||||
arguments.push("--data".to_string());
|
||||
match self.body.clone() {
|
||||
Body::Text(s) => {
|
||||
let prefix = if s.contains('\n') { "$" } else { "" };
|
||||
arguments.push(format!("{}'{}'", prefix, s.replace("\n", "\\n")))
|
||||
}
|
||||
Body::Binary(bytes) => arguments.push(format!("$'{}'", encode_bytes(bytes))),
|
||||
Body::File(_, filename) => {
|
||||
let prefix = if context_dir.as_str() == "." {
|
||||
"".to_string()
|
||||
} else {
|
||||
format!("{}/", context_dir)
|
||||
};
|
||||
arguments.push(format!("'@{}{}'", prefix, filename))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
arguments
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_byte(b: u8) -> String {
|
||||
format!("\\x{:02x}", b)
|
||||
}
|
||||
|
||||
fn encode_bytes(b: Vec<u8>) -> String {
|
||||
b.iter().map(|b| encode_byte(*b)).collect()
|
||||
}
|
||||
|
||||
impl Method {
|
||||
pub fn curl_args(&self, data: bool) -> Vec<String> {
|
||||
match self {
|
||||
Method::Get => {
|
||||
if data {
|
||||
vec!["-X".to_string(), "GET".to_string()]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
Method::Head => vec!["-X".to_string(), "HEAD".to_string()],
|
||||
Method::Post => {
|
||||
if data {
|
||||
vec![]
|
||||
} else {
|
||||
vec!["-X".to_string(), "POST".to_string()]
|
||||
}
|
||||
}
|
||||
Method::Put => vec!["-X".to_string(), "PUT".to_string()],
|
||||
Method::Delete => vec!["-X".to_string(), "DELETE".to_string()],
|
||||
Method::Connect => vec!["-X".to_string(), "CONNECT".to_string()],
|
||||
Method::Options => vec!["-X".to_string(), "OPTIONS".to_string()],
|
||||
Method::Trace => vec!["-X".to_string(), "TRACE".to_string()],
|
||||
Method::Patch => vec!["-X".to_string(), "PATCH".to_string()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Header {
|
||||
pub fn curl_args(&self) -> Vec<String> {
|
||||
let name = self.name.clone();
|
||||
let value = self.value.clone();
|
||||
vec![
|
||||
"-H".to_string(),
|
||||
encode_value(format!("{}: {}", name, value)),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl Param {
|
||||
pub fn curl_arg_escape(&self) -> String {
|
||||
let name = self.name.clone();
|
||||
let value = escape_url(self.value.clone());
|
||||
format!("{}={}", name, value)
|
||||
}
|
||||
|
||||
pub fn curl_arg(&self) -> String {
|
||||
let name = self.name.clone();
|
||||
let value = self.value.clone();
|
||||
format!("{}={}", name, value)
|
||||
}
|
||||
}
|
||||
|
||||
impl MultipartParam {
|
||||
pub fn curl_arg(&self, context_dir: String) -> String {
|
||||
match self {
|
||||
MultipartParam::Param(param) => param.curl_arg(),
|
||||
MultipartParam::FileParam(FileParam {
|
||||
name,
|
||||
filename,
|
||||
content_type,
|
||||
..
|
||||
}) => {
|
||||
let prefix = if context_dir.as_str() == "." {
|
||||
"".to_string()
|
||||
} else {
|
||||
format!("{}/", context_dir)
|
||||
};
|
||||
let value = format!("@{}{};type={}", prefix, filename, content_type);
|
||||
format!("{}={}", name, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn escape_single_quote(s: String) -> String {
|
||||
s.chars()
|
||||
.map(|c| {
|
||||
if c == '\'' {
|
||||
"\\'".to_string()
|
||||
} else {
|
||||
c.to_string()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
fn escape_url(s: String) -> String {
|
||||
percent_encoding::percent_encode(s.as_bytes(), percent_encoding::NON_ALPHANUMERIC).to_string()
|
||||
}
|
||||
|
||||
// special encoding for the shell
|
||||
// $'...'
|
||||
fn encode_value(s: String) -> String {
|
||||
if s.contains('\'') {
|
||||
let s = escape_single_quote(s);
|
||||
format!("$'{}'", s)
|
||||
} else {
|
||||
format!("'{}'", s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
|
||||
pub fn hello_http_request() -> RequestSpec {
|
||||
RequestSpec {
|
||||
method: Method::Get,
|
||||
url: "http://localhost:8000/hello".to_string(),
|
||||
querystring: vec![],
|
||||
headers: vec![],
|
||||
cookies: vec![],
|
||||
body: Body::Binary(vec![]),
|
||||
multipart: vec![],
|
||||
form: vec![],
|
||||
content_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn custom_http_request() -> RequestSpec {
|
||||
RequestSpec {
|
||||
method: Method::Get,
|
||||
url: "http://localhost/custom".to_string(),
|
||||
querystring: vec![],
|
||||
headers: vec![
|
||||
Header {
|
||||
name: String::from("User-Agent"),
|
||||
value: String::from("iPhone"),
|
||||
},
|
||||
Header {
|
||||
name: String::from("Foo"),
|
||||
value: String::from("Bar"),
|
||||
},
|
||||
],
|
||||
cookies: vec![
|
||||
RequestCookie {
|
||||
name: String::from("theme"),
|
||||
value: String::from("light"),
|
||||
},
|
||||
RequestCookie {
|
||||
name: String::from("sessionToken"),
|
||||
value: String::from("abc123"),
|
||||
},
|
||||
],
|
||||
body: Body::Binary(vec![]),
|
||||
multipart: vec![],
|
||||
form: vec![],
|
||||
content_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query_http_request() -> RequestSpec {
|
||||
RequestSpec {
|
||||
method: Method::Get,
|
||||
url: "http://localhost:8000/querystring-params".to_string(),
|
||||
querystring: vec![
|
||||
Param {
|
||||
name: String::from("param1"),
|
||||
value: String::from("value1"),
|
||||
},
|
||||
Param {
|
||||
name: String::from("param2"),
|
||||
value: String::from("a b"),
|
||||
},
|
||||
],
|
||||
headers: vec![],
|
||||
cookies: vec![],
|
||||
body: Body::Binary(vec![]),
|
||||
multipart: vec![],
|
||||
form: vec![],
|
||||
content_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn form_http_request() -> RequestSpec {
|
||||
RequestSpec {
|
||||
method: Method::Post,
|
||||
url: "http://localhost/form-params".to_string(),
|
||||
querystring: vec![],
|
||||
headers: vec![Header {
|
||||
name: String::from("Content-Type"),
|
||||
value: String::from("application/x-www-form-urlencoded"),
|
||||
}],
|
||||
cookies: vec![],
|
||||
body: Body::Binary(vec![]),
|
||||
multipart: vec![],
|
||||
form: vec![
|
||||
Param {
|
||||
name: String::from("param1"),
|
||||
value: String::from("value1"),
|
||||
},
|
||||
Param {
|
||||
name: String::from("param2"),
|
||||
value: String::from("a b"),
|
||||
},
|
||||
],
|
||||
content_type: Some("multipart/form-data".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_byte() {
|
||||
assert_eq!(encode_byte(1), "\\x01".to_string());
|
||||
assert_eq!(encode_byte(32), "\\x20".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method_curl_args() {
|
||||
assert!(Method::Get.curl_args(false).is_empty());
|
||||
assert_eq!(
|
||||
Method::Get.curl_args(true),
|
||||
vec!["-X".to_string(), "GET".to_string()]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Method::Post.curl_args(false),
|
||||
vec!["-X".to_string(), "POST".to_string()]
|
||||
);
|
||||
assert!(Method::Post.curl_args(true).is_empty());
|
||||
|
||||
assert_eq!(
|
||||
Method::Put.curl_args(false),
|
||||
vec!["-X".to_string(), "PUT".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
Method::Put.curl_args(true),
|
||||
vec!["-X".to_string(), "PUT".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_curl_args() {
|
||||
assert_eq!(
|
||||
Header {
|
||||
name: "Host".to_string(),
|
||||
value: "example.com".to_string()
|
||||
}
|
||||
.curl_args(),
|
||||
vec!["-H".to_string(), "'Host: example.com'".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
Header {
|
||||
name: "If-Match".to_string(),
|
||||
value: "\"e0023aa4e\"".to_string()
|
||||
}
|
||||
.curl_args(),
|
||||
vec!["-H".to_string(), "'If-Match: \"e0023aa4e\"'".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn param_curl_args() {
|
||||
assert_eq!(
|
||||
Param {
|
||||
name: "param1".to_string(),
|
||||
value: "value1".to_string()
|
||||
}
|
||||
.curl_arg(),
|
||||
"param1=value1".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
Param {
|
||||
name: "param2".to_string(),
|
||||
value: "".to_string()
|
||||
}
|
||||
.curl_arg(),
|
||||
"param2=".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
Param {
|
||||
name: "param3".to_string(),
|
||||
value: "a=b".to_string()
|
||||
}
|
||||
.curl_arg_escape(),
|
||||
"param3=a%3Db".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
Param {
|
||||
name: "param4".to_string(),
|
||||
value: "1,2,3".to_string()
|
||||
}
|
||||
.curl_arg_escape(),
|
||||
"param4=1%2C2%2C3".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requests_curl_args() {
|
||||
assert_eq!(
|
||||
hello_http_request().curl_args(".".to_string()),
|
||||
vec!["'http://localhost:8000/hello'".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
custom_http_request().curl_args(".".to_string()),
|
||||
vec![
|
||||
"'http://localhost/custom'".to_string(),
|
||||
"-H".to_string(),
|
||||
"'User-Agent: iPhone'".to_string(),
|
||||
"-H".to_string(),
|
||||
"'Foo: Bar'".to_string(),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
query_http_request().curl_args(".".to_string()),
|
||||
vec![
|
||||
"'http://localhost:8000/querystring-params?param1=value1¶m2=a%20b'".to_string()
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
form_http_request().curl_args(".".to_string()),
|
||||
vec![
|
||||
"'http://localhost/form-params'".to_string(),
|
||||
"-H".to_string(),
|
||||
"'Content-Type: application/x-www-form-urlencoded'".to_string(),
|
||||
"--data".to_string(),
|
||||
"'param1=value1'".to_string(),
|
||||
"--data".to_string(),
|
||||
"'param2=a%20b'".to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_value() {
|
||||
assert_eq!(
|
||||
encode_value("Header1: x".to_string()),
|
||||
"'Header1: x'".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
encode_value("Header1: '".to_string()),
|
||||
"$'Header1: \\''".to_string()
|
||||
);
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@ use super::value::Value;
|
||||
use crate::runner::request::{cookie_storage_clear, cookie_storage_set};
|
||||
|
||||
/// Run an entry with the hurl http client
|
||||
/// Return one or more EntryResults (if following redirect)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@ -54,18 +55,18 @@ pub fn run(
|
||||
context_dir: String,
|
||||
log_verbose: &impl Fn(&str),
|
||||
log_error_message: &impl Fn(bool, &str),
|
||||
) -> EntryResult {
|
||||
) -> Vec<EntryResult> {
|
||||
let http_request = match eval_request(entry.request.clone(), variables, context_dir.clone()) {
|
||||
Ok(r) => r,
|
||||
Err(error) => {
|
||||
return EntryResult {
|
||||
return vec![EntryResult {
|
||||
request: None,
|
||||
response: None,
|
||||
captures: vec![],
|
||||
asserts: vec![],
|
||||
errors: vec![error],
|
||||
time_in_ms: 0,
|
||||
};
|
||||
}];
|
||||
}
|
||||
};
|
||||
|
||||
@ -106,8 +107,8 @@ pub fn run(
|
||||
.as_str(),
|
||||
);
|
||||
|
||||
let http_response = match http_client.execute(&http_request, 0) {
|
||||
Ok(response) => response,
|
||||
let calls = match http_client.execute_with_redirect(&http_request) {
|
||||
Ok(calls) => calls,
|
||||
Err(http_error) => {
|
||||
let runner_error = match http_error {
|
||||
HttpError::CouldNotResolveProxyName => RunnerError::CouldNotResolveProxyName,
|
||||
@ -129,87 +130,100 @@ pub fn run(
|
||||
url: http_request.url.clone(),
|
||||
},
|
||||
};
|
||||
return EntryResult {
|
||||
request: Some(http_request.clone()),
|
||||
return vec![EntryResult {
|
||||
request: None,
|
||||
response: None,
|
||||
captures: vec![],
|
||||
asserts: vec![],
|
||||
errors: vec![Error {
|
||||
source_info: SourceInfo {
|
||||
start: entry.clone().request.url.source_info.start,
|
||||
end: entry.clone().request.url.source_info.end,
|
||||
start: entry.request.url.source_info.start,
|
||||
end: entry.request.url.source_info.end,
|
||||
},
|
||||
inner: runner_error,
|
||||
assert: false,
|
||||
}],
|
||||
time_in_ms: 0,
|
||||
}];
|
||||
}
|
||||
};
|
||||
|
||||
let mut entry_results = vec![];
|
||||
for (i, (http_request, http_response)) in calls.iter().enumerate() {
|
||||
let mut captures = vec![];
|
||||
let mut asserts = vec![];
|
||||
let mut errors = vec![];
|
||||
let time_in_ms = http_response.duration.as_millis();
|
||||
|
||||
// Last call
|
||||
if i == calls.len() - 1 {
|
||||
captures = match entry.response.clone() {
|
||||
None => vec![],
|
||||
Some(response) => match eval_captures(response, http_response, variables) {
|
||||
Ok(captures) => captures,
|
||||
Err(e) => {
|
||||
return vec![EntryResult {
|
||||
request: Some(http_request.clone()),
|
||||
response: Some(http_response.clone()),
|
||||
captures: vec![],
|
||||
asserts: vec![],
|
||||
errors: vec![e],
|
||||
time_in_ms,
|
||||
}];
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let time_in_ms = http_response.duration.as_millis();
|
||||
log_verbose(format!("Response Time: {}ms", time_in_ms.to_string()).as_str());
|
||||
|
||||
let captures = match entry.response.clone() {
|
||||
None => vec![],
|
||||
Some(response) => match eval_captures(response, http_response.clone(), variables) {
|
||||
Ok(captures) => captures,
|
||||
Err(e) => {
|
||||
return EntryResult {
|
||||
request: Some(http_request),
|
||||
response: Some(http_response),
|
||||
captures: vec![],
|
||||
asserts: vec![],
|
||||
errors: vec![e],
|
||||
time_in_ms,
|
||||
};
|
||||
// update variables now!
|
||||
for capture_result in captures.clone() {
|
||||
variables.insert(capture_result.name, capture_result.value);
|
||||
}
|
||||
},
|
||||
};
|
||||
asserts = match entry.response.clone() {
|
||||
None => vec![],
|
||||
Some(response) => eval_asserts(
|
||||
response,
|
||||
variables,
|
||||
http_response.clone(),
|
||||
context_dir.clone(),
|
||||
),
|
||||
};
|
||||
errors = asserts
|
||||
.iter()
|
||||
.filter_map(|assert| assert.clone().error())
|
||||
.map(
|
||||
|Error {
|
||||
source_info, inner, ..
|
||||
}| Error {
|
||||
source_info,
|
||||
inner,
|
||||
assert: true,
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
|
||||
// update variables now!
|
||||
for capture_result in captures.clone() {
|
||||
variables.insert(capture_result.name, capture_result.value);
|
||||
}
|
||||
|
||||
let asserts = match entry.response {
|
||||
None => vec![],
|
||||
Some(response) => eval_asserts(response, variables, http_response.clone(), context_dir),
|
||||
};
|
||||
let errors = asserts
|
||||
.iter()
|
||||
.filter_map(|assert| assert.clone().error())
|
||||
.map(
|
||||
|Error {
|
||||
source_info, inner, ..
|
||||
}| Error {
|
||||
source_info,
|
||||
inner,
|
||||
assert: true,
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
|
||||
if !captures.is_empty() {
|
||||
log_verbose("Captures");
|
||||
for capture in captures.clone() {
|
||||
log_verbose(format!("{}: {}", capture.name, capture.value).as_str());
|
||||
if !captures.is_empty() {
|
||||
log_verbose("Captures");
|
||||
for capture in captures.clone() {
|
||||
log_verbose(format!("{}: {}", capture.name, capture.value).as_str());
|
||||
}
|
||||
}
|
||||
log_verbose("");
|
||||
}
|
||||
|
||||
let entry_result = EntryResult {
|
||||
request: Some(http_request.clone()),
|
||||
response: Some(http_response.clone()),
|
||||
captures,
|
||||
asserts,
|
||||
errors,
|
||||
time_in_ms,
|
||||
};
|
||||
entry_results.push(entry_result);
|
||||
}
|
||||
|
||||
log_verbose("");
|
||||
|
||||
EntryResult {
|
||||
request: Some(http_request),
|
||||
response: Some(http_response),
|
||||
captures,
|
||||
asserts,
|
||||
errors,
|
||||
time_in_ms,
|
||||
}
|
||||
entry_results
|
||||
}
|
||||
|
||||
pub fn log_request(log_verbose: impl Fn(&str), request: &http::Request) {
|
||||
pub fn log_request(log_verbose: impl Fn(&str), request: &http::RequestSpec) {
|
||||
log_verbose("Request");
|
||||
log_verbose(format!("{} {}", request.method, request.url).as_str());
|
||||
for header in request.headers.clone() {
|
||||
|
@ -124,7 +124,7 @@ pub fn run(
|
||||
break;
|
||||
}
|
||||
|
||||
let entry_result = entry::run(
|
||||
let entry_results = entry::run(
|
||||
entry,
|
||||
http_client,
|
||||
entry_index,
|
||||
@ -133,12 +133,15 @@ pub fn run(
|
||||
&log_verbose,
|
||||
&log_error_message,
|
||||
);
|
||||
entries.push(entry_result.clone());
|
||||
for e in entry_result.errors.clone() {
|
||||
log_error(&e, false);
|
||||
|
||||
for entry_result in entry_results.clone() {
|
||||
for e in entry_result.errors.clone() {
|
||||
log_error(&e, false);
|
||||
}
|
||||
entries.push(entry_result.clone());
|
||||
}
|
||||
let exit = (options.post_entry)();
|
||||
if exit || (options.fail_fast && !entry_result.errors.is_empty()) {
|
||||
if exit || (options.fail_fast && !entry_results.last().unwrap().errors.is_empty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@
|
||||
*
|
||||
*/
|
||||
|
||||
use crate::http;
|
||||
use crate::http::*;
|
||||
|
||||
use super::cookie::*;
|
||||
@ -99,11 +98,90 @@ fn parse_entry_result(value: serde_json::Value) -> Result<EntryResult, String> {
|
||||
time_in_ms: 0,
|
||||
})
|
||||
}
|
||||
// pub fn _parse_request(value: serde_json::Value) -> Result<Request, ParseError> {
|
||||
// if let serde_json::Value::Object(map) = value {
|
||||
// let method = match map.get("method") {
|
||||
// Some(serde_json::Value::String(s)) => parse_method(s.clone())?,
|
||||
// _ => return Err("expecting a string for the method".to_string()),
|
||||
// };
|
||||
// let url = match map.get("url") {
|
||||
// Some(serde_json::Value::String(s)) => s.to_string(),
|
||||
// _ => return Err("expecting a string for the url".to_string()),
|
||||
// };
|
||||
//
|
||||
// let headers = match map.get("headers") {
|
||||
// Some(serde_json::Value::Array(values)) => {
|
||||
// let mut headers = vec![];
|
||||
// for value in values {
|
||||
// let header = parse_header(value.clone())?;
|
||||
// headers.push(header);
|
||||
// }
|
||||
// headers
|
||||
// }
|
||||
// _ => vec![],
|
||||
// };
|
||||
//
|
||||
// let querystring = match map.get("queryString") {
|
||||
// Some(serde_json::Value::Array(values)) => {
|
||||
// let mut params = vec![];
|
||||
// for value in values {
|
||||
// let param = parse_param(value.clone())?;
|
||||
// params.push(param);
|
||||
// }
|
||||
// params
|
||||
// }
|
||||
// _ => vec![],
|
||||
// };
|
||||
//
|
||||
// let form = match map.get("form") {
|
||||
// Some(serde_json::Value::Array(values)) => {
|
||||
// let mut params = vec![];
|
||||
// for value in values {
|
||||
// let param = parse_param(value.clone())?;
|
||||
// params.push(param);
|
||||
// }
|
||||
// params
|
||||
// }
|
||||
// _ => vec![],
|
||||
// };
|
||||
//
|
||||
// let cookies = match map.get("cookies") {
|
||||
// Some(serde_json::Value::Array(values)) => {
|
||||
// let mut headers = vec![];
|
||||
// for value in values {
|
||||
// let header = parse_request_cookie(value.clone())?;
|
||||
// headers.push(header);
|
||||
// }
|
||||
// headers
|
||||
// }
|
||||
// _ => vec![],
|
||||
// };
|
||||
//
|
||||
// // TODO
|
||||
// let multipart = vec![];
|
||||
// let body = http::Body::Binary(vec![]);
|
||||
// let content_type = None;
|
||||
//
|
||||
// Ok(Request {
|
||||
// method,
|
||||
// url,
|
||||
// headers,
|
||||
// querystring,
|
||||
// form,
|
||||
// multipart,
|
||||
// cookies,
|
||||
// body,
|
||||
// content_type,
|
||||
// })
|
||||
// } else {
|
||||
// Err("expecting an object for the request".to_string())
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn parse_request(value: serde_json::Value) -> Result<Request, ParseError> {
|
||||
if let serde_json::Value::Object(map) = value {
|
||||
let method = match map.get("method") {
|
||||
Some(serde_json::Value::String(s)) => parse_method(s.clone())?,
|
||||
Some(serde_json::Value::String(s)) => parse_method(s.clone())?.to_string(),
|
||||
_ => return Err("expecting a string for the method".to_string()),
|
||||
};
|
||||
let url = match map.get("url") {
|
||||
@ -123,57 +201,10 @@ pub fn parse_request(value: serde_json::Value) -> Result<Request, ParseError> {
|
||||
_ => vec![],
|
||||
};
|
||||
|
||||
let querystring = match map.get("queryString") {
|
||||
Some(serde_json::Value::Array(values)) => {
|
||||
let mut params = vec![];
|
||||
for value in values {
|
||||
let param = parse_param(value.clone())?;
|
||||
params.push(param);
|
||||
}
|
||||
params
|
||||
}
|
||||
_ => vec![],
|
||||
};
|
||||
|
||||
let form = match map.get("form") {
|
||||
Some(serde_json::Value::Array(values)) => {
|
||||
let mut params = vec![];
|
||||
for value in values {
|
||||
let param = parse_param(value.clone())?;
|
||||
params.push(param);
|
||||
}
|
||||
params
|
||||
}
|
||||
_ => vec![],
|
||||
};
|
||||
|
||||
let cookies = match map.get("cookies") {
|
||||
Some(serde_json::Value::Array(values)) => {
|
||||
let mut headers = vec![];
|
||||
for value in values {
|
||||
let header = parse_request_cookie(value.clone())?;
|
||||
headers.push(header);
|
||||
}
|
||||
headers
|
||||
}
|
||||
_ => vec![],
|
||||
};
|
||||
|
||||
// TODO
|
||||
let multipart = vec![];
|
||||
let body = http::Body::Binary(vec![]);
|
||||
let content_type = None;
|
||||
|
||||
Ok(Request {
|
||||
method,
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
querystring,
|
||||
form,
|
||||
multipart,
|
||||
cookies,
|
||||
body,
|
||||
content_type,
|
||||
})
|
||||
} else {
|
||||
Err("expecting an object for the request".to_string())
|
||||
@ -256,37 +287,37 @@ fn parse_header(value: serde_json::Value) -> Result<Header, ParseError> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_param(value: serde_json::Value) -> Result<Param, ParseError> {
|
||||
if let serde_json::Value::Object(map) = value {
|
||||
let name = match map.get("name") {
|
||||
Some(serde_json::Value::String(s)) => s.to_string(),
|
||||
_ => return Err("expecting a string for the cookie name".to_string()),
|
||||
};
|
||||
let value = match map.get("value") {
|
||||
Some(serde_json::Value::String(s)) => s.to_string(),
|
||||
_ => return Err("expecting a string for the cookie value".to_string()),
|
||||
};
|
||||
Ok(Param { name, value })
|
||||
} else {
|
||||
Err("Expecting object for the param".to_string())
|
||||
}
|
||||
}
|
||||
// pub fn parse_param(value: serde_json::Value) -> Result<Param, ParseError> {
|
||||
// if let serde_json::Value::Object(map) = value {
|
||||
// let name = match map.get("name") {
|
||||
// Some(serde_json::Value::String(s)) => s.to_string(),
|
||||
// _ => return Err("expecting a string for the cookie name".to_string()),
|
||||
// };
|
||||
// let value = match map.get("value") {
|
||||
// Some(serde_json::Value::String(s)) => s.to_string(),
|
||||
// _ => return Err("expecting a string for the cookie value".to_string()),
|
||||
// };
|
||||
// Ok(Param { name, value })
|
||||
// } else {
|
||||
// Err("Expecting object for the param".to_string())
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn parse_request_cookie(value: serde_json::Value) -> Result<RequestCookie, ParseError> {
|
||||
if let serde_json::Value::Object(map) = value {
|
||||
let name = match map.get("name") {
|
||||
Some(serde_json::Value::String(s)) => s.to_string(),
|
||||
_ => return Err("expecting a string for the cookie name".to_string()),
|
||||
};
|
||||
let value = match map.get("value") {
|
||||
Some(serde_json::Value::String(s)) => s.to_string(),
|
||||
_ => return Err("expecting a string for the cookie value".to_string()),
|
||||
};
|
||||
Ok(RequestCookie { name, value })
|
||||
} else {
|
||||
Err("Expecting object for the request cookie".to_string())
|
||||
}
|
||||
}
|
||||
// pub fn parse_request_cookie(value: serde_json::Value) -> Result<RequestCookie, ParseError> {
|
||||
// if let serde_json::Value::Object(map) = value {
|
||||
// let name = match map.get("name") {
|
||||
// Some(serde_json::Value::String(s)) => s.to_string(),
|
||||
// _ => return Err("expecting a string for the cookie name".to_string()),
|
||||
// };
|
||||
// let value = match map.get("value") {
|
||||
// Some(serde_json::Value::String(s)) => s.to_string(),
|
||||
// _ => return Err("expecting a string for the cookie value".to_string()),
|
||||
// };
|
||||
// Ok(RequestCookie { name, value })
|
||||
// } else {
|
||||
// Err("Expecting object for the request cookie".to_string())
|
||||
// }
|
||||
// }
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn parse_response_cookie(value: serde_json::Value) -> Result<ResponseCookie, ParseError> {
|
||||
@ -383,59 +414,59 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::runner::value::Value;
|
||||
|
||||
#[test]
|
||||
fn test_parse_request() {
|
||||
let v: serde_json::Value = serde_json::from_str(
|
||||
r#"{
|
||||
"method": "GET",
|
||||
"url": "http://localhost:8000/hello",
|
||||
"headers": []
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(parse_request(v).unwrap(), hello_http_request());
|
||||
|
||||
let v: serde_json::Value = serde_json::from_str(
|
||||
r#"{
|
||||
"method": "GET",
|
||||
"url": "http://localhost:8000/querystring-params?param1=value1¶m2=a%20b",
|
||||
"headers": []
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
parse_request(v).unwrap(),
|
||||
http::Request {
|
||||
method: http::Method::Get,
|
||||
url: "http://localhost:8000/querystring-params?param1=value1¶m2=a%20b"
|
||||
.to_string(),
|
||||
querystring: vec![],
|
||||
headers: vec![],
|
||||
cookies: vec![],
|
||||
body: http::Body::Binary(vec![]),
|
||||
form: vec![],
|
||||
multipart: vec![],
|
||||
content_type: None,
|
||||
}
|
||||
);
|
||||
|
||||
let v: serde_json::Value = serde_json::from_str(
|
||||
r#"{
|
||||
"method": "GET",
|
||||
"url": "http://localhost/custom",
|
||||
"headers": [
|
||||
{"name": "User-Agent", "value": "iPhone"},
|
||||
{"name": "Foo", "value": "Bar"}
|
||||
],
|
||||
"cookies": [
|
||||
{"name": "theme", "value": "light"},
|
||||
{"name": "sessionToken", "value": "abc123"}
|
||||
]
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(parse_request(v).unwrap(), custom_http_request());
|
||||
}
|
||||
// #[test]
|
||||
// fn test_parse_request() {
|
||||
// let v: serde_json::Value = serde_json::from_str(
|
||||
// r#"{
|
||||
// "method": "GET",
|
||||
// "url": "http://localhost:8000/hello",
|
||||
// "headers": []
|
||||
// }"#,
|
||||
// )
|
||||
// .unwrap();
|
||||
// assert_eq!(_parse_request(v).unwrap(), hello_http_request());
|
||||
//
|
||||
// let v: serde_json::Value = serde_json::from_str(
|
||||
// r#"{
|
||||
// "method": "GET",
|
||||
// "url": "http://localhost:8000/querystring-params?param1=value1¶m2=a%20b",
|
||||
// "headers": []
|
||||
// }"#,
|
||||
// )
|
||||
// .unwrap();
|
||||
// assert_eq!(
|
||||
// _parse_request(v).unwrap(),
|
||||
// http::Request {
|
||||
// method: http::Method::Get,
|
||||
// url: "http://localhost:8000/querystring-params?param1=value1¶m2=a%20b"
|
||||
// .to_string(),
|
||||
// querystring: vec![],
|
||||
// headers: vec![],
|
||||
// cookies: vec![],
|
||||
// body: http::Body::Binary(vec![]),
|
||||
// form: vec![],
|
||||
// multipart: vec![],
|
||||
// content_type: None,
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// let v: serde_json::Value = serde_json::from_str(
|
||||
// r#"{
|
||||
// "method": "GET",
|
||||
// "url": "http://localhost/custom",
|
||||
// "headers": [
|
||||
// {"name": "User-Agent", "value": "iPhone"},
|
||||
// {"name": "Foo", "value": "Bar"}
|
||||
// ],
|
||||
// "cookies": [
|
||||
// {"name": "theme", "value": "light"},
|
||||
// {"name": "sessionToken", "value": "abc123"}
|
||||
// ]
|
||||
// }"#,
|
||||
// )
|
||||
// .unwrap();
|
||||
// assert_eq!(_parse_request(v).unwrap(), custom_http_request());
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn test_parse_response() {
|
||||
|
@ -94,20 +94,12 @@ impl Serialize for Request {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
// 3 is the number of fields in the struct.
|
||||
let mut state = serializer.serialize_struct("??", 3)?;
|
||||
state.serialize_field("method", &self.clone().method.to_string())?;
|
||||
let mut state = serializer.serialize_struct("Request", 5)?;
|
||||
state.serialize_field("method", &self.clone().method)?;
|
||||
state.serialize_field("url", &self.clone().url)?;
|
||||
state.serialize_field("queryString", &self.clone().querystring)?;
|
||||
state.serialize_field("headers", &self.clone().headers)?;
|
||||
state.serialize_field("cookies", &self.clone().cookies)?;
|
||||
state.serialize_field("multipartFormData", &self.clone().multipart)?;
|
||||
|
||||
if !self.clone().form.is_empty() {
|
||||
state.serialize_field("form", &self.clone().form)?;
|
||||
}
|
||||
state.serialize_field("body", &base64::encode(&self.body.bytes()))?;
|
||||
|
||||
state.serialize_field("cookies", &self.clone().cookies())?;
|
||||
state.serialize_field("queryString", &self.clone().query_string_params())?;
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
@ -118,7 +110,7 @@ impl Serialize for Response {
|
||||
S: Serializer,
|
||||
{
|
||||
// 3 is the number of fields in the struct.
|
||||
let mut state = serializer.serialize_struct("??", 3)?;
|
||||
let mut state = serializer.serialize_struct("Response", 3)?;
|
||||
state.serialize_field("httpVersion", &self.clone().version)?;
|
||||
state.serialize_field("status", &self.clone().status)?;
|
||||
state.serialize_field("cookies", &self.clone().cookies())?;
|
||||
|
@ -36,7 +36,7 @@ pub fn eval_request(
|
||||
request: Request,
|
||||
variables: &HashMap<String, Value>,
|
||||
context_dir: String,
|
||||
) -> Result<http::Request, Error> {
|
||||
) -> Result<http::RequestSpec, Error> {
|
||||
let method = eval_method(request.method.clone());
|
||||
|
||||
let url = eval_template(request.clone().url, &variables)?;
|
||||
@ -121,7 +121,7 @@ pub fn eval_request(
|
||||
// }
|
||||
// }
|
||||
|
||||
Ok(http::Request {
|
||||
Ok(http::RequestSpec {
|
||||
method,
|
||||
url,
|
||||
headers,
|
||||
|
@ -212,7 +212,7 @@ pub fn eval_asserts(
|
||||
|
||||
pub fn eval_captures(
|
||||
response: Response,
|
||||
http_response: http::Response,
|
||||
http_response: &http::Response,
|
||||
variables: &HashMap<String, Value>,
|
||||
) -> Result<Vec<CaptureResult>, Error> {
|
||||
let mut captures = vec![];
|
||||
@ -321,7 +321,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
eval_captures(
|
||||
user_response(),
|
||||
http::xml_two_users_http_response(),
|
||||
&http::xml_two_users_http_response(),
|
||||
&variables,
|
||||
)
|
||||
.unwrap(),
|
||||
|
@ -14,8 +14,8 @@ fn default_client() -> Client {
|
||||
Client::init(options)
|
||||
}
|
||||
|
||||
fn default_get_request(url: String) -> Request {
|
||||
Request {
|
||||
fn default_get_request(url: String) -> RequestSpec {
|
||||
RequestSpec {
|
||||
method: Method::Get,
|
||||
url,
|
||||
headers: vec![],
|
||||
@ -33,13 +33,25 @@ fn default_get_request(url: String) -> Request {
|
||||
#[test]
|
||||
fn test_hello() {
|
||||
let mut client = default_client();
|
||||
let request = default_get_request("http://localhost:8000/hello".to_string());
|
||||
let request_spec = default_get_request("http://localhost:8000/hello".to_string());
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'http://localhost:8000/hello'".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert_eq!(request.method, "GET".to_string());
|
||||
assert_eq!(request.url, "http://localhost:8000/hello".to_string());
|
||||
assert_eq!(request.headers.len(), 3);
|
||||
assert!(request.headers.contains(&Header {
|
||||
name: "Host".to_string(),
|
||||
value: "localhost:8000".to_string()
|
||||
}));
|
||||
assert!(request.headers.contains(&Header {
|
||||
name: "Accept".to_string(),
|
||||
value: "*/*".to_string()
|
||||
}));
|
||||
|
||||
assert_eq!(response.version, Version::Http10);
|
||||
assert_eq!(response.status, 200);
|
||||
assert_eq!(response.body, b"Hello World!".to_vec());
|
||||
@ -63,7 +75,7 @@ fn test_hello() {
|
||||
#[test]
|
||||
fn test_put() {
|
||||
let mut client = default_client();
|
||||
let request = Request {
|
||||
let request_spec = RequestSpec {
|
||||
method: Method::Put,
|
||||
url: "http://localhost:8000/put".to_string(),
|
||||
headers: vec![],
|
||||
@ -75,11 +87,22 @@ fn test_put() {
|
||||
content_type: None,
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'http://localhost:8000/put' -X PUT".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert_eq!(request.method, "PUT".to_string());
|
||||
assert_eq!(request.url, "http://localhost:8000/put".to_string());
|
||||
assert!(request.headers.contains(&Header {
|
||||
name: "Host".to_string(),
|
||||
value: "localhost:8000".to_string()
|
||||
}));
|
||||
assert!(request.headers.contains(&Header {
|
||||
name: "Accept".to_string(),
|
||||
value: "*/*".to_string()
|
||||
}));
|
||||
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
}
|
||||
@ -87,7 +110,7 @@ fn test_put() {
|
||||
#[test]
|
||||
fn test_patch() {
|
||||
let mut client = default_client();
|
||||
let request = Request {
|
||||
let request_spec = RequestSpec {
|
||||
method: Method::Patch,
|
||||
url: "http://localhost:8000/patch/file.txt".to_string(),
|
||||
headers: vec![
|
||||
@ -112,11 +135,25 @@ fn test_patch() {
|
||||
content_type: None,
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'http://localhost:8000/patch/file.txt' -X PATCH -H 'Host: www.example.com' -H 'Content-Type: application/example' -H 'If-Match: \"e0023aa4e\"'".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert_eq!(request.method, "PATCH".to_string());
|
||||
assert_eq!(
|
||||
request.url,
|
||||
"http://localhost:8000/patch/file.txt".to_string()
|
||||
);
|
||||
assert!(request.headers.contains(&Header {
|
||||
name: "Host".to_string(),
|
||||
value: "www.example.com".to_string()
|
||||
}));
|
||||
assert!(request.headers.contains(&Header {
|
||||
name: "Content-Type".to_string(),
|
||||
value: "application/example".to_string()
|
||||
}));
|
||||
|
||||
assert_eq!(response.status, 204);
|
||||
assert!(response.body.is_empty());
|
||||
}
|
||||
@ -128,7 +165,7 @@ fn test_patch() {
|
||||
#[test]
|
||||
fn test_custom_headers() {
|
||||
let mut client = default_client();
|
||||
let request = Request {
|
||||
let request_spec = RequestSpec {
|
||||
method: Method::Get,
|
||||
url: "http://localhost:8000/custom-headers".to_string(),
|
||||
headers: vec![
|
||||
@ -147,11 +184,20 @@ fn test_custom_headers() {
|
||||
};
|
||||
assert!(client.options.curl_args().is_empty());
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'http://localhost:8000/custom-headers' -H 'Fruit: Raspberry' -H 'Fruit: Apple' -H 'Fruit: Banana' -H 'Fruit: Grape' -H 'Color: Green'".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert_eq!(request.method, "GET".to_string());
|
||||
assert_eq!(
|
||||
request.url,
|
||||
"http://localhost:8000/custom-headers".to_string()
|
||||
);
|
||||
assert!(request.headers.contains(&Header {
|
||||
name: "Fruit".to_string(),
|
||||
value: "Raspberry".to_string()
|
||||
}));
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
}
|
||||
@ -163,7 +209,7 @@ fn test_custom_headers() {
|
||||
#[test]
|
||||
fn test_querystring_params() {
|
||||
let mut client = default_client();
|
||||
let request = Request {
|
||||
let request_spec = RequestSpec {
|
||||
method: Method::Get,
|
||||
url: "http://localhost:8000/querystring-params".to_string(),
|
||||
headers: vec![],
|
||||
@ -192,10 +238,14 @@ fn test_querystring_params() {
|
||||
content_type: None,
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'http://localhost:8000/querystring-params?param1=value1¶m2=¶m3=a%3Db¶m4=1%2C2%2C3'".to_string()
|
||||
);
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert_eq!(request.method, "GET".to_string());
|
||||
assert_eq!(request.url, "http://localhost:8000/querystring-params?param1=value1¶m2=¶m3=a%3Db¶m4=1%2C2%2C3".to_string());
|
||||
assert_eq!(request.headers.len(), 3);
|
||||
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
}
|
||||
@ -207,7 +257,7 @@ fn test_querystring_params() {
|
||||
#[test]
|
||||
fn test_form_params() {
|
||||
let mut client = default_client();
|
||||
let request = Request {
|
||||
let request_spec = RequestSpec {
|
||||
method: Method::Post,
|
||||
url: "http://localhost:8000/form-params".to_string(),
|
||||
headers: vec![],
|
||||
@ -236,17 +286,27 @@ fn test_form_params() {
|
||||
content_type: Some("application/x-www-form-urlencoded".to_string()),
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'http://localhost:8000/form-params' --data 'param1=value1' --data 'param2=' --data 'param3=a%3Db' --data 'param4=a%253db'".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert_eq!(request.method, "POST".to_string());
|
||||
assert_eq!(request.url, "http://localhost:8000/form-params".to_string());
|
||||
assert!(request.headers.contains(&Header {
|
||||
name: "Content-Type".to_string(),
|
||||
value: "application/x-www-form-urlencoded".to_string()
|
||||
}));
|
||||
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
|
||||
// make sure you can reuse client for other request
|
||||
let request = default_get_request("http://localhost:8000/hello".to_string());
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request).unwrap();
|
||||
assert_eq!(request.method, "GET".to_string());
|
||||
assert_eq!(request.url, "http://localhost:8000/hello".to_string());
|
||||
assert_eq!(request.headers.len(), 3);
|
||||
assert_eq!(response.status, 200);
|
||||
assert_eq!(response.body, b"Hello World!".to_vec());
|
||||
}
|
||||
@ -256,11 +316,15 @@ fn test_form_params() {
|
||||
// region redirect
|
||||
|
||||
#[test]
|
||||
fn test_follow_location() {
|
||||
let request = default_get_request("http://localhost:8000/redirect".to_string());
|
||||
fn test_redirect() {
|
||||
let request_spec = default_get_request("http://localhost:8000/redirect".to_string());
|
||||
|
||||
let mut client = default_client();
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert_eq!(request.method, "GET".to_string());
|
||||
assert_eq!(request.url, "http://localhost:8000/redirect".to_string());
|
||||
assert_eq!(request.headers.len(), 3);
|
||||
|
||||
assert_eq!(response.status, 302);
|
||||
assert_eq!(
|
||||
response
|
||||
@ -270,6 +334,11 @@ fn test_follow_location() {
|
||||
"http://localhost:8000/redirected"
|
||||
);
|
||||
assert_eq!(client.redirect_count, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_follow_location() {
|
||||
let request_spec = default_get_request("http://localhost:8000/redirect".to_string());
|
||||
|
||||
let options = ClientOptions {
|
||||
follow_location: true,
|
||||
@ -288,24 +357,35 @@ fn test_follow_location() {
|
||||
let mut client = Client::init(options);
|
||||
assert_eq!(client.options.curl_args(), vec!["-L".to_string(),]);
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'http://localhost:8000/redirect' -L".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
assert_eq!(
|
||||
response
|
||||
.get_header_values("Content-Length".to_string())
|
||||
.get(0)
|
||||
.unwrap(),
|
||||
"0"
|
||||
);
|
||||
let calls = client.execute_with_redirect(&request_spec).unwrap();
|
||||
assert_eq!(calls.len(), 2);
|
||||
|
||||
let (request1, response1) = calls.get(0).unwrap();
|
||||
assert_eq!(request1.method, "GET".to_string());
|
||||
assert_eq!(request1.url, "http://localhost:8000/redirect".to_string());
|
||||
assert_eq!(request1.headers.len(), 3);
|
||||
assert_eq!(response1.status, 302);
|
||||
assert!(response1.headers.contains(&Header {
|
||||
name: "Location".to_string(),
|
||||
value: "http://localhost:8000/redirected".to_string()
|
||||
}));
|
||||
|
||||
let (request2, response2) = calls.get(1).unwrap();
|
||||
assert_eq!(request2.method, "GET".to_string());
|
||||
assert_eq!(request2.url, "http://localhost:8000/redirected".to_string());
|
||||
assert_eq!(request2.headers.len(), 3);
|
||||
assert_eq!(response2.status, 200);
|
||||
|
||||
assert_eq!(client.redirect_count, 1);
|
||||
|
||||
// make sure that the redirect count is reset to 0
|
||||
let request = default_get_request("http://localhost:8000/hello".to_string());
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let calls = client.execute_with_redirect(&request).unwrap();
|
||||
let (_, response) = calls.get(0).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
assert_eq!(response.body, b"Hello World!".to_vec());
|
||||
assert_eq!(client.redirect_count, 0);
|
||||
@ -328,18 +408,25 @@ fn test_max_redirect() {
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
let request = default_get_request("http://localhost:8000/redirect".to_string());
|
||||
|
||||
let request_spec = default_get_request("http://localhost:8000/redirect/15".to_string());
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'http://localhost:8000/redirect' -L --max-redirs 10".to_string()
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'http://localhost:8000/redirect/15' -L --max-redirs 10".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 5).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
assert_eq!(client.redirect_count, 6);
|
||||
|
||||
let error = client.execute(&request, 11).err().unwrap();
|
||||
let error = client.execute_with_redirect(&request_spec).err().unwrap();
|
||||
assert_eq!(error, HttpError::TooManyRedirect);
|
||||
|
||||
let request_spec = default_get_request("http://localhost:8000/redirect/8".to_string());
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'http://localhost:8000/redirect/8' -L --max-redirs 10".to_string()
|
||||
);
|
||||
let calls = client.execute_with_redirect(&request_spec).unwrap();
|
||||
let (request, response) = calls.last().unwrap();
|
||||
assert_eq!(request.url, "http://localhost:8000/redirect/0".to_string());
|
||||
assert_eq!(response.status, 200);
|
||||
assert_eq!(client.redirect_count, 8);
|
||||
}
|
||||
|
||||
// endregion
|
||||
@ -349,7 +436,7 @@ fn test_max_redirect() {
|
||||
#[test]
|
||||
fn test_multipart_form_data() {
|
||||
let mut client = default_client();
|
||||
let request = Request {
|
||||
let request_spec = RequestSpec {
|
||||
method: Method::Post,
|
||||
url: "http://localhost:8000/multipart-form-data".to_string(),
|
||||
headers: vec![],
|
||||
@ -384,17 +471,22 @@ fn test_multipart_form_data() {
|
||||
content_type: Some("multipart/form-data".to_string()),
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'http://localhost:8000/multipart-form-data' -F 'key1=value1' -F 'upload1=@data.txt;type=text/plain' -F 'upload2=@data.html;type=text/html' -F 'upload3=@data.txt;type=text/html'".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert!(request.headers.contains(&Header {
|
||||
name: "Content-Length".to_string(),
|
||||
value: "616".to_string(),
|
||||
}));
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
|
||||
// make sure you can reuse client for other request
|
||||
let request = default_get_request("http://localhost:8000/hello".to_string());
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let request_spec = default_get_request("http://localhost:8000/hello".to_string());
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert_eq!(request.method, "GET".to_string());
|
||||
assert_eq!(response.status, 200);
|
||||
assert_eq!(response.body, b"Hello World!".to_vec());
|
||||
}
|
||||
@ -406,7 +498,7 @@ fn test_multipart_form_data() {
|
||||
#[test]
|
||||
fn test_post_bytes() {
|
||||
let mut client = default_client();
|
||||
let request = Request {
|
||||
let request_spec = RequestSpec {
|
||||
method: Method::Post,
|
||||
url: "http://localhost:8000/post-base64".to_string(),
|
||||
headers: vec![],
|
||||
@ -418,10 +510,15 @@ fn test_post_bytes() {
|
||||
content_type: None,
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'http://localhost:8000/post-base64' -H 'Content-Type: application/octet-stream' --data $'\\x48\\x65\\x6c\\x6c\\x6f\\x20\\x57\\x6f\\x72\\x6c\\x64\\x21'".to_string()
|
||||
);
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert!(request.headers.contains(&Header {
|
||||
name: "Content-Length".to_string(),
|
||||
value: "12".to_string(),
|
||||
}));
|
||||
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
}
|
||||
@ -431,7 +528,7 @@ fn test_post_bytes() {
|
||||
#[test]
|
||||
fn test_expect() {
|
||||
let mut client = default_client();
|
||||
let request = Request {
|
||||
let request_spec = RequestSpec {
|
||||
method: Method::Post,
|
||||
url: "http://localhost:8000/expect".to_string(),
|
||||
headers: vec![Header {
|
||||
@ -446,11 +543,15 @@ fn test_expect() {
|
||||
content_type: None,
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'http://localhost:8000/expect' -H 'Expect: 100-continue' -H 'Content-Type:' --data 'data'".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert!(request.headers.contains(&Header {
|
||||
name: "Expect".to_string(),
|
||||
value: "100-continue".to_string(),
|
||||
}));
|
||||
assert_eq!(response.status, 200);
|
||||
assert_eq!(response.version, Version::Http10);
|
||||
assert!(response.body.is_empty());
|
||||
@ -473,7 +574,7 @@ fn test_basic_authentication() {
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
let request = Request {
|
||||
let request_spec = RequestSpec {
|
||||
method: Method::Get,
|
||||
url: "http://localhost:8000/basic-authentication".to_string(),
|
||||
headers: vec![],
|
||||
@ -485,16 +586,20 @@ fn test_basic_authentication() {
|
||||
content_type: None,
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'http://localhost:8000/basic-authentication' --user 'bob:secret'".to_string()
|
||||
);
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert!(request.headers.contains(&Header {
|
||||
name: "Authorization".to_string(),
|
||||
value: "Basic Ym9iOnNlY3JldA==".to_string(),
|
||||
}));
|
||||
assert_eq!(response.status, 200);
|
||||
assert_eq!(response.version, Version::Http10);
|
||||
assert_eq!(response.body, b"You are authenticated".to_vec());
|
||||
|
||||
let mut client = default_client();
|
||||
let request = Request {
|
||||
let request_spec = RequestSpec {
|
||||
method: Method::Get,
|
||||
url: "http://bob:secret@localhost:8000/basic-authentication".to_string(),
|
||||
headers: vec![],
|
||||
@ -506,10 +611,14 @@ fn test_basic_authentication() {
|
||||
content_type: None,
|
||||
};
|
||||
assert_eq!(
|
||||
request.curl_args(".".to_string()),
|
||||
request_spec.curl_args(".".to_string()),
|
||||
vec!["'http://bob:secret@localhost:8000/basic-authentication'".to_string()]
|
||||
);
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert!(request.headers.contains(&Header {
|
||||
name: "Authorization".to_string(),
|
||||
value: "Basic Ym9iOnNlY3JldA==".to_string(),
|
||||
}));
|
||||
assert_eq!(response.status, 200);
|
||||
assert_eq!(response.version, Version::Http10);
|
||||
assert_eq!(response.body, b"You are authenticated".to_vec());
|
||||
@ -521,7 +630,7 @@ fn test_basic_authentication() {
|
||||
fn test_error_could_not_resolve_host() {
|
||||
let mut client = default_client();
|
||||
let request = default_get_request("http://unknown".to_string());
|
||||
let error = client.execute(&request, 0).err().unwrap();
|
||||
let error = client.execute(&request).err().unwrap();
|
||||
|
||||
assert_eq!(error, HttpError::CouldNotResolveHost("unknown".to_string()));
|
||||
}
|
||||
@ -529,8 +638,8 @@ fn test_error_could_not_resolve_host() {
|
||||
#[test]
|
||||
fn test_error_fail_to_connect() {
|
||||
let mut client = default_client();
|
||||
let request = default_get_request("http://localhost:9999".to_string());
|
||||
let error = client.execute(&request, 0).err().unwrap();
|
||||
let request_spec = default_get_request("http://localhost:9999".to_string());
|
||||
let error = client.execute(&request_spec).err().unwrap();
|
||||
assert_eq!(error, HttpError::FailToConnect);
|
||||
|
||||
let options = ClientOptions {
|
||||
@ -549,7 +658,7 @@ fn test_error_fail_to_connect() {
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
let request = default_get_request("http://localhost:8000/hello".to_string());
|
||||
let error = client.execute(&request, 0).err().unwrap();
|
||||
let error = client.execute(&request).err().unwrap();
|
||||
assert_eq!(error, HttpError::FailToConnect);
|
||||
}
|
||||
|
||||
@ -570,8 +679,8 @@ fn test_error_could_not_resolve_proxy_name() {
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
let request = default_get_request("http://localhost:8000/hello".to_string());
|
||||
let error = client.execute(&request, 0).err().unwrap();
|
||||
let request_spec = default_get_request("http://localhost:8000/hello".to_string());
|
||||
let error = client.execute(&request_spec).err().unwrap();
|
||||
assert_eq!(error, HttpError::CouldNotResolveProxyName);
|
||||
}
|
||||
|
||||
@ -592,8 +701,8 @@ fn test_error_ssl() {
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
let request = default_get_request("https://localhost:8001/hello".to_string());
|
||||
let error = client.execute(&request, 0).err().unwrap();
|
||||
let request_spec = default_get_request("https://localhost:8001/hello".to_string());
|
||||
let error = client.execute(&request_spec).err().unwrap();
|
||||
let message = if cfg!(windows) {
|
||||
"schannel: SEC_E_UNTRUSTED_ROOT (0x80090325) - The certificate chain was issued by an authority that is not trusted.".to_string()
|
||||
} else {
|
||||
@ -619,8 +728,8 @@ fn test_timeout() {
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
let request = default_get_request("http://localhost:8000/timeout".to_string());
|
||||
let error = client.execute(&request, 0).err().unwrap();
|
||||
let request_spec = default_get_request("http://localhost:8000/timeout".to_string());
|
||||
let error = client.execute(&request_spec).err().unwrap();
|
||||
assert_eq!(error, HttpError::Timeout);
|
||||
}
|
||||
|
||||
@ -642,7 +751,7 @@ fn test_accept_encoding() {
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
|
||||
let request = Request {
|
||||
let request_spec = RequestSpec {
|
||||
method: Method::Get,
|
||||
url: "http://localhost:8000/compressed/gzip".to_string(),
|
||||
headers: vec![],
|
||||
@ -653,7 +762,11 @@ fn test_accept_encoding() {
|
||||
body: Body::Binary(vec![]),
|
||||
content_type: None,
|
||||
};
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert!(request.headers.contains(&Header {
|
||||
name: "Accept-Encoding".to_string(),
|
||||
value: "gzip, deflate, br".to_string()
|
||||
}));
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.headers.contains(&Header {
|
||||
name: "Content-Length".to_string(),
|
||||
@ -678,12 +791,12 @@ fn test_connect_timeout() {
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
let request = default_get_request("http://10.0.0.0".to_string());
|
||||
let request_spec = default_get_request("http://10.0.0.0".to_string());
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'http://10.0.0.0' --connect-timeout 1".to_string()
|
||||
);
|
||||
let error = client.execute(&request, 0).err().unwrap();
|
||||
let error = client.execute(&request_spec).err().unwrap();
|
||||
if cfg!(target_os = "macos") {
|
||||
assert_eq!(error, HttpError::FailToConnect);
|
||||
} else {
|
||||
@ -697,7 +810,7 @@ fn test_connect_timeout() {
|
||||
#[test]
|
||||
fn test_cookie() {
|
||||
let mut client = default_client();
|
||||
let request = Request {
|
||||
let request_spec = RequestSpec {
|
||||
method: Method::Get,
|
||||
url: "http://localhost:8000/cookies/set-request-cookie1-valueA".to_string(),
|
||||
headers: vec![],
|
||||
@ -712,18 +825,22 @@ fn test_cookie() {
|
||||
content_type: None,
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'http://localhost:8000/cookies/set-request-cookie1-valueA' --cookie 'cookie1=valueA'"
|
||||
.to_string()
|
||||
);
|
||||
|
||||
//assert_eq!(request.cookies(), vec!["cookie1=valueA".to_string(),]);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert!(request.headers.contains(&Header {
|
||||
name: "Cookie".to_string(),
|
||||
value: "cookie1=valueA".to_string()
|
||||
}));
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
|
||||
let request = Request {
|
||||
let request_spec = RequestSpec {
|
||||
method: Method::Get,
|
||||
url: "http://localhost:8000/cookies/assert-that-cookie1-is-not-in-session".to_string(),
|
||||
headers: vec![],
|
||||
@ -734,14 +851,14 @@ fn test_cookie() {
|
||||
body: Body::Binary(vec![]),
|
||||
content_type: None,
|
||||
};
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (_request, response) = client.execute(&request_spec).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_request_cookies() {
|
||||
let mut client = default_client();
|
||||
let request = Request {
|
||||
let request_spec = RequestSpec {
|
||||
method: Method::Get,
|
||||
url: "http://localhost:8000/cookies/set-multiple-request-cookies".to_string(),
|
||||
headers: vec![],
|
||||
@ -762,11 +879,15 @@ fn test_multiple_request_cookies() {
|
||||
content_type: None,
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'http://localhost:8000/cookies/set-multiple-request-cookies' --cookie 'user1=Bob; user2=Bill'".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert!(request.headers.contains(&Header {
|
||||
name: "Cookie".to_string(),
|
||||
value: "user1=Bob; user2=Bill".to_string()
|
||||
}));
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
}
|
||||
@ -774,9 +895,13 @@ fn test_multiple_request_cookies() {
|
||||
#[test]
|
||||
fn test_cookie_storage() {
|
||||
let mut client = default_client();
|
||||
let request =
|
||||
let request_spec =
|
||||
default_get_request("http://localhost:8000/cookies/set-session-cookie2-valueA".to_string());
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert_eq!(
|
||||
request.url,
|
||||
"http://localhost:8000/cookies/set-session-cookie2-valueA".to_string()
|
||||
);
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
|
||||
@ -791,14 +916,18 @@ fn test_cookie_storage() {
|
||||
expires: "0".to_string(),
|
||||
name: "cookie2".to_string(),
|
||||
value: "valueA".to_string(),
|
||||
http_only: false
|
||||
http_only: false,
|
||||
}
|
||||
);
|
||||
|
||||
let request = default_get_request(
|
||||
let request_spec = default_get_request(
|
||||
"http://localhost:8000/cookies/assert-that-cookie2-is-valueA".to_string(),
|
||||
);
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert!(request.headers.contains(&Header {
|
||||
name: "Cookie".to_string(),
|
||||
value: "cookie2=valueA".to_string()
|
||||
}));
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
}
|
||||
@ -820,15 +949,24 @@ fn test_cookie_file() {
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
let request = default_get_request(
|
||||
let request_spec = default_get_request(
|
||||
"http://localhost:8000/cookies/assert-that-cookie2-is-valueA".to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'http://localhost:8000/cookies/assert-that-cookie2-is-valueA' --cookie tests/cookies.txt".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert_eq!(
|
||||
request.url,
|
||||
"http://localhost:8000/cookies/assert-that-cookie2-is-valueA"
|
||||
);
|
||||
assert!(request.headers.contains(&Header {
|
||||
name: "Cookie".to_string(),
|
||||
value: "cookie2=valueA".to_string()
|
||||
}));
|
||||
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
}
|
||||
@ -855,12 +993,13 @@ fn test_proxy() {
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
let request = default_get_request("http://localhost:8000/proxy".to_string());
|
||||
let request_spec = default_get_request("http://localhost:8000/proxy".to_string());
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'http://localhost:8000/proxy' --proxy 'localhost:8888'".to_string()
|
||||
);
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert_eq!(request.url, "http://localhost:8000/proxy");
|
||||
assert_eq!(response.status, 200);
|
||||
}
|
||||
|
||||
@ -884,15 +1023,13 @@ fn test_insecure() {
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
assert_eq!(client.options.curl_args(), vec!["--insecure".to_string()]);
|
||||
let request = default_get_request("https://localhost:8001/hello".to_string());
|
||||
let request_spec = default_get_request("https://localhost:8001/hello".to_string());
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
client.curl_command_line(&request_spec),
|
||||
"curl 'https://localhost:8001/hello' --insecure".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
let (request, response) = client.execute(&request_spec).unwrap();
|
||||
assert_eq!(request.url, "https://localhost:8001/hello");
|
||||
assert_eq!(response.status, 200);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user