diff --git a/packages/hurl/src/jsonpath/ast.rs b/packages/hurl/src/jsonpath/ast.rs index 3e810a075..e1ae23c6b 100644 --- a/packages/hurl/src/jsonpath/ast.rs +++ b/packages/hurl/src/jsonpath/ast.rs @@ -56,7 +56,9 @@ pub enum PredicateFunc { KeyExist, EqualBool(bool), EqualString(String), + NotEqualString(String), Equal(Number), + NotEqual(Number), GreaterThan(Number), GreaterThanOrEqual(Number), LessThan(Number), diff --git a/packages/hurl/src/jsonpath/eval/selector.rs b/packages/hurl/src/jsonpath/eval/selector.rs index 000c11f63..71e0ba7e0 100644 --- a/packages/hurl/src/jsonpath/eval/selector.rs +++ b/packages/hurl/src/jsonpath/eval/selector.rs @@ -172,6 +172,12 @@ impl Predicate { (serde_json::Value::String(v), PredicateFunc::EqualString(ref s)) => { v == *s } + (serde_json::Value::String(v), PredicateFunc::NotEqualString(ref s)) => { + v != *s + } + (serde_json::Value::Number(v), PredicateFunc::NotEqual(ref num)) => { + !approx_eq!(f64, v.as_f64().unwrap(), num.to_f64(), ulps = 2) + } (serde_json::Value::Bool(v), PredicateFunc::EqualBool(ref s)) => v == *s, _ => false, } diff --git a/packages/hurl/src/jsonpath/jsonpath.grammar b/packages/hurl/src/jsonpath/jsonpath.grammar index e182908a6..136d75800 100644 --- a/packages/hurl/src/jsonpath/jsonpath.grammar +++ b/packages/hurl/src/jsonpath/jsonpath.grammar @@ -31,7 +31,9 @@ predicate-key = "@." key-name predicate-func = key-exist-predicate-func | equal-string-predicate-func + | notequal-string-predicate-func | equal-number-predicate-func + | notequal-number-predicate-func | greater-than-predicate-func | greater-or-equal-than-predicate-func @@ -40,6 +42,10 @@ equal-string-predicate-func = "=" string-value equal-number-predicate-func- = "=" number +notequal-string-predicate-func = "!=" string-value + +notequal-number-predicate-func = "!=" number + # # Primitives @@ -50,6 +56,3 @@ key-name = string-value = "'" "'" number = - - - diff --git a/packages/hurl/src/jsonpath/parser/parse.rs b/packages/hurl/src/jsonpath/parser/parse.rs index 646c6d1ef..fca5719a6 100644 --- a/packages/hurl/src/jsonpath/parser/parse.rs +++ b/packages/hurl/src/jsonpath/parser/parse.rs @@ -232,6 +232,8 @@ fn predicate_func(reader: &mut Reader) -> ParseResult { less_than_or_equal_predicate_func, equal_boolean_predicate_func, equal_string_predicate_func, + notequal_string_predicate_func, + notequal_number_func ], reader, ) @@ -286,6 +288,20 @@ fn equal_string_predicate_func(reader: &mut Reader) -> ParseResult ParseResult { + try_literal("!=", reader)?; + whitespace(reader); + let s = string_value(reader)?; + Ok(PredicateFunc::NotEqualString(s)) +} + +fn notequal_number_func(reader: &mut Reader) -> ParseResult { + try_literal("!=", reader)?; + whitespace(reader); + let num = number(reader)?; + Ok(PredicateFunc::NotEqual(num)) +} + #[cfg(test)] mod tests { use hurl_core::reader::Pos; @@ -637,6 +653,24 @@ mod tests { ); assert_eq!(reader.cursor().index, 9); + let mut reader = Reader::new("!='hello'"); + assert_eq!( + predicate_func(&mut reader).unwrap(), + PredicateFunc::NotEqualString("hello".to_string()) + ); + + let mut reader = Reader::new("!=2"); + assert_eq!( + predicate_func(&mut reader).unwrap(), + PredicateFunc::NotEqual(Number { int: 2, decimal: 0 }) + ); + + let mut reader = Reader::new("!=2.5"); + assert_eq!( + predicate_func(&mut reader).unwrap(), + PredicateFunc::NotEqual(Number { int: 2, decimal: 500_000_000_000_000_000 }) + ); + let mut reader = Reader::new(">5"); assert_eq!( predicate_func(&mut reader).unwrap(), diff --git a/packages/hurl/src/jsonpath/tests/mod.rs b/packages/hurl/src/jsonpath/tests/mod.rs index e7ce4afed..0930b4074 100644 --- a/packages/hurl/src/jsonpath/tests/mod.rs +++ b/packages/hurl/src/jsonpath/tests/mod.rs @@ -253,6 +253,29 @@ fn test_bookstore_path() { JsonpathResult::Collection(vec![book0_value(), book2_value()]) ); + // get all books whose title is not "hamlet". + let expr = jsonpath::parse("$..book[?(@.title!='Moby Dick')]").unwrap(); + assert_eq!( + expr.eval(&bookstore_value()).unwrap(), + JsonpathResult::Collection(vec![ + book0_value(), + book1_value(), + book3_value() + ]) + ); + + // get all books whose price is not 8.95 (first book) + let expr = jsonpath::parse("$..book[?(@.price!=8.95)]").unwrap(); + assert_eq!( + expr.eval(&bookstore_value()).unwrap(), + JsonpathResult::Collection(vec![ + book1_value(), + book2_value(), + book3_value() + ]) + ); + + // All members of JSON structure let expr = jsonpath::parse("$..*").unwrap(); // Order is reproducible