Merge pull request #98 from Orange-OpenSource/feature/add-type-predicates

Add type predicates
This commit is contained in:
Fabrice Reix 2020-11-28 18:46:43 +01:00 committed by GitHub
commit 1b59dfaa6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 184 additions and 9 deletions

File diff suppressed because one or more lines are too long

View File

@ -4,17 +4,22 @@ HTTP/1.0 200
jsonpath "$.success" equals false
jsonpath "$.success" not equals null
jsonpath "$.success" exists
jsonpath "$.success" isBoolean
jsonpath "$.errors" countEquals 2
jsonpath "$.errors" isCollection
jsonpath "$.warnings" countEquals 0
jsonpath "$.toto" not exists
jsonpath "$.warnings" exists
jsonpath "$.warnings" exists
jsonpath "$.errors[0]" exists
jsonpath "$.errors[0]" isCollection
jsonpath "$.errors[0].id" equals "error1"
jsonpath "$.errors[0]['id']" equals "error1"
jsonpath "$.duration" equals 1.5
jsonpath "$.duration" lessThanOrEquals 2.0
jsonpath "$.duration" lessThan 2
jsonpath "$.duration" isFloat
jsonpath "$.duration" not isInteger
jsonpath "$.nullable" equals null
{

View File

@ -1 +1 @@
{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/assert-json"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"jsonpath","expr":"$.success"},"predicate":{"type":"equal","value":false}},{"query":{"type":"jsonpath","expr":"$.success"},"predicate":{"not":true,"type":"equal","value":null}},{"query":{"type":"jsonpath","expr":"$.success"},"predicate":{"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.errors"},"predicate":{"type":"count","value":2}},{"query":{"type":"jsonpath","expr":"$.warnings"},"predicate":{"type":"count","value":0}},{"query":{"type":"jsonpath","expr":"$.toto"},"predicate":{"not":true,"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.warnings"},"predicate":{"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.warnings"},"predicate":{"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.errors[0]"},"predicate":{"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.errors[0].id"},"predicate":{"type":"equal","value":"error1"}},{"query":{"type":"jsonpath","expr":"$.errors[0]['id']"},"predicate":{"type":"equal","value":"error1"}},{"query":{"type":"jsonpath","expr":"$.duration"},"predicate":{"type":"equal","value":1.5}},{"query":{"type":"jsonpath","expr":"$.duration"},"predicate":{"type":"less-than-or-equal","value":"2.0"}},{"query":{"type":"jsonpath","expr":"$.duration"},"predicate":{"type":"greater-than","value":"2"}},{"query":{"type":"jsonpath","expr":"$.nullable"},"predicate":{"type":"equal","value":null}}],"body":{"type":"json","value":{"success":false,"errors":[{"id":"error1"},{"id":"error2"}],"warnings":[],"duration":1.5,"tags":["test"],"nullable":null}}}},{"request":{"method":"GET","url":"http://localhost:8000/assert-json/index"},"response":{"version":"HTTP/1.0","status":200,"captures":[{"name":"index","query":{"type":"body"}}]}},{"request":{"method":"GET","url":"http://localhost:8000/assert-json"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"jsonpath","expr":"$.errors[{{index}}].id"},"predicate":{"type":"equal","value":"error2"}},{"query":{"type":"jsonpath","expr":"$.tags"},"predicate":{"type":"include","value":"test"}},{"query":{"type":"jsonpath","expr":"$.tags"},"predicate":{"not":true,"type":"include","value":"prod"}},{"query":{"type":"jsonpath","expr":"$.tags"},"predicate":{"not":true,"type":"include","value":null}}]}},{"request":{"method":"GET","url":"http://localhost:8000/assert-json/list"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"jsonpath","expr":"$"},"predicate":{"type":"count","value":2}},{"query":{"type":"jsonpath","expr":"$.[0].name"},"predicate":{"type":"equal","value":"Bob"}},{"query":{"type":"jsonpath","expr":"$[0].name"},"predicate":{"type":"equal","value":"Bob"}}]}}]}
{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/assert-json"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"jsonpath","expr":"$.success"},"predicate":{"type":"equal","value":false}},{"query":{"type":"jsonpath","expr":"$.success"},"predicate":{"not":true,"type":"equal","value":null}},{"query":{"type":"jsonpath","expr":"$.success"},"predicate":{"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.success"},"predicate":{"type":"isBoolean"}},{"query":{"type":"jsonpath","expr":"$.errors"},"predicate":{"type":"count","value":2}},{"query":{"type":"jsonpath","expr":"$.errors"},"predicate":{"type":"isCollection"}},{"query":{"type":"jsonpath","expr":"$.warnings"},"predicate":{"type":"count","value":0}},{"query":{"type":"jsonpath","expr":"$.toto"},"predicate":{"not":true,"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.warnings"},"predicate":{"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.warnings"},"predicate":{"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.errors[0]"},"predicate":{"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.errors[0]"},"predicate":{"type":"isCollection"}},{"query":{"type":"jsonpath","expr":"$.errors[0].id"},"predicate":{"type":"equal","value":"error1"}},{"query":{"type":"jsonpath","expr":"$.errors[0]['id']"},"predicate":{"type":"equal","value":"error1"}},{"query":{"type":"jsonpath","expr":"$.duration"},"predicate":{"type":"equal","value":1.5}},{"query":{"type":"jsonpath","expr":"$.duration"},"predicate":{"type":"less-than-or-equal","value":"2.0"}},{"query":{"type":"jsonpath","expr":"$.duration"},"predicate":{"type":"greater-than","value":"2"}},{"query":{"type":"jsonpath","expr":"$.duration"},"predicate":{"type":"isFloat"}},{"query":{"type":"jsonpath","expr":"$.duration"},"predicate":{"not":true,"type":"isInteger"}},{"query":{"type":"jsonpath","expr":"$.nullable"},"predicate":{"type":"equal","value":null}}],"body":{"type":"json","value":{"success":false,"errors":[{"id":"error1"},{"id":"error2"}],"warnings":[],"duration":1.5,"tags":["test"],"nullable":null}}}},{"request":{"method":"GET","url":"http://localhost:8000/assert-json/index"},"response":{"version":"HTTP/1.0","status":200,"captures":[{"name":"index","query":{"type":"body"}}]}},{"request":{"method":"GET","url":"http://localhost:8000/assert-json"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"jsonpath","expr":"$.errors[{{index}}].id"},"predicate":{"type":"equal","value":"error2"}},{"query":{"type":"jsonpath","expr":"$.tags"},"predicate":{"type":"include","value":"test"}},{"query":{"type":"jsonpath","expr":"$.tags"},"predicate":{"not":true,"type":"include","value":"prod"}},{"query":{"type":"jsonpath","expr":"$.tags"},"predicate":{"not":true,"type":"include","value":null}}]}},{"request":{"method":"GET","url":"http://localhost:8000/assert-json/list"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"jsonpath","expr":"$"},"predicate":{"type":"count","value":2}},{"query":{"type":"jsonpath","expr":"$.[0].name"},"predicate":{"type":"equal","value":"Bob"}},{"query":{"type":"jsonpath","expr":"$[0].name"},"predicate":{"type":"equal","value":"Bob"}}]}}]}

View File

@ -39,3 +39,11 @@ error: Assert Failure
| expected: int <5>
|
error: Assert Failure
--> tests/error_assert_value_error.hurl:9:0
|
9 | jsonpath "$.count" isFloat
| actual: int <2>
| expected: float
|

View File

@ -1 +1 @@
<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/error-assert-value</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">header</span> <span class="string">"content-type"</span> <span class="predicate-type">equals</span> <span class="string">"XXX"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.id"</span> <span class="predicate-type">equals</span> <span class="string">"000001"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.values"</span> <span class="predicate-type">includes</span> <span class="number">100</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.values"</span> not <span class="predicate-type">contains</span> <span class="string">"Hello"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.count"</span> <span class="predicate-type">greater-than</span> <span class="number">5</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/error-assert-value</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">header</span> <span class="string">"content-type"</span> <span class="predicate-type">equals</span> <span class="string">"XXX"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.id"</span> <span class="predicate-type">equals</span> <span class="string">"000001"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.values"</span> <span class="predicate-type">includes</span> <span class="number">100</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.values"</span> not <span class="predicate-type">contains</span> <span class="string">"Hello"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.count"</span> <span class="predicate-type">greater-than</span> <span class="number">5</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.count"</span> <span class="predicate-type">isFloat</span></span></div></div></div>

View File

@ -5,4 +5,5 @@ header "content-type" equals "XXX"
jsonpath "$.id" equals "000001"
jsonpath "$.values" includes 100
jsonpath "$.values" not contains "Hello"
jsonpath "$.count" greaterThan 5
jsonpath "$.count" greaterThan 5
jsonpath "$.count" isFloat

View File

@ -1 +1 @@
{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/error-assert-value"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"header","name":"content-type"},"predicate":{"type":"equal","value":"XXX"}},{"query":{"type":"jsonpath","expr":"$.id"},"predicate":{"type":"equal","value":"000001"}},{"query":{"type":"jsonpath","expr":"$.values"},"predicate":{"type":"include","value":100}},{"query":{"type":"jsonpath","expr":"$.values"},"predicate":{"not":true,"type":"contain","value":"Hello"}},{"query":{"type":"jsonpath","expr":"$.count"},"predicate":{"type":"greater-than","value":"5"}}]}}]}
{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/error-assert-value"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"header","name":"content-type"},"predicate":{"type":"equal","value":"XXX"}},{"query":{"type":"jsonpath","expr":"$.id"},"predicate":{"type":"equal","value":"000001"}},{"query":{"type":"jsonpath","expr":"$.values"},"predicate":{"type":"include","value":100}},{"query":{"type":"jsonpath","expr":"$.values"},"predicate":{"not":true,"type":"contain","value":"Hello"}},{"query":{"type":"jsonpath","expr":"$.count"},"predicate":{"type":"greater-than","value":"5"}},{"query":{"type":"jsonpath","expr":"$.count"},"predicate":{"type":"isFloat"}}]}}]}

View File

@ -284,6 +284,11 @@ fn expected(
let expected = eval_template(expected, variables)?;
Ok(format!("matches regex <{}>", expected))
}
PredicateFuncValue::IsInteger {} => Ok("integer".to_string()),
PredicateFuncValue::IsFloat {} => Ok("float".to_string()),
PredicateFuncValue::IsBoolean {} => Ok("boolean".to_string()),
PredicateFuncValue::IsString {} => Ok("string".to_string()),
PredicateFuncValue::IsCollection {} => Ok("collection".to_string()),
PredicateFuncValue::Exist {} => Ok("something".to_string()),
}
}
@ -498,6 +503,41 @@ fn eval_something(
}
}
// types
PredicateFuncValue::IsInteger {} => Ok(AssertResult {
success: matches!(value, Value::Integer(_)),
actual: value.display(),
expected: "integer".to_string(),
type_mismatch: false,
}),
PredicateFuncValue::IsFloat {} => Ok(AssertResult {
success: matches!(value, Value::Float(_, _)),
actual: value.display(),
expected: "float".to_string(),
type_mismatch: false,
}),
PredicateFuncValue::IsBoolean {} => Ok(AssertResult {
success: matches!(value, Value::Bool(_)),
actual: value.display(),
expected: "boolean".to_string(),
type_mismatch: false,
}),
PredicateFuncValue::IsString {} => Ok(AssertResult {
success: matches!(value, Value::String(_)),
actual: value.display(),
expected: "string".to_string(),
type_mismatch: false,
}),
PredicateFuncValue::IsCollection {} => Ok(AssertResult {
success: matches!(value, Value::Bytes(_))
|| matches!(value, Value::List(_))
|| matches!(value, Value::Nodeset(_))
|| matches!(value, Value::Object(_)),
actual: value.display(),
expected: "collection".to_string(),
type_mismatch: false,
}),
// exists
PredicateFuncValue::Exist {} => match value {
Value::Nodeset(0) => Ok(AssertResult {
@ -1165,7 +1205,7 @@ mod tests {
success: true,
type_mismatch: false,
actual: "int <2>".to_string(),
expected: "int <1>".to_string()
expected: "int <1>".to_string(),
}
);
assert_eq!(
@ -1174,7 +1214,7 @@ mod tests {
success: false,
type_mismatch: false,
actual: "int <1>".to_string(),
expected: "int <1>".to_string()
expected: "int <1>".to_string(),
}
);
assert_eq!(
@ -1183,7 +1223,7 @@ mod tests {
success: true,
type_mismatch: false,
actual: "float <1.1>".to_string(),
expected: "int <1>".to_string()
expected: "int <1>".to_string(),
}
);
assert_eq!(
@ -1192,7 +1232,7 @@ mod tests {
success: false,
type_mismatch: false,
actual: "float <1.1>".to_string(),
expected: "int <2>".to_string()
expected: "int <2>".to_string(),
}
);
}
@ -1299,6 +1339,38 @@ mod tests {
assert_eq!(assert_result.expected.as_str(), "count equals to <1>");
}
#[test]
fn test_predicate_type() {
let variables = HashMap::new();
let assert_result = eval_something(
PredicateFunc {
value: PredicateFuncValue::IsInteger {},
source_info: SourceInfo::init(0, 0, 0, 0),
},
&variables,
Value::Integer(1),
)
.unwrap();
assert_eq!(assert_result.success, true);
assert_eq!(assert_result.type_mismatch, false);
assert_eq!(assert_result.actual.as_str(), "int <1>");
assert_eq!(assert_result.expected.as_str(), "integer");
let assert_result = eval_something(
PredicateFunc {
value: PredicateFuncValue::IsInteger {},
source_info: SourceInfo::init(0, 0, 0, 0),
},
&variables,
Value::Float(1, 0),
)
.unwrap();
assert_eq!(assert_result.success, false);
assert_eq!(assert_result.type_mismatch, false);
assert_eq!(assert_result.actual.as_str(), "float <1.0>");
assert_eq!(assert_result.expected.as_str(), "integer");
}
#[test]
fn test_predicate_not_with_different_types() {
// equals predicate does not generate a type error with an integer value

View File

@ -422,6 +422,11 @@ pub enum PredicateFuncValue {
IncludeNull { space0: Whitespace },
IncludeExpression { space0: Whitespace, value: Expr },
Match { space0: Whitespace, value: Template },
IsInteger {},
IsFloat {},
IsBoolean {},
IsString {},
IsCollection {},
Exist {},
}

View File

@ -72,6 +72,11 @@ fn predicate_func_value(reader: &mut Reader) -> ParseResult<'static, PredicateFu
contain_predicate,
include_predicate,
match_predicate,
integer_predicate,
float_predicate,
boolean_predicate,
string_predicate,
collection_predicate,
exist_predicate,
],
reader,
@ -289,6 +294,31 @@ fn match_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateFuncVal
Ok(PredicateFuncValue::Match { space0, value })
}
fn integer_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateFuncValue> {
try_literal("isInteger", reader)?;
Ok(PredicateFuncValue::IsInteger {})
}
fn float_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateFuncValue> {
try_literal("isFloat", reader)?;
Ok(PredicateFuncValue::IsFloat {})
}
fn boolean_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateFuncValue> {
try_literal("isBoolean", reader)?;
Ok(PredicateFuncValue::IsBoolean {})
}
fn string_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateFuncValue> {
try_literal("isString", reader)?;
Ok(PredicateFuncValue::IsString {})
}
fn collection_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateFuncValue> {
try_literal("isCollection", reader)?;
Ok(PredicateFuncValue::IsCollection {})
}
fn exist_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateFuncValue> {
try_literal("exists", reader)?;
Ok(PredicateFuncValue::Exist {})

View File

@ -591,6 +591,21 @@ impl Htmlable for PredicateFuncValue {
buffer.push_str(space0.to_html().as_str());
buffer.push_str(format!("<span class=\"boolean\">{}</span>", value).as_str());
}
PredicateFuncValue::IsInteger {} => {
buffer.push_str("<span class=\"predicate-type\">isInteger</span>");
}
PredicateFuncValue::IsFloat {} => {
buffer.push_str("<span class=\"predicate-type\">isFloat</span>");
}
PredicateFuncValue::IsBoolean {} => {
buffer.push_str("<span class=\"predicate-type\">isBoolean</span>");
}
PredicateFuncValue::IsString {} => {
buffer.push_str("<span class=\"predicate-type\">isString</span>");
}
PredicateFuncValue::IsCollection {} => {
buffer.push_str("<span class=\"predicate-type\">isCollection</span>");
}
PredicateFuncValue::Exist {} => {
buffer.push_str("<span class=\"predicate-type\">exists</span>");
}

View File

@ -438,6 +438,24 @@ impl ToJson for Predicate {
attributes.push(("type".to_string(), JValue::String("match".to_string())));
attributes.push(("value".to_string(), JValue::String(value.to_string())));
}
PredicateFuncValue::IsInteger {} => {
attributes.push(("type".to_string(), JValue::String("isInteger".to_string())));
}
PredicateFuncValue::IsFloat {} => {
attributes.push(("type".to_string(), JValue::String("isFloat".to_string())));
}
PredicateFuncValue::IsBoolean {} => {
attributes.push(("type".to_string(), JValue::String("isBoolean".to_string())));
}
PredicateFuncValue::IsString {} => {
attributes.push(("type".to_string(), JValue::String("isString".to_string())));
}
PredicateFuncValue::IsCollection {} => {
attributes.push((
"type".to_string(),
JValue::String("isCollection".to_string()),
));
}
PredicateFuncValue::Exist {} => {
attributes.push(("type".to_string(), JValue::String("exist".to_string())));
}

View File

@ -652,6 +652,22 @@ impl Tokenizable for PredicateFuncValue {
add_tokens(&mut tokens, space0.tokenize());
add_tokens(&mut tokens, value.tokenize());
}
PredicateFuncValue::IsInteger {} => {
tokens.push(Token::PredicateType(String::from("isInteger")));
}
PredicateFuncValue::IsFloat {} => {
tokens.push(Token::PredicateType(String::from("isFloat")));
}
PredicateFuncValue::IsBoolean {} => {
tokens.push(Token::PredicateType(String::from("isBoolean")));
}
PredicateFuncValue::IsString {} => {
tokens.push(Token::PredicateType(String::from("isString")));
}
PredicateFuncValue::IsCollection {} => {
tokens.push(Token::PredicateType(String::from("isCollection")));
}
PredicateFuncValue::Exist {} => {
tokens.push(Token::PredicateType(String::from("exists")));
}

View File

@ -508,6 +508,11 @@ impl Lintable<PredicateFuncValue> for PredicateFuncValue {
space0: one_whitespace(),
value: value.clone(),
},
PredicateFuncValue::IsInteger {} => PredicateFuncValue::IsInteger {},
PredicateFuncValue::IsFloat {} => PredicateFuncValue::IsFloat {},
PredicateFuncValue::IsBoolean {} => PredicateFuncValue::IsBoolean {},
PredicateFuncValue::IsString {} => PredicateFuncValue::IsString {},
PredicateFuncValue::IsCollection {} => PredicateFuncValue::IsCollection {},
PredicateFuncValue::Exist {} => PredicateFuncValue::Exist {},
}
}