Merge pull request #117 from Orange-OpenSource/feature/template-in-json

Add template for json null/number/boolean
This commit is contained in:
Fabrice Reix 2021-01-09 18:43:44 +01:00 committed by GitHub
commit a52ea2f570
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 138 additions and 41 deletions

View File

@ -0,0 +1,7 @@
error: Invalid Json
--> tests/error_body_json.hurl:3:18
|
3 | "success": {{success}}
| ^^^^^^^ actual value is <invalid>
|

View File

@ -0,0 +1 @@
3

View File

@ -0,0 +1 @@
<div class="hurl-file"><div class="hurl-entry"><div class="request"><span class="line"><span class="method">POST</span> <span class="url">unused</span></span></div><span class="line">{<span class="line"> "success": {{success}}</span><span class="line">}</span></div></div>

View File

@ -0,0 +1,4 @@
POST unused
{
"success": {{success}}
}

View File

@ -0,0 +1 @@
{"entries":[{"request":{"method":"POST","url":"unused","body":{"type":"json","value":{"success":"{{success}}"}}}}]}

View File

@ -0,0 +1 @@
--variable success=invalid

View File

@ -1 +1 @@
<div class="hurl-file"><div class="hurl-entry"><div class="request"><span class="line"><span class="method">POST</span> <span class="url">http://localhost:8000/post-json</span></span></div><span class="line"></span><span class="line">{<span class="line"> "name": "Bob",</span><span class="line"> "password": "secret"</span><span class="line">}</span><div class="response"><span class="line"></span><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">POST</span> <span class="url">http://localhost:8000/post-json-array</span></span></div><span class="line">[1,2,3]<div class="response"><span class="line"></span><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">POST</span> <span class="url">http://localhost:8000/post-json-string</span></span></div><span class="line">"Hello"<div class="response"><span class="line"></span><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">POST</span> <span class="url">http://localhost:8000/post-json-number</span></span></div><span class="line">100<div class="response"><span class="line"></span><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">POST</span> <span class="url">http://localhost:8000/post-json-numbers</span></span></div><span class="line">{<span class="line"> "natural": 100,</span><span class="line"> "negative": -1,</span><span class="line"> "float": "3.333333333333333",</span><span class="line"> "exponent": 100e100</span><span class="line">}</span><div class="response"><span class="line"></span><span class="line"></span><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">POST</span> <span class="url">http://localhost:8000/post-json-boolean</span></span></div><span class="line">true<div class="response"><span class="line"></span><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="comment">#</span></span><span class="line"><span class="comment"># Use variable in your input json</span></span><span class="line"><span class="comment">#</span></span><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/get-name</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">[Captures]</span></span><span class="line">name<span>:</span> <span class="query-type">status</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">POST</span> <span class="url">http://localhost:8000/post-json</span></span></div><span class="line"></span><span class="line">{<span class="line"> "name": "{{name}}",</span><span class="line"> "password": "secret"</span><span class="line">}</span></div></div>
<div class="hurl-file"><div class="hurl-entry"><div class="request"><span class="line"><span class="method">POST</span> <span class="url">http://localhost:8000/post-json</span></span></div><span class="line"></span><span class="line">{<span class="line"> "name": "Bob",</span><span class="line"> "password": "secret",</span><span class="line"> "age": 30,</span><span class="line"> "strict": true</span><span class="line">}</span><div class="response"><span class="line"></span><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">POST</span> <span class="url">http://localhost:8000/post-json-array</span></span></div><span class="line">[1,2,3]<div class="response"><span class="line"></span><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">POST</span> <span class="url">http://localhost:8000/post-json-string</span></span></div><span class="line">"Hello"<div class="response"><span class="line"></span><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">POST</span> <span class="url">http://localhost:8000/post-json-number</span></span></div><span class="line">100<div class="response"><span class="line"></span><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">POST</span> <span class="url">http://localhost:8000/post-json-numbers</span></span></div><span class="line">{<span class="line"> "natural": 100,</span><span class="line"> "negative": -1,</span><span class="line"> "float": "3.333333333333333",</span><span class="line"> "exponent": 100e100</span><span class="line">}</span><div class="response"><span class="line"></span><span class="line"></span><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">POST</span> <span class="url">http://localhost:8000/post-json-boolean</span></span></div><span class="line">true<div class="response"><span class="line"></span><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="comment">#</span></span><span class="line"><span class="comment"># Use variable in your input json</span></span><span class="line"><span class="comment">#</span></span><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/get-name</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">[Captures]</span></span><span class="line">name<span>:</span> <span class="query-type">status</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">POST</span> <span class="url">http://localhost:8000/post-json</span></span></div><span class="line"></span><span class="line">{<span class="line"> "name": "{{name}}",</span><span class="line"> "password": "secret",</span><span class="line"> "age": {{age}},</span><span class="line"> "strict": {{strict}}</span><span class="line">}</span></div></div>

View File

@ -2,7 +2,9 @@ POST http://localhost:8000/post-json
{
"name": "Bob",
"password": "secret"
"password": "secret",
"age": 30,
"strict": true
}
HTTP/1.0 200
@ -50,5 +52,7 @@ POST http://localhost:8000/post-json
{
"name": "{{name}}",
"password": "secret"
}
"password": "secret",
"age": {{age}},
"strict": {{strict}}
}

View File

@ -1 +1 @@
{"entries":[{"request":{"method":"POST","url":"http://localhost:8000/post-json","body":{"type":"json","value":{"name":"Bob","password":"secret"}}},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"POST","url":"http://localhost:8000/post-json-array","body":{"type":"json","value":[1,2,3]}},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"POST","url":"http://localhost:8000/post-json-string","body":{"type":"json","value":"Hello"}},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"POST","url":"http://localhost:8000/post-json-number","body":{"type":"json","value":100}},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"POST","url":"http://localhost:8000/post-json-numbers","body":{"type":"json","value":{"natural":100,"negative":-1,"float":"3.333333333333333","exponent":100e100}}},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"POST","url":"http://localhost:8000/post-json-boolean","body":{"type":"json","value":true}},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/get-name"},"response":{"version":"HTTP/1.0","status":200,"captures":[{"name":"name","query":{"type":"body"}}]}},{"request":{"method":"POST","url":"http://localhost:8000/post-json","body":{"type":"json","value":{"name":"{{name}}","password":"secret"}}}}]}
{"entries":[{"request":{"method":"POST","url":"http://localhost:8000/post-json","body":{"type":"json","value":{"name":"Bob","password":"secret","age":30,"strict":true}}},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"POST","url":"http://localhost:8000/post-json-array","body":{"type":"json","value":[1,2,3]}},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"POST","url":"http://localhost:8000/post-json-string","body":{"type":"json","value":"Hello"}},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"POST","url":"http://localhost:8000/post-json-number","body":{"type":"json","value":100}},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"POST","url":"http://localhost:8000/post-json-numbers","body":{"type":"json","value":{"natural":100,"negative":-1,"float":"3.333333333333333","exponent":100e100}}},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"POST","url":"http://localhost:8000/post-json-boolean","body":{"type":"json","value":true}},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/get-name"},"response":{"version":"HTTP/1.0","status":200,"captures":[{"name":"name","query":{"type":"body"}}]}},{"request":{"method":"POST","url":"http://localhost:8000/post-json","body":{"type":"json","value":{"name":"{{name}}","password":"secret","age":"{{age}}","strict":"{{strict}}"}}}}]}

View File

@ -0,0 +1 @@
--variable age=30 --variable strict=true

View File

@ -6,9 +6,12 @@ from tests import app
def post_json():
assert request.headers['Content-Type'] == 'application/json'
s = request.data.decode("utf-8")
print(s)
assert s == '''{
"name": "Bob",
"password": "secret"
"password": "secret",
"age": 30,
"strict": true
}'''
return ''

View File

@ -116,6 +116,9 @@ pub enum RunnerError {
VariableNotDefined {
name: String,
},
InvalidJson {
value: String,
},
InvalidURL(String),
HttpConnection {

View File

@ -48,6 +48,7 @@ impl Error for runner::Error {
RunnerError::NoQueryResult { .. } => "No query result".to_string(),
RunnerError::UnsupportedContentEncoding(..) => "Decompression Error".to_string(),
RunnerError::CouldNotUncompressResponse(..) => "Decompression Error".to_string(),
RunnerError::InvalidJson { .. } => "Invalid Json".to_string(),
}
}
@ -136,6 +137,9 @@ impl Error for runner::Error {
RunnerError::CouldNotUncompressResponse(algorithm) => {
format!("Could not uncompress response with {}", algorithm)
}
RunnerError::InvalidJson { value } => {
format!("actual value is <{}>", value)
}
}
}
}

View File

@ -18,10 +18,12 @@
use std::collections::HashMap;
use hurl_core::ast::{JsonListElement, JsonObjectElement, JsonValue};
use hurl_core::parser::{parse_json_boolean, parse_json_null, parse_json_number, Reader};
use super::core::Error;
use super::core::{Error, RunnerError};
use super::template::eval_template;
use super::value::Value;
use crate::runner::template::eval_expression;
pub fn eval_json_value(
json_value: JsonValue,
@ -51,6 +53,30 @@ pub fn eval_json_value(
}
Ok(format!("{{{}{}}}", space0, elems_string.join(",")))
}
JsonValue::Expression(exp) => {
let s = eval_expression(exp.clone(), variables)?;
// The String can only be null, a bool, a number
// It will be easier when your variables value have a type
let mut reader = Reader::init(s.as_str());
let start = reader.state.clone();
if parse_json_number(&mut reader).is_ok() {
return Ok(s);
}
reader.state = start.clone();
if parse_json_boolean(&mut reader).is_ok() {
return Ok(s);
}
reader.state = start;
if parse_json_null(&mut reader).is_ok() {
return Ok(s);
}
Err(Error {
source_info: exp.variable.source_info,
inner: RunnerError::InvalidJson { value: s },
assert: false,
})
}
}
}

View File

@ -45,29 +45,32 @@ fn eval_template_element(
) -> Result<String, Error> {
match template_element {
TemplateElement::String { value, .. } => Ok(value),
TemplateElement::Expression(Expr {
variable: Variable { name, source_info },
..
}) => match variables.get(&name as &str) {
Some(value) => {
if value.is_renderable() {
Ok(value.clone().to_string())
} else {
Err(Error {
source_info,
inner: RunnerError::UnrenderableVariable {
value: value.to_string(),
},
assert: false,
})
}
TemplateElement::Expression(expr) => eval_expression(expr, variables),
}
}
pub fn eval_expression(expr: Expr, variables: &HashMap<String, Value>) -> Result<String, Error> {
let source_info = expr.variable.source_info;
let name = expr.variable.name;
match variables.get(name.as_str()) {
Some(value) => {
if value.is_renderable() {
Ok(value.clone().to_string())
} else {
Err(Error {
source_info,
inner: RunnerError::UnrenderableVariable {
value: value.to_string(),
},
assert: false,
})
}
_ => Err(Error {
source_info,
inner: RunnerError::TemplateVariableNotDefined { name },
assert: false,
}),
},
}
_ => Err(Error {
source_info,
inner: RunnerError::TemplateVariableNotDefined { name },
assert: false,
}),
}
}

View File

@ -37,10 +37,7 @@ pub fn log_runner_error(error: &runner::Error, _warning: bool) {
// can be used for debugging
#[test]
fn test_hurl_file() {
//let filename = "integration/tests/post_json.hurl";
//let filename = "integration/tests/error_assert_match_utf8.hurl";
let filename = "../../integration/tests/error_template_variable_not_renderable.hurl";
//let filename = "/mnt/secure/repos/work/myshop/integration/src/main/hurl-generated/pcm/pcm-jdd-open-up-150go.hurl";
let filename = "../../integration/tests/post_json.hurl";
let content = std::fs::read_to_string(filename).expect("Something went wrong reading the file");
let hurl_file = parser::parse_hurl_file(content.as_str()).unwrap();
let variables = HashMap::new();

View File

@ -16,6 +16,7 @@
*
*/
use super::core::Template;
use crate::ast::Expr;
///
/// This the AST for the JSON used within hurl
@ -26,6 +27,7 @@ use super::core::Template;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Value {
Expression(Expr),
Number(String),
String(Template),
Boolean(bool),
@ -49,6 +51,7 @@ impl Value {
Value::List { .. } => "list".to_string(),
Value::Object { .. } => "object".to_string(),
Value::String(_) => "string".to_string(),
Value::Expression(_) => "expression".to_string(),
}
}
}

View File

@ -23,6 +23,7 @@ use super::primitives::*;
use super::reader::*;
use super::template::*;
use super::ParseResult;
use crate::parser::expr;
pub fn parse(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
choice(
@ -31,6 +32,7 @@ pub fn parse(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
boolean_value,
string_value,
number_value,
expression_value,
list_value,
object_value,
],
@ -38,12 +40,12 @@ pub fn parse(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
)
}
fn null_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
pub fn null_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
try_literal("null", reader)?;
Ok(JsonValue::Null {})
}
fn boolean_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
pub fn boolean_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
let value = boolean(reader)?;
Ok(JsonValue::Boolean(value))
}
@ -161,8 +163,8 @@ fn hex_value(reader: &mut Reader) -> ParseResult<'static, u32> {
Ok(value)
}
fn number_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
let start = reader.state.pos.clone();
pub fn number_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
let start = reader.state.clone();
let sign = match try_literal("-", reader) {
Err(_) => "".to_string(),
@ -174,7 +176,7 @@ fn number_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
let digits = reader.read_while(|c| c.is_ascii_digit());
if digits.is_empty() {
return Err(error::Error {
pos: start,
pos: start.pos,
recoverable: true,
inner: error::ParseError::Expecting {
value: "number".to_string(),
@ -226,6 +228,11 @@ fn number_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
)))
}
fn expression_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
let exp = expr::parse(reader)?;
Ok(JsonValue::Expression(exp))
}
fn list_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
try_literal("[", reader)?;
let space0 = whitespace(reader);
@ -695,6 +702,29 @@ mod tests {
assert_eq!(error.recoverable, false);
}
#[test]
fn test_expression_value() {
let mut reader = Reader::init("{{n}}");
assert_eq!(
expression_value(&mut reader).unwrap(),
JsonValue::Expression(Expr {
space0: Whitespace {
value: "".to_string(),
source_info: SourceInfo::init(1, 3, 1, 3)
},
variable: Variable {
name: "n".to_string(),
source_info: SourceInfo::init(1, 3, 1, 4)
},
space1: Whitespace {
value: "".to_string(),
source_info: SourceInfo::init(1, 4, 1, 4)
}
})
);
assert_eq!(reader.state.cursor, 5);
}
#[test]
fn test_list_value() {
let mut reader = Reader::init("[]");

View File

@ -26,6 +26,9 @@ pub fn parse_hurl_file(s: &str) -> ParseResult<'static, HurlFile> {
}
pub use self::error::{Error, ParseError};
pub use self::json::boolean_value as parse_json_boolean;
pub use self::json::null_value as parse_json_null;
pub use self::json::number_value as parse_json_number;
pub use self::json::parse as parse_json;
pub use self::reader::Reader;
pub use self::template::templatize;

View File

@ -459,7 +459,7 @@ impl ToJson for hurl_core::ast::JsonValue {
fn to_json(&self) -> JValue {
match self {
JsonValue::Null {} => JValue::Null {},
JsonValue::Number(s) => JValue::Number(s.clone()),
JsonValue::Number(s) => JValue::Number(s.to_string()),
JsonValue::String(s) => JValue::String(s.to_string()),
JsonValue::Boolean(v) => JValue::Boolean(*v),
JsonValue::List { elements, .. } => {
@ -471,6 +471,7 @@ impl ToJson for hurl_core::ast::JsonValue {
.map(|elem| (elem.name.clone(), elem.value.to_json()))
.collect(),
),
JsonValue::Expression(exp) => JValue::String(format!("{{{{{}}}}}", exp.to_string())),
}
}
}

View File

@ -791,10 +791,10 @@ impl Tokenizable for JsonValue {
//tokens.push(Token::CodeDelimiter("\"".to_string()));
}
JsonValue::Number(value) => {
tokens.push(Token::Number(value.clone()));
tokens.push(Token::Number(value.to_string()));
}
JsonValue::Boolean(value) => {
tokens.push(Token::Number(value.to_string()));
tokens.push(Token::Boolean(value.to_string()));
}
JsonValue::List { space0, elements } => {
tokens.push(Token::CodeDelimiter("[".to_string()));
@ -821,6 +821,9 @@ impl Tokenizable for JsonValue {
JsonValue::Null {} => {
tokens.push(Token::Keyword("null".to_string()));
}
JsonValue::Expression(exp) => {
tokens.append(&mut exp.tokenize());
}
}
tokens
}

View File

@ -242,6 +242,7 @@ fn format_token(token: Token) -> String {
match token {
Token::Whitespace(s)
| Token::Number(s)
| Token::Boolean(s)
| Token::String(s)
| Token::Keyword(s)
| Token::Quote(s)