use std::str::FromStr;
use base64::Engine;
use base64::engine::general_purpose;
use hurl_core::ast::Body as AstBody;
use hurl_core::ast::Method as AstMethod;
use hurl_core::ast::{Bytes, MultilineString, MultilineStringKind, Request, Template};
use crate::http::{
AUTHORIZATION, Body, Header, HeaderVec, Method, Param, RequestCookie, RequestSpec, Url,
UrlError,
};
use crate::util::path::ContextDir;
use super::body;
use super::error::{RunnerError, RunnerErrorKind};
use super::multipart;
use super::template;
use super::variable::VariableSet;
pub fn eval_request(
request: &Request,
variables: &VariableSet,
context_dir: &ContextDir,
) -> Result<RequestSpec, RunnerError> {
let method = eval_method(&request.method);
let url = eval_url(&request.url, variables)?;
let mut headers = HeaderVec::new();
for header in &request.headers {
let name = template::eval_template(&header.key, variables)?;
let value = template::eval_template(&header.value, variables)?;
let header = Header::new(&name, &value);
headers.push(header);
}
if let Some(kv) = &request.basic_auth() {
let name = template::eval_template(&kv.key, variables)?;
let value = template::eval_template(&kv.value, variables)?;
let user_password = format!("{name}:{value}");
let user_password = user_password.as_bytes();
let authorization = general_purpose::STANDARD.encode(user_password);
let value = format!("Basic {authorization}");
let header = Header::new(AUTHORIZATION, &value);
headers.push(header);
}
let mut querystring = vec![];
for param in request.querystring_params() {
let name = template::eval_template(¶m.key, variables)?;
let value = template::eval_template(¶m.value, variables)?;
let param = Param { name, value };
querystring.push(param);
}
let mut form = vec![];
for param in request.form_params() {
let name = template::eval_template(¶m.key, variables)?;
let value = template::eval_template(¶m.value, variables)?;
let param = Param { name, value };
form.push(param);
}
let mut cookies = vec![];
for cookie in request.cookies() {
let name = template::eval_template(&cookie.name, variables)?;
let value = template::eval_template(&cookie.value, variables)?;
let cookie = RequestCookie { name, value };
cookies.push(cookie);
}
let body = match &request.body {
Some(body) => body::eval_body(body, variables, context_dir)?,
None => Body::Binary(vec![]),
};
let mut multipart = vec![];
for multipart_param in request.multipart_form_data() {
let param = multipart::eval_multipart_param(multipart_param, variables, context_dir)?;
multipart.push(param);
}
let implicit_content_type = if !form.is_empty() {
Some("application/x-www-form-urlencoded".to_string())
} else if !multipart.is_empty() {
Some("multipart/form-data".to_string())
} else if let Some(AstBody {
value:
Bytes::Json { .. }
| Bytes::MultilineString(MultilineString {
kind: MultilineStringKind::GraphQl(..),
..
})
| Bytes::MultilineString(MultilineString {
kind: MultilineStringKind::Json(..),
..
}),
..
}) = request.body
{
Some("application/json".to_string())
} else if let Some(AstBody {
value:
Bytes::Xml { .. }
| Bytes::MultilineString(MultilineString {
kind: MultilineStringKind::Xml(..),
..
}),
..
}) = request.body
{
Some("application/xml".to_string())
} else {
None
};
Ok(RequestSpec {
method,
url,
headers,
querystring,
form,
multipart,
cookies,
body,
implicit_content_type,
})
}
fn eval_url(url_template: &Template, variables: &VariableSet) -> Result<Url, RunnerError> {
let url = template::eval_template(url_template, variables)?;
let url = Url::from_str(&url);
match url {
Ok(u) => Ok(u),
Err(UrlError { url, reason }) => {
let source_info = url_template.source_info;
let runner_error_kind = RunnerErrorKind::InvalidUrl {
url,
message: reason,
};
Err(RunnerError::new(source_info, runner_error_kind, false))
}
}
}
pub fn get_cmd_cookie_storage_set(request: &Request) -> Option<String> {
for line_terminator in request.line_terminators.iter() {
if let Some(s) = &line_terminator.comment
&& s.value.contains("@cookie_storage_set:")
{
let index = "#@cookie_storage_set:".to_string().len();
let value = &s.value[index..s.value.len()].to_string().trim().to_string();
return Some(value.to_string());
}
}
None
}
pub fn get_cmd_cookie_storage_clear(request: &Request) -> bool {
for line_terminator in request.line_terminators.iter() {
if let Some(s) = &line_terminator.comment
&& s.value.contains("@cookie_storage_clear")
{
return true;
}
}
false
}
fn eval_method(method: &AstMethod) -> Method {
Method(method.to_string())
}
#[cfg(test)]
mod tests {
use hurl_core::ast::{
Comment, Expr, ExprKind, KeyValue, LineTerminator, Placeholder, Section, SectionValue,
SourceInfo, TemplateElement, Variable, Whitespace,
};
use hurl_core::reader::Pos;
use hurl_core::types::ToSource;
use super::super::error::RunnerErrorKind;
use super::*;
use crate::http;
use crate::runner::Value;
fn whitespace() -> Whitespace {
Whitespace {
value: String::from(" "),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
}
}
fn hello_request() -> Request {
let line_terminator = LineTerminator {
space0: whitespace(),
comment: None,
newline: whitespace(),
};
Request {
line_terminators: vec![],
space0: whitespace(),
method: AstMethod::new("GET"),
space1: whitespace(),
url: Template::new(
None,
vec![
TemplateElement::Placeholder(Placeholder {
space0: whitespace(),
expr: Expr {
kind: ExprKind::Variable(Variable {
name: "base_url".to_string(),
source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 15)),
}),
source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 15)),
},
space1: whitespace(),
}),
TemplateElement::String {
value: "/hello".to_string(),
source: "/hello".to_source(),
},
],
SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
),
line_terminator0: line_terminator,
headers: vec![],
sections: vec![],
body: None,
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
}
}
fn simple_key_value(key: Template, value: Template) -> KeyValue {
let line_terminator = LineTerminator {
space0: whitespace(),
comment: None,
newline: whitespace(),
};
KeyValue {
line_terminators: vec![],
space0: whitespace(),
key,
space1: whitespace(),
space2: whitespace(),
value,
line_terminator0: line_terminator,
}
}
fn query_request() -> Request {
let line_terminator = LineTerminator {
space0: whitespace(),
comment: None,
newline: whitespace(),
};
Request {
line_terminators: vec![],
space0: whitespace(),
method: AstMethod::new("GET"),
space1: whitespace(),
url: Template::new(
None,
vec![TemplateElement::String {
value: "http://localhost:8000/querystring-params".to_string(),
source: "http://localhost:8000/querystring-params".to_source(),
}],
SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
),
line_terminator0: line_terminator.clone(),
headers: vec![],
sections: vec![Section {
line_terminators: vec![],
space0: whitespace(),
line_terminator0: line_terminator,
value: SectionValue::QueryParams(
vec![
simple_key_value(
Template::new(
None,
vec![TemplateElement::String {
value: "param1".to_string(),
source: "param1".to_source(),
}],
SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
),
Template::new(
None,
vec![TemplateElement::Placeholder(Placeholder {
space0: whitespace(),
expr: Expr {
kind: ExprKind::Variable(Variable {
name: "param1".to_string(),
source_info: SourceInfo::new(
Pos::new(1, 7),
Pos::new(1, 15),
),
}),
source_info: SourceInfo::new(
Pos::new(1, 7),
Pos::new(1, 15),
),
},
space1: whitespace(),
})],
SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
),
),
simple_key_value(
Template::new(
None,
vec![TemplateElement::String {
value: "param2".to_string(),
source: "param2".to_source(),
}],
SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
),
Template::new(
None,
vec![TemplateElement::String {
value: "a b".to_string(),
source: "a b".to_source(),
}],
SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
),
),
],
false,
),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
}],
body: None,
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
}
}
#[test]
fn test_error_variable() {
let variables = VariableSet::new();
let error = eval_request(&hello_request(), &variables, &ContextDir::default())
.err()
.unwrap();
assert_eq!(
error.source_info,
SourceInfo::new(Pos::new(1, 7), Pos::new(1, 15))
);
assert_eq!(
error.kind,
RunnerErrorKind::TemplateVariableNotDefined {
name: String::from("base_url")
}
);
}
#[test]
fn test_hello_request() {
let mut variables = VariableSet::new();
variables.insert(
String::from("base_url"),
Value::String(String::from("http://localhost:8000")),
);
let http_request =
eval_request(&hello_request(), &variables, &ContextDir::default()).unwrap();
assert_eq!(http_request, http::hello_http_request());
}
#[test]
fn test_query_request() {
let mut variables = VariableSet::new();
variables.insert(
String::from("param1"),
Value::String(String::from("value1")),
);
let http_request =
eval_request(&query_request(), &variables, &ContextDir::default()).unwrap();
assert_eq!(http_request, http::query_http_request());
}
#[test]
fn clear_cookie_store() {
assert!(!get_cmd_cookie_storage_clear(&hello_request()));
let line_terminator = LineTerminator {
space0: whitespace(),
comment: None,
newline: whitespace(),
};
assert!(get_cmd_cookie_storage_clear(&Request {
line_terminators: vec![LineTerminator {
space0: whitespace(),
comment: Some(Comment {
value: "@cookie_storage_clear".to_string(),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
}),
newline: whitespace(),
}],
space0: whitespace(),
method: AstMethod::new("GET"),
space1: whitespace(),
url: Template::new(
None,
vec![TemplateElement::String {
value: "http:///localhost".to_string(),
source: "http://localhost".to_source(),
},],
SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0))
),
line_terminator0: line_terminator,
headers: vec![],
sections: vec![],
body: None,
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
}));
}
#[test]
fn add_cookie_in_storage() {
assert_eq!(None, get_cmd_cookie_storage_set(&hello_request()));
let line_terminator = LineTerminator {
space0: whitespace(),
comment: None,
newline: whitespace(),
};
assert_eq!(
Some("localhost\tFALSE\t/\tFALSE\t0\tcookie1\tvalueA".to_string()),
get_cmd_cookie_storage_set(&Request {
line_terminators: vec![LineTerminator {
space0: whitespace(),
comment: Some(Comment {
value:
"@cookie_storage_set: localhost\tFALSE\t/\tFALSE\t0\tcookie1\tvalueA"
.to_string(),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
}),
newline: whitespace(),
}],
space0: whitespace(),
method: AstMethod::new("GET"),
space1: whitespace(),
url: Template::new(
None,
vec![TemplateElement::String {
value: "http:///localhost".to_string(),
source: "http://localhost".to_source(),
},],
SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0))
),
line_terminator0: line_terminator,
headers: vec![],
sections: vec![],
body: None,
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
})
);
}
}