use hurl_core::ast::{
JsonListElement, JsonObjectElement, JsonValue, Placeholder, Template, TemplateElement,
};
use hurl_core::parser::{parse_json_boolean, parse_json_null, parse_json_number};
use hurl_core::reader::Reader;
use super::error::{RunnerError, RunnerErrorKind};
use super::expr;
use super::template::eval_template;
use super::variable::VariableSet;
pub fn eval_json_value(
json_value: &JsonValue,
variables: &VariableSet,
keep_whitespace: bool,
) -> Result<String, RunnerError> {
match json_value {
JsonValue::Null => Ok("null".to_string()),
JsonValue::Number(s) => Ok(s.clone()),
JsonValue::String(template) => {
let s = eval_json_template(template, variables)?;
Ok(format!("\"{s}\""))
}
JsonValue::Boolean(v) => Ok(v.to_string()),
JsonValue::List { space0, elements } => {
let mut elems_string = vec![];
for element in elements {
let s = eval_json_list_element(element, variables, keep_whitespace)?;
elems_string.push(s);
}
if keep_whitespace {
Ok(format!("[{}{}]", space0, elems_string.join(",")))
} else {
Ok(format!("[{}]", elems_string.join(",")))
}
}
JsonValue::Object { space0, elements } => {
let mut elems_string = vec![];
for element in elements {
let s = eval_json_object_element(element, variables, keep_whitespace)?;
elems_string.push(s);
}
if keep_whitespace {
Ok(format!("{{{}{}}}", space0, elems_string.join(",")))
} else {
Ok(format!("{{{}}}", elems_string.join(",")))
}
}
JsonValue::Placeholder(Placeholder { expr, .. }) => {
let s = expr::render(expr, variables)?;
let mut reader = Reader::new(s.as_str());
let start = reader.cursor();
if parse_json_number(&mut reader).is_ok() {
return Ok(s);
}
reader.seek(start);
if parse_json_boolean(&mut reader).is_ok() {
return Ok(s);
}
reader.seek(start);
if parse_json_null(&mut reader).is_ok() {
return Ok(s);
}
let kind = RunnerErrorKind::InvalidJson { value: s };
Err(RunnerError::new(expr.source_info, kind, false))
}
}
}
fn eval_json_list_element(
element: &JsonListElement,
variables: &VariableSet,
keep_whitespace: bool,
) -> Result<String, RunnerError> {
let s = eval_json_value(&element.value, variables, keep_whitespace)?;
if keep_whitespace {
Ok(format!("{}{}{}", element.space0, s, element.space1))
} else {
Ok(s)
}
}
fn eval_json_object_element(
element: &JsonObjectElement,
variables: &VariableSet,
keep_whitespace: bool,
) -> Result<String, RunnerError> {
let name = eval_template(&element.name, variables)?;
let value = eval_json_value(&element.value, variables, keep_whitespace)?;
if keep_whitespace {
Ok(format!(
"{}\"{}\"{}:{}{}{}",
element.space0, name, element.space1, element.space2, value, element.space3
))
} else {
Ok(format!("\"{}\":{}", element.name, value))
}
}
pub fn eval_json_template(
template: &Template,
variables: &VariableSet,
) -> Result<String, RunnerError> {
let Template { elements, .. } = template;
{
let mut value = String::new();
for elem in elements {
match eval_json_template_element(elem, variables) {
Ok(v) => value.push_str(v.as_str()),
Err(e) => return Err(e),
}
}
Ok(value)
}
}
fn eval_json_template_element(
template_element: &TemplateElement,
variables: &VariableSet,
) -> Result<String, RunnerError> {
match template_element {
TemplateElement::String { source, .. } => Ok(source.to_string()),
TemplateElement::Placeholder(Placeholder { expr, .. }) => {
let s = expr::render(expr, variables)?;
Ok(encode_json_string(&s))
}
}
}
fn encode_json_string(s: &str) -> String {
s.chars().map(encode_json_char).collect()
}
fn encode_json_char(c: char) -> String {
match c {
'"' => "\\\"".to_string(),
'\\' => "\\\\".to_string(),
'\n' => "\\n".to_string(),
'\r' => "\\r".to_string(),
'\t' => "\\t".to_string(),
c => c.to_string(),
}
}
#[cfg(test)]
mod tests {
use hurl_core::ast::*;
use hurl_core::reader::Pos;
use hurl_core::types::ToSource;
use super::super::error::RunnerErrorKind;
use super::*;
use crate::runner::Value;
pub fn json_hello_world_value() -> JsonValue {
JsonValue::String(Template::new(
Some('"'),
vec![
TemplateElement::String {
value: "Hello ".to_string(),
source: "Hello\\u0020".to_source(),
},
TemplateElement::Placeholder(Placeholder {
space0: Whitespace {
value: String::new(),
source_info: SourceInfo::new(Pos::new(1, 15), Pos::new(1, 15)),
},
expr: Expr {
kind: ExprKind::Variable(Variable {
name: "name".to_string(),
source_info: SourceInfo::new(Pos::new(1, 15), Pos::new(1, 19)),
}),
source_info: SourceInfo::new(Pos::new(1, 15), Pos::new(1, 19)),
},
space1: Whitespace {
value: String::new(),
source_info: SourceInfo::new(Pos::new(1, 19), Pos::new(1, 19)),
},
}),
TemplateElement::String {
value: "!".to_string(),
source: "!".to_source(),
},
],
SourceInfo::new(Pos::new(1, 2), Pos::new(1, 22)),
))
}
pub fn json_person_value() -> JsonValue {
JsonValue::Object {
space0: "\n ".to_string(),
elements: vec![JsonObjectElement {
space0: String::new(),
name: Template::new(
None,
vec![TemplateElement::String {
value: "firstName".to_string(),
source: "firstName".to_source(),
}],
SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
),
space1: String::new(),
space2: " ".to_string(),
value: JsonValue::String(Template::new(
None,
vec![TemplateElement::String {
value: "John".to_string(),
source: "John".to_source(),
}],
SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
)),
space3: "\n".to_string(),
}],
}
}
#[test]
fn test_scalar_value() {
let mut variables = VariableSet::new();
variables.insert("name".to_string(), Value::String("Bob".to_string()));
assert_eq!(
eval_json_value(&JsonValue::Null, &variables, true).unwrap(),
"null".to_string()
);
assert_eq!(
eval_json_value(&JsonValue::Number("3.14".to_string()), &variables, true).unwrap(),
"3.14".to_string()
);
assert_eq!(
eval_json_value(&JsonValue::Boolean(false), &variables, true).unwrap(),
"false".to_string()
);
assert_eq!(
eval_json_value(&json_hello_world_value(), &variables, true).unwrap(),
"\"Hello\\u0020Bob!\"".to_string()
);
}
#[test]
fn test_error() {
let variables = VariableSet::new();
let error = eval_json_value(&json_hello_world_value(), &variables, true)
.err()
.unwrap();
assert_eq!(
error.source_info,
SourceInfo::new(Pos::new(1, 15), Pos::new(1, 19))
);
assert_eq!(
error.kind,
RunnerErrorKind::TemplateVariableNotDefined {
name: "name".to_string()
}
);
}
#[test]
fn test_list_value() {
let mut variables = VariableSet::new();
variables.insert("name".to_string(), Value::String("Bob".to_string()));
assert_eq!(
eval_json_value(
&JsonValue::List {
space0: String::new(),
elements: vec![],
},
&variables,
true,
)
.unwrap(),
"[]".to_string()
);
assert_eq!(
eval_json_value(
&JsonValue::List {
space0: String::new(),
elements: vec![
JsonListElement {
space0: String::new(),
value: JsonValue::Number("1".to_string()),
space1: String::new(),
},
JsonListElement {
space0: " ".to_string(),
value: JsonValue::Number("-2".to_string()),
space1: String::new(),
},
JsonListElement {
space0: " ".to_string(),
value: JsonValue::Number("3.0".to_string()),
space1: String::new(),
},
],
},
&variables,
true
)
.unwrap(),
"[1, -2, 3.0]".to_string()
);
let template = Template::new(
Some('"'),
vec![TemplateElement::String {
value: "Hi".to_string(),
source: "Hi".to_source(),
}],
SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
);
assert_eq!(
eval_json_value(
&JsonValue::List {
space0: String::new(),
elements: vec![
JsonListElement {
space0: String::new(),
value: JsonValue::String(template),
space1: String::new(),
},
JsonListElement {
space0: " ".to_string(),
value: json_hello_world_value(),
space1: String::new(),
},
],
},
&variables,
true
)
.unwrap(),
"[\"Hi\", \"Hello\\u0020Bob!\"]".to_string()
);
}
#[test]
fn test_object_value() {
let variables = VariableSet::new();
assert_eq!(
eval_json_value(
&JsonValue::Object {
space0: String::new(),
elements: vec![],
},
&variables,
true
)
.unwrap(),
"{}".to_string()
);
assert_eq!(
eval_json_value(&json_person_value(), &variables, true).unwrap(),
r#"{
"firstName": "John"
}"#
.to_string()
);
}
#[test]
fn test_escape_sequence() {
let variables = VariableSet::new();
assert_eq!(
eval_json_value(
&JsonValue::String(Template::new(
None,
vec![TemplateElement::String {
value: "\n".to_string(),
source: "\\n".to_source(),
}],
SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1))
)),
&variables,
true
)
.unwrap(),
"\"\\n\"".to_string()
);
}
#[test]
fn test_eval_json_template() {
let variables = VariableSet::new();
assert_eq!(
eval_json_template(
&Template::new(
None,
vec![TemplateElement::String {
value: "\n".to_string(),
source: "\\n".to_source(),
}],
SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
),
&variables,
)
.unwrap(),
"\\n".to_string()
);
let mut variables = VariableSet::new();
variables.insert("quote".to_string(), Value::String("\"".to_string()));
assert_eq!(
eval_json_template(
&Template::new(
Some('"'),
vec![
TemplateElement::String {
value: "Hello ".to_string(),
source: "Hello ".to_source(),
},
TemplateElement::Placeholder(Placeholder {
space0: whitespace(),
expr: Expr {
kind: ExprKind::Variable(Variable {
name: "quote".to_string(),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
}),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
},
space1: whitespace(),
}),
],
SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
),
&variables,
)
.unwrap(),
"Hello \\\"".to_string()
);
}
fn whitespace() -> Whitespace {
Whitespace {
value: String::new(),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
}
}
#[test]
fn test_encode_json_string() {
assert_eq!(encode_json_string("a"), "a");
assert_eq!(encode_json_string("\""), "\\\"");
assert_eq!(encode_json_string("\\"), "\\\\");
}
#[test]
fn test_not_preserving_spaces() {
let variables = VariableSet::new();
assert_eq!(
eval_json_value(&json_person_value(), &variables, false).unwrap(),
r#"{"firstName":"John"}"#.to_string()
);
}
}