Skip to content

Commit 89a0630

Browse files
Added negate operator '!' (#54)
* Added negate operator '!' * Fix chaining '!' by moving the logic_not before parsing logic_atom * rustfmt fail fix
1 parent c7b5524 commit 89a0630

File tree

6 files changed

+100
-10
lines changed

6 files changed

+100
-10
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ following elements:
8686

8787
| Expression sign | Description | Where to use |
8888
|-----------------|--------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
89+
| `!` | Not | To negate the expression |
8990
| `==` | Equal | To compare numbers or string literals |
9091
| `!=` | Unequal | To compare numbers or string literals in opposite way to equals |
9192
| `<` | Less | To compare numbers |
@@ -99,7 +100,7 @@ following elements:
99100
| `noneOf` | The left size has no intersection with right | |
100101
| `anyOf` | The left size has at least one intersection with right | |
101102
| `subsetOf` | The left is a subset of the right side | |
102-
| | Exists operator. | The operator checks the existence of the field depicted on the left side like that `[?(@.key.isActive)]` |
103+
| `?` | Exists operator. | The operator checks the existence of the field depicted on the left side like that `[?(@.key.isActive)]` |
103104

104105
Filter expressions can be chained using `||` and `&&` (logical or and logical and correspondingly) in the following way:
105106

src/lib.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,6 +1244,66 @@ mod tests {
12441244
);
12451245
}
12461246

1247+
#[test]
1248+
fn logical_not_exp_test() {
1249+
let json: Box<Value> = Box::new(json!({"first":{"second":{"active":1}}}));
1250+
let path: Box<JsonPathInst> = Box::from(
1251+
JsonPathInst::from_str("$.first[?([email protected]_not_exist >= 1.0)]")
1252+
.expect("the path is correct"),
1253+
);
1254+
let finder = JsonPathFinder::new(json.clone(), path);
1255+
let v = finder.find_slice();
1256+
assert_eq!(
1257+
v,
1258+
vec![Slice(
1259+
&json!({"second":{"active": 1}}),
1260+
"$.['first']".to_string()
1261+
)]
1262+
);
1263+
1264+
let path: Box<JsonPathInst> = Box::from(
1265+
JsonPathInst::from_str("$.first[?(!(@.does_not_exist >= 1.0))]")
1266+
.expect("the path is correct"),
1267+
);
1268+
let finder = JsonPathFinder::new(json.clone(), path);
1269+
let v = finder.find_slice();
1270+
assert_eq!(
1271+
v,
1272+
vec![Slice(
1273+
&json!({"second":{"active": 1}}),
1274+
"$.['first']".to_string()
1275+
)]
1276+
);
1277+
1278+
let path: Box<JsonPathInst> = Box::from(
1279+
JsonPathInst::from_str("$.first[?(!(@.second.active == 1) || @.second.active == 1)]")
1280+
.expect("the path is correct"),
1281+
);
1282+
let finder = JsonPathFinder::new(json.clone(), path);
1283+
let v = finder.find_slice();
1284+
assert_eq!(
1285+
v,
1286+
vec![Slice(
1287+
&json!({"second":{"active": 1}}),
1288+
"$.['first']".to_string()
1289+
)]
1290+
);
1291+
1292+
let path: Box<JsonPathInst> = Box::from(
1293+
JsonPathInst::from_str("$.first[?([email protected] == 1 && [email protected] == 1 || [email protected] == 2)]")
1294+
.expect("the path is correct"),
1295+
);
1296+
let finder = JsonPathFinder::new(json, path);
1297+
let v = finder.find_slice();
1298+
assert_eq!(
1299+
v,
1300+
vec![Slice(
1301+
&json!({"second":{"active": 1}}),
1302+
"$.['first']".to_string()
1303+
)]
1304+
);
1305+
}
1306+
12471307
// #[test]
12481308
// fn no_value_len_field_test() {
12491309
// let json: Box<Value> =

src/parser/grammar/json_path.pest

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ char = _{
1919
}
2020
root = {"$"}
2121
sign = { "==" | "!=" | "~=" | ">=" | ">" | "<=" | "<" | "in" | "nin" | "size" | "noneOf" | "anyOf" | "subsetOf"}
22+
not = {"!"}
2223
key_lim = {!"length()" ~ (word | ASCII_DIGIT | specs)+}
2324
key_unlim = {"[" ~ string_qt ~ "]"}
2425
key = ${key_lim | key_unlim}
@@ -38,11 +39,12 @@ slice = {start_slice? ~ col ~ end_slice? ~ step_slice? }
3839

3940
unit_keys = { string_qt ~ ("," ~ string_qt)+ }
4041
unit_indexes = { number ~ ("," ~ number)+ }
41-
filter = {"?"~ "(" ~ logic ~ ")"}
42+
filter = {"?"~ "(" ~ logic_or ~ ")"}
4243

43-
logic = {logic_and ~ ("||" ~ logic_and)*}
44-
logic_and = {logic_atom ~ ("&&" ~ logic_atom)*}
45-
logic_atom = {atom ~ (sign ~ atom)? | "(" ~ logic ~ ")"}
44+
logic_or = {logic_and ~ ("||" ~ logic_and)*}
45+
logic_and = {logic_not ~ ("&&" ~ logic_not)*}
46+
logic_not = {not? ~ logic_atom}
47+
logic_atom = {atom ~ (sign ~ atom)? | "(" ~ logic_or ~ ")"}
4648

4749
atom = {chain | string_qt | number | boolean | null}
4850

src/parser/model.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ pub enum FilterExpression {
6969
And(Box<FilterExpression>, Box<FilterExpression>),
7070
/// or with ||
7171
Or(Box<FilterExpression>, Box<FilterExpression>),
72+
/// not with !
73+
Not(Box<FilterExpression>),
7274
}
7375

7476
impl FilterExpression {

src/parser/parser.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::parser::errors::JsonPathParserError::ParserError;
22
use crate::parser::errors::{parser_err, JsonPathParserError};
3-
use crate::parser::model::FilterExpression::{And, Or};
3+
use crate::parser::model::FilterExpression::{And, Not, Or};
44
use crate::parser::model::{
55
FilterExpression, FilterSign, Function, JsonPath, JsonPathIndex, Operand,
66
};
@@ -145,10 +145,10 @@ fn parse_chain_in_operand(rule: Pair<Rule>) -> Result<Operand, JsonPathParserErr
145145
}
146146

147147
fn parse_filter_index(pair: Pair<Rule>) -> Result<JsonPathIndex, JsonPathParserError> {
148-
Ok(JsonPathIndex::Filter(parse_logic(pair.into_inner())?))
148+
Ok(JsonPathIndex::Filter(parse_logic_or(pair.into_inner())?))
149149
}
150150

151-
fn parse_logic(pairs: Pairs<Rule>) -> Result<FilterExpression, JsonPathParserError> {
151+
fn parse_logic_or(pairs: Pairs<Rule>) -> Result<FilterExpression, JsonPathParserError> {
152152
let mut expr: Option<FilterExpression> = None;
153153
let error_message = format!("Failed to parse logical expression: {:?}", pairs);
154154
for pair in pairs {
@@ -168,7 +168,7 @@ fn parse_logic_and(pairs: Pairs<Rule>) -> Result<FilterExpression, JsonPathParse
168168
let mut expr: Option<FilterExpression> = None;
169169
let error_message = format!("Failed to parse logical `and` expression: {:?}", pairs,);
170170
for pair in pairs {
171-
let next_expr = parse_logic_atom(pair.into_inner())?;
171+
let next_expr = parse_logic_not(pair.into_inner())?;
172172
match expr {
173173
None => expr = Some(next_expr),
174174
Some(e) => expr = Some(And(Box::new(e), Box::new(next_expr))),
@@ -180,10 +180,26 @@ fn parse_logic_and(pairs: Pairs<Rule>) -> Result<FilterExpression, JsonPathParse
180180
}
181181
}
182182

183+
fn parse_logic_not(mut pairs: Pairs<Rule>) -> Result<FilterExpression, JsonPathParserError> {
184+
if let Some(rule) = pairs.peek().map(|x| x.as_rule()) {
185+
match rule {
186+
Rule::not => {
187+
pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)");
188+
parse_logic_not(pairs)
189+
.map(|expr|Not(Box::new(expr)))
190+
},
191+
Rule::logic_atom => parse_logic_atom(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").into_inner()),
192+
x => Err(JsonPathParserError::UnexpectedRuleLogicError(x, pairs)),
193+
}
194+
} else {
195+
Err(JsonPathParserError::UnexpectedNoneLogicError(pairs))
196+
}
197+
}
198+
183199
fn parse_logic_atom(mut pairs: Pairs<Rule>) -> Result<FilterExpression, JsonPathParserError> {
184200
if let Some(rule) = pairs.peek().map(|x| x.as_rule()) {
185201
match rule {
186-
Rule::logic => parse_logic(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").into_inner()),
202+
Rule::logic_or => parse_logic_or(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").into_inner()),
187203
Rule::atom => {
188204
let left: Operand = parse_atom(pairs.next().unwrap())?;
189205
if pairs.peek().is_none() {

src/path/index.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,9 @@ pub enum FilterPath<'a> {
205205
left: PathInstance<'a>,
206206
right: PathInstance<'a>,
207207
},
208+
Not {
209+
exp: PathInstance<'a>,
210+
},
208211
}
209212

210213
impl<'a> FilterPath<'a> {
@@ -223,6 +226,9 @@ impl<'a> FilterPath<'a> {
223226
left: Box::new(FilterPath::new(l, root)),
224227
right: Box::new(FilterPath::new(r, root)),
225228
},
229+
FilterExpression::Not(exp) => FilterPath::Not {
230+
exp: Box::new(FilterPath::new(exp, root)),
231+
},
226232
}
227233
}
228234
fn compound(
@@ -307,6 +313,9 @@ impl<'a> FilterPath<'a> {
307313
!JsonPathValue::vec_as_data(right.find(Slice(curr_el, pref))).is_empty()
308314
}
309315
}
316+
FilterPath::Not { exp } => {
317+
JsonPathValue::vec_as_data(exp.find(Slice(curr_el, pref))).is_empty()
318+
}
310319
}
311320
}
312321
}

0 commit comments

Comments
 (0)