From 15c7627c49f6cfa0e07a910290f15188ef1091ef Mon Sep 17 00:00:00 2001 From: StochitaCatalin Date: Sun, 19 Nov 2023 20:33:47 +0200 Subject: [PATCH 1/3] Added negate operator '!' --- README.md | 3 ++- src/lib.rs | 18 +++++++++++++++++- src/parser/grammar/json_path.pest | 4 +++- src/parser/model.rs | 2 ++ src/parser/parser.rs | 20 ++++++++++++++++++-- src/path/index.rs | 9 +++++++++ 6 files changed, 51 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2894b6e..a9b096a 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ following elements: | Expression sign | Description | Where to use | |-----------------|--------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| +| `!` | Not | To negate the expression | | `==` | Equal | To compare numbers or string literals | | `!=` | Unequal | To compare numbers or string literals in opposite way to equals | | `<` | Less | To compare numbers | @@ -99,7 +100,7 @@ following elements: | `noneOf` | The left size has no intersection with right | | | `anyOf` | The left size has at least one intersection with right | | | `subsetOf` | The left is a subset of the right side | | -| | Exists operator. | The operator checks the existence of the field depicted on the left side like that `[?(@.key.isActive)]` | +| `?` | Exists operator. | The operator checks the existence of the field depicted on the left side like that `[?(@.key.isActive)]` | Filter expressions can be chained using `||` and `&&` (logical or and logical and correspondingly) in the following way: diff --git a/src/lib.rs b/src/lib.rs index 786a5ae..92c905d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1221,10 +1221,26 @@ mod tests { JsonPathInst::from_str("$.first[?(@.does_not_exist >= 1.0)]") .expect("the path is correct"), ); - let finder = JsonPathFinder::new(json, path); + let finder = JsonPathFinder::new(json.clone(), path); let v = finder.find_slice(); assert_eq!(v, vec![NoValue]); + + let path: Box = Box::from( + JsonPathInst::from_str("$.first[?(!@.does_not_exist >= 1.0)]") + .expect("the path is correct"), + ); + let finder = JsonPathFinder::new(json.clone(), path); + let v = finder.find_slice(); + assert_eq!(v, vec![Slice(&json!({"second":[{"active": 1}, {"passive": 1}]}), "$.['first']".to_string())]); + + let path: Box = Box::from( + JsonPathInst::from_str("$.first[?(!(@.does_not_exist >= 1.0))]") + .expect("the path is correct"), + ); + let finder = JsonPathFinder::new(json, path); + let v = finder.find_slice(); + assert_eq!(v, vec![Slice(&json!({"second":[{"active": 1}, {"passive": 1}]}), "$.['first']".to_string())]) } // #[test] diff --git a/src/parser/grammar/json_path.pest b/src/parser/grammar/json_path.pest index a0ed959..b10094e 100644 --- a/src/parser/grammar/json_path.pest +++ b/src/parser/grammar/json_path.pest @@ -40,7 +40,9 @@ unit_keys = { string_qt ~ ("," ~ string_qt)+ } unit_indexes = { number ~ ("," ~ number)+ } filter = {"?"~ "(" ~ logic ~ ")"} -logic = {logic_and ~ ("||" ~ logic_and)*} +logic = {logic_not | logic_or} +logic_not = {"!" ~ "(" ~ logic_or ~ ")" | "!" ~ logic_or} +logic_or = {logic_and ~ ("||" ~ logic_and)*} logic_and = {logic_atom ~ ("&&" ~ logic_atom)*} logic_atom = {atom ~ (sign ~ atom)? | "(" ~ logic ~ ")"} diff --git a/src/parser/model.rs b/src/parser/model.rs index d60ecd3..5b55336 100644 --- a/src/parser/model.rs +++ b/src/parser/model.rs @@ -69,6 +69,8 @@ pub enum FilterExpression { And(Box, Box), /// or with || Or(Box, Box), + /// not with ! + Not(Box), } impl FilterExpression { diff --git a/src/parser/parser.rs b/src/parser/parser.rs index e95e01a..2b2402c 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -1,6 +1,6 @@ use crate::parser::errors::JsonPathParserError::ParserError; use crate::parser::errors::{parser_err, JsonPathParserError}; -use crate::parser::model::FilterExpression::{And, Or}; +use crate::parser::model::FilterExpression::{And, Not, Or}; use crate::parser::model::{ FilterExpression, FilterSign, Function, JsonPath, JsonPathIndex, Operand, }; @@ -148,7 +148,23 @@ fn parse_filter_index(pair: Pair) -> Result) -> Result { +fn parse_logic(mut pairs: Pairs) -> Result { + if let Some(rule) = pairs.peek().map(|x| x.as_rule()) { + match rule { + Rule::logic_not => parse_logic_not(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").into_inner()), + Rule::logic_or => parse_logic_or(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").into_inner()), + x => Err(JsonPathParserError::UnexpectedRuleLogicError(x, pairs)), + } + } else { + Err(JsonPathParserError::UnexpectedNoneLogicError(pairs)) + } +} + +fn parse_logic_not(mut pairs: Pairs) -> Result { + Ok(Not(Box::new(parse_logic_or(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").into_inner())?))) +} + +fn parse_logic_or(pairs: Pairs) -> Result { let mut expr: Option = None; let error_message = format!("Failed to parse logical expression: {:?}", pairs); for pair in pairs { diff --git a/src/path/index.rs b/src/path/index.rs index f371788..c26f8b0 100644 --- a/src/path/index.rs +++ b/src/path/index.rs @@ -205,6 +205,9 @@ pub enum FilterPath<'a> { left: PathInstance<'a>, right: PathInstance<'a>, }, + Not { + exp: PathInstance<'a>, + } } impl<'a> FilterPath<'a> { @@ -223,6 +226,9 @@ impl<'a> FilterPath<'a> { left: Box::new(FilterPath::new(l, root)), right: Box::new(FilterPath::new(r, root)), }, + FilterExpression::Not(exp) => FilterPath::Not { + exp: Box::new(FilterPath::new(exp, root)), + } } } fn compound( @@ -307,6 +313,9 @@ impl<'a> FilterPath<'a> { !JsonPathValue::vec_as_data(right.find(Slice(curr_el, pref))).is_empty() } } + FilterPath::Not { exp } => { + JsonPathValue::vec_as_data(exp.find(Slice(curr_el, pref))).is_empty() + } } } } From 4078b5e0200bf8b03ec443434e4e4056e8b8451b Mon Sep 17 00:00:00 2001 From: StochitaCatalin Date: Tue, 21 Nov 2023 14:26:40 +0200 Subject: [PATCH 2/3] Fix chaining '!' by moving the logic_not before parsing logic_atom --- src/lib.rs | 24 +++++++++++++++++++-- src/parser/grammar/json_path.pest | 10 ++++----- src/parser/parser.rs | 36 ++++++++++++++++--------------- 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 92c905d..0e5f020 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1225,22 +1225,42 @@ mod tests { let v = finder.find_slice(); assert_eq!(v, vec![NoValue]); + } + #[test] + fn logical_not_exp_test(){ + let json: Box = Box::new(json!({"first":{"second":{"active":1}}})); let path: Box = Box::from( JsonPathInst::from_str("$.first[?(!@.does_not_exist >= 1.0)]") .expect("the path is correct"), ); let finder = JsonPathFinder::new(json.clone(), path); let v = finder.find_slice(); - assert_eq!(v, vec![Slice(&json!({"second":[{"active": 1}, {"passive": 1}]}), "$.['first']".to_string())]); + assert_eq!(v, vec![Slice(&json!({"second":{"active": 1}}), "$.['first']".to_string())]); let path: Box = Box::from( JsonPathInst::from_str("$.first[?(!(@.does_not_exist >= 1.0))]") .expect("the path is correct"), ); + let finder = JsonPathFinder::new(json.clone(), path); + let v = finder.find_slice(); + assert_eq!(v, vec![Slice(&json!({"second":{"active": 1}}), "$.['first']".to_string())]); + + let path: Box = Box::from( + JsonPathInst::from_str("$.first[?(!(@.second.active == 1) || @.second.active == 1)]") + .expect("the path is correct"), + ); + let finder = JsonPathFinder::new(json.clone(), path); + let v = finder.find_slice(); + assert_eq!(v, vec![Slice(&json!({"second":{"active": 1}}), "$.['first']".to_string())]); + + let path: Box = Box::from( + JsonPathInst::from_str("$.first[?(!@.second.active == 1 && !@.second.active == 1 || !@.second.active == 2)]") + .expect("the path is correct"), + ); let finder = JsonPathFinder::new(json, path); let v = finder.find_slice(); - assert_eq!(v, vec![Slice(&json!({"second":[{"active": 1}, {"passive": 1}]}), "$.['first']".to_string())]) + assert_eq!(v, vec![Slice(&json!({"second":{"active": 1}}), "$.['first']".to_string())]); } // #[test] diff --git a/src/parser/grammar/json_path.pest b/src/parser/grammar/json_path.pest index b10094e..183297f 100644 --- a/src/parser/grammar/json_path.pest +++ b/src/parser/grammar/json_path.pest @@ -19,6 +19,7 @@ char = _{ } root = {"$"} sign = { "==" | "!=" | "~=" | ">=" | ">" | "<=" | "<" | "in" | "nin" | "size" | "noneOf" | "anyOf" | "subsetOf"} +not = {"!"} key_lim = {!"length()" ~ (word | ASCII_DIGIT | specs)+} key_unlim = {"[" ~ string_qt ~ "]"} key = ${key_lim | key_unlim} @@ -38,13 +39,12 @@ slice = {start_slice? ~ col ~ end_slice? ~ step_slice? } unit_keys = { string_qt ~ ("," ~ string_qt)+ } unit_indexes = { number ~ ("," ~ number)+ } -filter = {"?"~ "(" ~ logic ~ ")"} +filter = {"?"~ "(" ~ logic_or ~ ")"} -logic = {logic_not | logic_or} -logic_not = {"!" ~ "(" ~ logic_or ~ ")" | "!" ~ logic_or} logic_or = {logic_and ~ ("||" ~ logic_and)*} -logic_and = {logic_atom ~ ("&&" ~ logic_atom)*} -logic_atom = {atom ~ (sign ~ atom)? | "(" ~ logic ~ ")"} +logic_and = {logic_not ~ ("&&" ~ logic_not)*} +logic_not = {not? ~ logic_atom} +logic_atom = {atom ~ (sign ~ atom)? | "(" ~ logic_or ~ ")"} atom = {chain | string_qt | number | boolean | null} diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 2b2402c..5324914 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -145,24 +145,10 @@ fn parse_chain_in_operand(rule: Pair) -> Result) -> Result { - Ok(JsonPathIndex::Filter(parse_logic(pair.into_inner())?)) + Ok(JsonPathIndex::Filter(parse_logic_or(pair.into_inner())?)) } -fn parse_logic(mut pairs: Pairs) -> Result { - if let Some(rule) = pairs.peek().map(|x| x.as_rule()) { - match rule { - Rule::logic_not => parse_logic_not(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").into_inner()), - Rule::logic_or => parse_logic_or(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").into_inner()), - x => Err(JsonPathParserError::UnexpectedRuleLogicError(x, pairs)), - } - } else { - Err(JsonPathParserError::UnexpectedNoneLogicError(pairs)) - } -} -fn parse_logic_not(mut pairs: Pairs) -> Result { - Ok(Not(Box::new(parse_logic_or(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").into_inner())?))) -} fn parse_logic_or(pairs: Pairs) -> Result { let mut expr: Option = None; @@ -184,7 +170,7 @@ fn parse_logic_and(pairs: Pairs) -> Result = None; let error_message = format!("Failed to parse logical `and` expression: {:?}", pairs,); for pair in pairs { - let next_expr = parse_logic_atom(pair.into_inner())?; + let next_expr = parse_logic_not(pair.into_inner())?; match expr { None => expr = Some(next_expr), Some(e) => expr = Some(And(Box::new(e), Box::new(next_expr))), @@ -196,10 +182,26 @@ fn parse_logic_and(pairs: Pairs) -> Result) -> Result { + if let Some(rule) = pairs.peek().map(|x| x.as_rule()) { + match rule { + Rule::not => { + pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)"); + parse_logic_not(pairs) + .map(|expr|Not(Box::new(expr))) + }, + Rule::logic_atom => parse_logic_atom(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").into_inner()), + x => Err(JsonPathParserError::UnexpectedRuleLogicError(x, pairs)), + } + } else { + Err(JsonPathParserError::UnexpectedNoneLogicError(pairs)) + } +} + fn parse_logic_atom(mut pairs: Pairs) -> Result { if let Some(rule) = pairs.peek().map(|x| x.as_rule()) { match rule { - Rule::logic => parse_logic(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").into_inner()), + Rule::logic_or => parse_logic_or(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").into_inner()), Rule::atom => { let left: Operand = parse_atom(pairs.next().unwrap())?; if pairs.peek().is_none() { From f45bfacb4ce59e92e4d3b5348ec5626e8a78b56e Mon Sep 17 00:00:00 2001 From: StochitaCatalin Date: Wed, 22 Nov 2023 15:21:06 +0200 Subject: [PATCH 3/3] rustfmt fail fix --- src/lib.rs | 34 +++++++++++++++++++++++++++++----- src/parser/parser.rs | 2 -- src/path/index.rs | 4 ++-- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0e5f020..8859bd7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1228,7 +1228,7 @@ mod tests { } #[test] - fn logical_not_exp_test(){ + fn logical_not_exp_test() { let json: Box = Box::new(json!({"first":{"second":{"active":1}}})); let path: Box = Box::from( JsonPathInst::from_str("$.first[?(!@.does_not_exist >= 1.0)]") @@ -1236,7 +1236,13 @@ mod tests { ); let finder = JsonPathFinder::new(json.clone(), path); let v = finder.find_slice(); - assert_eq!(v, vec![Slice(&json!({"second":{"active": 1}}), "$.['first']".to_string())]); + assert_eq!( + v, + vec![Slice( + &json!({"second":{"active": 1}}), + "$.['first']".to_string() + )] + ); let path: Box = Box::from( JsonPathInst::from_str("$.first[?(!(@.does_not_exist >= 1.0))]") @@ -1244,7 +1250,13 @@ mod tests { ); let finder = JsonPathFinder::new(json.clone(), path); let v = finder.find_slice(); - assert_eq!(v, vec![Slice(&json!({"second":{"active": 1}}), "$.['first']".to_string())]); + assert_eq!( + v, + vec![Slice( + &json!({"second":{"active": 1}}), + "$.['first']".to_string() + )] + ); let path: Box = Box::from( JsonPathInst::from_str("$.first[?(!(@.second.active == 1) || @.second.active == 1)]") @@ -1252,7 +1264,13 @@ mod tests { ); let finder = JsonPathFinder::new(json.clone(), path); let v = finder.find_slice(); - assert_eq!(v, vec![Slice(&json!({"second":{"active": 1}}), "$.['first']".to_string())]); + assert_eq!( + v, + vec![Slice( + &json!({"second":{"active": 1}}), + "$.['first']".to_string() + )] + ); let path: Box = Box::from( JsonPathInst::from_str("$.first[?(!@.second.active == 1 && !@.second.active == 1 || !@.second.active == 2)]") @@ -1260,7 +1278,13 @@ mod tests { ); let finder = JsonPathFinder::new(json, path); let v = finder.find_slice(); - assert_eq!(v, vec![Slice(&json!({"second":{"active": 1}}), "$.['first']".to_string())]); + assert_eq!( + v, + vec![Slice( + &json!({"second":{"active": 1}}), + "$.['first']".to_string() + )] + ); } // #[test] diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 5324914..b7d5c51 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -148,8 +148,6 @@ fn parse_filter_index(pair: Pair) -> Result) -> Result { let mut expr: Option = None; let error_message = format!("Failed to parse logical expression: {:?}", pairs); diff --git a/src/path/index.rs b/src/path/index.rs index c26f8b0..cabf9d7 100644 --- a/src/path/index.rs +++ b/src/path/index.rs @@ -207,7 +207,7 @@ pub enum FilterPath<'a> { }, Not { exp: PathInstance<'a>, - } + }, } impl<'a> FilterPath<'a> { @@ -228,7 +228,7 @@ impl<'a> FilterPath<'a> { }, FilterExpression::Not(exp) => FilterPath::Not { exp: Box::new(FilterPath::new(exp, root)), - } + }, } } fn compound(