Skip to content

Commit

Permalink
Fix GraphQL query with variables to HTTP body request
Browse files Browse the repository at this point in the history
  • Loading branch information
jcamiel committed Jan 29, 2023
1 parent 9e8ef9a commit ba1c48c
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 36 deletions.
4 changes: 2 additions & 2 deletions integration/tests_ok/graphql.curl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
curl -H 'Content-Type: application/json' --data '{"query":"{\n allFilms {\n films {\n title\n director\n releaseDate\n }\n }\n}"}' 'http://localhost:8000/graphql'
curl -H 'Content-Type: application/json' --data '{"query":"{\n allFilms {\n films {\n title\n director\n releaseDate\n }\n }\n}"}' 'http://localhost:8000/graphql'
curl -H 'Content-Type: application/json' --data '{"query":"query Query {\n allFilms {\n films {\n title\n director\n releaseDate\n }\n }\n}"}' 'http://localhost:8000/graphql'
curl -H 'Content-Type: application/json' --data '{"query":"query Person($id: ID!) {\n person(id: $id) {\n name\n }\n}","variables":"{\n \"id\": \"cGVvcGxlOjQ=\"\n}"}' 'http://localhost:8000/graphql'
curl -H 'Content-Type: application/json' --data '{"query":"query Person($id: ID!) {\n person(id: $id) {\n name\n }\n}","variables":"{\n \"id\": \"cGVvcGxlOjQ=\"\n}"}' 'http://localhost:8000/graphql'
curl -H 'Content-Type: application/json' --data '{"query":"query Person($id: ID!) {\n person(id: $id) {\n name\n }\n}","variables":{"id":"cGVvcGxlOjQ="}}' 'http://localhost:8000/graphql'
curl -H 'Content-Type: application/json' --data '{"query":"query Person($id: ID!) {\n person(id: $id) {\n name\n }\n}","variables":{"id":"cGVvcGxlOjQ="}}' 'http://localhost:8000/graphql'
2 changes: 1 addition & 1 deletion integration/tests_ok/graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def graphql():
responses = {
r'{"query":"{\n allFilms {\n films {\n title\n director\n releaseDate\n }\n }\n}"}': r'{"data":{"allFilms":{"films":[{"title":"A New Hope","director":"George Lucas","releaseDate":"1977-05-25"},{"title":"The Empire Strikes Back","director":"Irvin Kershner","releaseDate":"1980-05-17"},{"title":"Return of the Jedi","director":"Richard Marquand","releaseDate":"1983-05-25"},{"title":"The Phantom Menace","director":"George Lucas","releaseDate":"1999-05-19"},{"title":"Attack of the Clones","director":"George Lucas","releaseDate":"2002-05-16"},{"title":"Revenge of the Sith","director":"George Lucas","releaseDate":"2005-05-19"}]}}}',
r'{"query":"query Query {\n allFilms {\n films {\n title\n director\n releaseDate\n }\n }\n}"}': r'{"data":{"allFilms":{"films":[{"title":"A New Hope","director":"George Lucas","releaseDate":"1977-05-25"},{"title":"The Empire Strikes Back","director":"Irvin Kershner","releaseDate":"1980-05-17"},{"title":"Return of the Jedi","director":"Richard Marquand","releaseDate":"1983-05-25"},{"title":"The Phantom Menace","director":"George Lucas","releaseDate":"1999-05-19"},{"title":"Attack of the Clones","director":"George Lucas","releaseDate":"2002-05-16"},{"title":"Revenge of the Sith","director":"George Lucas","releaseDate":"2005-05-19"}]}}}',
r'{"query":"query Person($id: ID!) {\n person(id: $id) {\n name\n }\n}","variables":"{\n \"id\": \"cGVvcGxlOjQ=\"\n}"}': r'{"data":{"person":{"name":"Darth Vader"}}}',
r'{"query":"query Person($id: ID!) {\n person(id: $id) {\n name\n }\n}","variables":{"id":"cGVvcGxlOjQ="}}': r'{"data":{"person":{"name":"Darth Vader"}}}',
}
data = responses[body_in]
resp = make_response(data)
Expand Down
2 changes: 1 addition & 1 deletion packages/hurl/src/runner/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ pub fn eval_bytes(
}
Bytes::Xml(value) => Ok(http::Body::Text(value.clone())),
Bytes::Json(value) => {
let value = eval_json_value(value, variables)?;
let value = eval_json_value(value, variables, true)?;
Ok(http::Body::Text(value))
}
Bytes::Base64(Base64 { value, .. }) => Ok(http::Body::Binary(value.clone())),
Expand Down
89 changes: 62 additions & 27 deletions packages/hurl/src/runner/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ use super::core::{Error, RunnerError};
use super::value::Value;
use crate::runner::template::eval_expression;

/// Evaluates a JSON value to a string given a set of `variables`.
/// If `keep_whitespace` is true, whitespace is preserved from the JSonValue, otherwise
/// it is trimmed.
pub fn eval_json_value(
json_value: &JsonValue,
variables: &HashMap<String, Value>,
keep_whitespace: bool,
) -> Result<String, Error> {
match json_value {
JsonValue::Null {} => Ok("null".to_string()),
Expand All @@ -39,18 +43,26 @@ pub fn eval_json_value(
JsonValue::List { space0, elements } => {
let mut elems_string = vec![];
for element in elements {
let s = eval_json_list_element(element, variables)?;
let s = eval_json_list_element(element, variables, keep_whitespace)?;
elems_string.push(s);
}
Ok(format!("[{}{}]", space0, elems_string.join(",")))
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)?;
let s = eval_json_object_element(element, variables, keep_whitespace)?;
elems_string.push(s);
}
Ok(format!("{{{}{}}}", space0, elems_string.join(",")))
if keep_whitespace {
Ok(format!("{{{}{}}}", space0, elems_string.join(",")))
} else {
Ok(format!("{{{}}}", elems_string.join(",")))
}
}
JsonValue::Expression(exp) => {
let s = eval_expression(exp, variables)?;
Expand Down Expand Up @@ -79,38 +91,47 @@ pub fn eval_json_value(
}
}

pub fn eval_json_list_element(
/// Evaluates a JSON list to a string given a set of `variables`.
/// If `keep_whitespace` is true, whitespace is preserved from the JSonValue, otherwise
/// it is trimmed.
fn eval_json_list_element(
element: &JsonListElement,
variables: &HashMap<String, Value>,
keep_whitespace: bool,
) -> Result<String, Error> {
let s = eval_json_value(&element.value, variables)?;
Ok(format!("{}{}{}", element.space0, s, element.space1))
let s = eval_json_value(&element.value, variables, keep_whitespace)?;
if keep_whitespace {
Ok(format!("{}{}{}", element.space0, s, element.space1))
} else {
Ok(s)
}
}

pub fn eval_json_object_element(
/// Renders a JSON object to a string given a set of `variables`.
/// If `keep_whitespace` is true, whitespace is preserved from the JSonValue, otherwise
/// it is trimmed.
fn eval_json_object_element(
element: &JsonObjectElement,
variables: &HashMap<String, Value>,
keep_whitespace: bool,
) -> Result<String, Error> {
let value = eval_json_value(&element.value, variables)?;
Ok(format!(
"{}\"{}\"{}:{}{}{}",
element.space0, element.name, element.space1, element.space2, value, element.space3
))
let value = eval_json_value(&element.value, variables, keep_whitespace)?;
if keep_whitespace {
Ok(format!(
"{}\"{}\"{}:{}{}{}",
element.space0, element.name, element.space1, element.space2, value, element.space3
))
} else {
Ok(format!("\"{}\":{}", element.name, value))
}
}

/// Eval a JSON template to a valid JSON string
/// The variable are replaced by their value and encoded into JSON
///
/// # Arguments
///
/// * `template` - An Hurl Template
/// * `variables` - A map of input variables
/// Evaluates a JSON template to a string given a set of `variables`
///
/// # Example
///
/// The template "Hello {{quote}}" with variable quote="
/// will be evaluated to the JSON String "Hello \""
///
pub fn eval_json_template(
template: &Template,
variables: &HashMap<String, Value>,
Expand Down Expand Up @@ -227,27 +248,27 @@ mod tests {
let mut variables = HashMap::new();
variables.insert("name".to_string(), Value::String("Bob".to_string()));
assert_eq!(
eval_json_value(&JsonValue::Null {}, &variables).unwrap(),
eval_json_value(&JsonValue::Null {}, &variables, true).unwrap(),
"null".to_string()
);
assert_eq!(
eval_json_value(&JsonValue::Number("3.14".to_string()), &variables).unwrap(),
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).unwrap(),
eval_json_value(&JsonValue::Boolean(false), &variables, true).unwrap(),
"false".to_string()
);
assert_eq!(
eval_json_value(&json_hello_world_value(), &variables).unwrap(),
eval_json_value(&json_hello_world_value(), &variables, true).unwrap(),
"\"Hello\\u0020Bob!\"".to_string()
);
}

#[test]
fn test_error() {
let variables = HashMap::new();
let error = eval_json_value(&json_hello_world_value(), &variables)
let error = eval_json_value(&json_hello_world_value(), &variables, true)
.err()
.unwrap();
assert_eq!(error.source_info, SourceInfo::new(1, 15, 1, 19));
Expand All @@ -270,6 +291,7 @@ mod tests {
elements: vec![],
},
&variables,
true,
)
.unwrap(),
"[]".to_string()
Expand Down Expand Up @@ -298,6 +320,7 @@ mod tests {
],
},
&variables,
true
)
.unwrap(),
"[1, -2, 3.0]".to_string()
Expand Down Expand Up @@ -329,6 +352,7 @@ mod tests {
],
},
&variables,
true
)
.unwrap(),
"[\"Hi\", \"Hello\\u0020Bob!\"]".to_string()
Expand All @@ -345,12 +369,13 @@ mod tests {
elements: vec![],
},
&variables,
true
)
.unwrap(),
"{}".to_string()
);
assert_eq!(
eval_json_value(&json_person_value(), &variables).unwrap(),
eval_json_value(&json_person_value(), &variables, true).unwrap(),
r#"{
"firstName": "John"
}"#
Expand All @@ -372,6 +397,7 @@ mod tests {
source_info: SourceInfo::new(1, 1, 1, 1),
}),
&variables,
true
)
.unwrap(),
"\"\\n\"".to_string()
Expand Down Expand Up @@ -439,4 +465,13 @@ mod tests {
assert_eq!(encode_json_string("\""), "\\\"");
assert_eq!(encode_json_string("\\"), "\\\\");
}

#[test]
fn test_not_preserving_spaces() {
let variables = HashMap::new();
assert_eq!(
eval_json_value(&json_person_value(), &variables, true).unwrap(),
r#"{"firstName":"John"}"#.to_string()
);
}
}
142 changes: 138 additions & 4 deletions packages/hurl/src/runner/multiline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use hurl_core::ast::{MultilineString, Text};
use serde_json::json;
use std::collections::HashMap;

/// Renders to string a multiline body, given a set of variables.
pub fn eval_multiline(
multiline: &MultilineString,
variables: &HashMap<String, Value>,
Expand All @@ -40,13 +41,146 @@ pub fn eval_multiline(
MultilineString::GraphQl(graphql) => {
let query = eval_template(&graphql.value, variables)?;
let body = match &graphql.variables {
None => json!({ "query": query.trim()}),
None => json!({ "query": query.trim()}).to_string(),
Some(vars) => {
let s = eval_json_value(&vars.value, variables)?;
json!({ "query": query.trim(), "variables": s})
let s = eval_json_value(&vars.value, variables, false)?;
let query = json!(query.trim());
format!(r#"{{"query":{query},"variables":{s}}}"#)
}
};
Ok(body.to_string())
Ok(body)
}
}
}

#[cfg(test)]
mod tests {
use crate::runner::multiline::eval_multiline;
use hurl_core::ast::JsonValue;
use hurl_core::ast::{
GraphQl, GraphQlVariables, JsonObjectElement, MultilineString, SourceInfo, Template,
TemplateElement, Whitespace,
};
use std::collections::HashMap;

fn whitespace() -> Whitespace {
Whitespace {
value: String::from(" "),
source_info: SourceInfo::new(0, 0, 0, 0),
}
}

fn newline() -> Whitespace {
Whitespace {
value: String::from("\n"),
source_info: SourceInfo::new(0, 0, 0, 0),
}
}

fn empty_source_info() -> SourceInfo {
SourceInfo::new(0, 0, 0, 0)
}

#[test]
fn eval_graphql_multiline_simple() {
let query = r#"{
human(id: "1000") {
name
height(unit: FOOT)
}
}"#;
let variables = HashMap::new();
let multiline = MultilineString::GraphQl(GraphQl {
space: whitespace(),
newline: newline(),
value: Template {
delimiter: None,
elements: vec![TemplateElement::String {
value: query.to_string(),
encoded: query.to_string(),
}],
source_info: empty_source_info(),
},
variables: None,
});
let body = eval_multiline(&multiline, &variables).unwrap();
assert_eq!(
body,
r#"{"query":"{\n human(id: \"1000\") {\n name\n height(unit: FOOT)\n }\n}"}"#
.to_string()
)
}

#[test]
fn eval_graphql_multiline_with_graphql_variables() {
let query = r#"{
human(id: "1000") {
name
height(unit: FOOT)
}
}"#;
let hurl_variables = HashMap::new();
let graphql_variables = GraphQlVariables {
space: whitespace(),
value: JsonValue::Object {
space0: "".to_string(),
elements: vec![
JsonObjectElement {
space0: "".to_string(),
name: Template {
delimiter: Some('"'),
elements: vec![TemplateElement::String {
value: "episode".to_string(),
encoded: "episode".to_string(),
}],
source_info: empty_source_info(),
},
space1: "".to_string(),
space2: "".to_string(),
value: JsonValue::String(Template {
delimiter: Some('"'),
elements: vec![TemplateElement::String {
value: "JEDI".to_string(),
encoded: "JEDI".to_string(),
}],
source_info: empty_source_info(),
}),
space3: "".to_string(),
},
JsonObjectElement {
space0: "".to_string(),
name: Template {
delimiter: Some('"'),
elements: vec![TemplateElement::String {
value: "withFriends".to_string(),
encoded: "withFriends".to_string(),
}],
source_info: empty_source_info(),
},
space1: "".to_string(),
space2: "".to_string(),
value: JsonValue::Boolean(false),
space3: "".to_string(),
},
],
},
whitespace: whitespace(),
};
let multiline = MultilineString::GraphQl(GraphQl {
space: whitespace(),
newline: newline(),
value: Template {
delimiter: None,
elements: vec![TemplateElement::String {
value: query.to_string(),
encoded: query.to_string(),
}],
source_info: empty_source_info(),
},
variables: Some(graphql_variables),
});

let body = eval_multiline(&multiline, &hurl_variables).unwrap();
assert_eq!(body, r#"{"query":"{\n human(id: \"1000\") {\n name\n height(unit: FOOT)\n }\n}","variables":{"episode":"JEDI","withFriends":false}}"#.to_string())
}
}
2 changes: 1 addition & 1 deletion packages/hurl/src/runner/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ fn eval_implicit_body_asserts(
) -> AssertResult {
match &spec_body.value {
Bytes::Json(value) => {
let expected = match eval_json_value(value, variables) {
let expected = match eval_json_value(value, variables, true) {
Ok(s) => Ok(Value::String(s)),
Err(e) => Err(e),
};
Expand Down

0 comments on commit ba1c48c

Please sign in to comment.