diff --git a/CHANGELOG.md b/CHANGELOG.md index 12b400d..a51e2bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,4 +51,8 @@ - Performance improvements - Change the contract for the struct of errors - **`0.7.0`** - - Bug fixes and api changes \ No newline at end of file + - Bug fixes and api changes +- **`0.7.1`** + - add Display to JsonPath +- **`0.7.2`** + - add JsonLike trait \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index bfca3f1..cc627cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "jsonpath-rust" description = "The library provides the basic functionality to find the set of the data according to the filtering query." -version = "0.7.0" +version = "0.7.2" authors = ["BorisZhguchev "] edition = "2021" license = "MIT" diff --git a/README.md b/README.md index 0e5fb49..3bc8070 100644 --- a/README.md +++ b/README.md @@ -244,22 +244,26 @@ This is enum type which represents: - `Slice` - a point to the passed original json - `NewValue` - a new json data that has been generated during the path( for instance length operator) -- `NoValue` - indicates there is no match between given json and jsonpath in the most cases due to absent fields or inconsistent data. +- `NoValue` - indicates there is no match between given json and jsonpath in the most cases due to absent fields or + inconsistent data. To extract data there are two methods, provided on the `value`: ```rust let v:JsonPathValue =... v.to_data(); -v.slice_or(&some_dafault_value) +v.slice_or( & some_dafault_value) ``` ### Find there are 4 different functions to find data inside a `value`. -All take references, to increase reusability. Especially json parsing and jsonpath parsing can take significant time, compared to a simple find. +All take references, to increase reusability. Especially json parsing and jsonpath parsing can take significant time, +compared to a simple find. -The methods `find`, `find_as_path`, `find_slice` and `find_slice_ptr` take the same inputs, but handle them differently depending on your usecase. They are further described in the [docs](https://docs.rs/jsonpath-rust/latest/jsonpath_rust/enum.JsonPath.html#implementations). +The methods `find`, `find_as_path`, `find_slice` and `find_slice_ptr` take the same inputs, but handle them differently +depending on your usecase. They are further described in +the [docs](https://docs.rs/jsonpath-rust/latest/jsonpath_rust/enum.JsonPath.html#implementations). ```rust use jsonpath_rust::{JsonPath, JsonPathValue}; @@ -289,12 +293,18 @@ https://docs.rs/jsonpath-rust/latest/jsonpath_rust/parser/model/enum.JsonPath.ht The internal structure of the `JsonPathIndex` can be found here: https://docs.rs/jsonpath-rust/latest/jsonpath_rust/parser/model/enum.JsonPathIndex.html +### JsonLike + +The library provides a trait `JsonLike` that can be implemented for any type. +This allows you to use the `JsonPath` methods on your own types. + ## How to contribute TBD ## How to update version - - update files - - commit them - - add tag `git tag -a v -m "message"` - - git push origin + +- update files +- commit them +- add tag `git tag -a v -m "message"` +- git push origin diff --git a/benches/regex.rs b/benches/regex.rs index 0ee1ef9..72f9090 100644 --- a/benches/regex.rs +++ b/benches/regex.rs @@ -1,6 +1,6 @@ use criterion::{criterion_group, criterion_main, Criterion}; use jsonpath_rust::{JsonPath, JsonPathQuery}; -use serde_json::json; +use serde_json::{json, Value}; use std::str::FromStr; struct SearchData { @@ -23,7 +23,7 @@ fn regex_perf_test_without_reuse() { } fn json_path_compiling() { - let _v = JsonPath::from_str(PATH).unwrap(); + let _v = JsonPath::::from_str(PATH).unwrap(); } pub fn criterion_benchmark(c: &mut Criterion) { diff --git a/src/jsonpath.rs b/src/jsonpath.rs index 94daa6d..18ba64f 100644 --- a/src/jsonpath.rs +++ b/src/jsonpath.rs @@ -1,11 +1,14 @@ use crate::path::json_path_instance; +use crate::path::JsonLike; use crate::JsonPath; use crate::JsonPathValue; use crate::JsonPathValue::NoValue; use crate::JsonPtr; -use crate::Value; -impl JsonPath { +impl JsonPath +where + T: JsonLike, +{ /// finds a slice of data in the set json. /// The result is a vector of references to the incoming structure. /// @@ -29,12 +32,11 @@ impl JsonPath { /// vec![JsonPathValue::Slice(&expected_value, expected_path)] /// ); /// ``` - pub fn find_slice<'a>(&'a self, json: &'a Value) -> Vec> { + pub fn find_slice<'a>(&'a self, json: &'a T) -> Vec> { use crate::path::Path; let instance = json_path_instance(self, json); let res = instance.find(JsonPathValue::from_root(json)); - let has_v: Vec> = - res.into_iter().filter(|v| v.has_value()).collect(); + let has_v: Vec> = res.into_iter().filter(|v| v.has_value()).collect(); if has_v.is_empty() { vec![NoValue] @@ -45,7 +47,7 @@ impl JsonPath { /// like [`Self::find_slice`] but returns a vector of [`JsonPtr`], which has no [`JsonPathValue::NoValue`]. /// if there is no match, it will return an empty vector - pub fn find_slice_ptr<'a>(&'a self, json: &'a Value) -> Vec> { + pub fn find_slice_ptr<'a>(&'a self, json: &'a T) -> Vec> { use crate::path::Path; json_path_instance(self, json) .find(JsonPathValue::from_root(json)) @@ -76,13 +78,13 @@ impl JsonPath { /// /// assert_eq!(cloned_data, Value::Array(vec![json!({"active":1})])); /// ``` - pub fn find(&self, json: &Value) -> Value { + pub fn find(&self, json: &T) -> T { let slice = self.find_slice(json); if !slice.is_empty() { if JsonPathValue::only_no_value(&slice) { - Value::Null + T::null() } else { - Value::Array( + T::array( slice .into_iter() .filter(|v| v.has_value()) @@ -91,7 +93,7 @@ impl JsonPath { ) } } else { - Value::Array(vec![]) + T::array(vec![]) } } @@ -114,8 +116,8 @@ impl JsonPath { /// let expected_path = "$.['first'].['second'][0]".to_string(); /// assert_eq!(slice_of_data, Value::Array(vec![Value::String(expected_path)])); /// ``` - pub fn find_as_path(&self, json: &Value) -> Value { - Value::Array( + pub fn find_as_path(&self, json: &T) -> T { + T::array( self.find_slice(json) .into_iter() .flat_map(|v| v.to_path()) @@ -554,7 +556,7 @@ mod tests { #[test] fn find_slice_test() { let json: Box = serde_json::from_str(template_json()).expect("to get json"); - let path: Box = Box::from( + let path: Box> = Box::from( JsonPath::try_from("$..book[?(@.author size 10)].title").expect("the path is correct"), ); let v = path.find_slice(&json); @@ -565,7 +567,7 @@ mod tests { #[test] fn find_in_array_test() { let json: Box = Box::new(json!([{"verb": "TEST"}, {"verb": "RUN"}])); - let path: Box = + let path: Box> = Box::from(JsonPath::try_from("$.[?(@.verb == 'TEST')]").expect("the path is correct")); let v = path.find_slice(&json); let js = json!({"verb":"TEST"}); @@ -576,7 +578,7 @@ mod tests { fn length_test() { let json: Box = Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); - let path: Box = Box::from( + let path: Box> = Box::from( JsonPath::try_from("$.[?(@.verb == 'TEST')].length()").expect("the path is correct"), ); let v = path.find(&json); @@ -585,14 +587,14 @@ mod tests { let json: Box = Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); - let path: Box = + let path: Box> = Box::from(JsonPath::try_from("$.length()").expect("the path is correct")); assert_eq!(path.find(&json), json!([3])); // length of search following the wildcard returns correct result let json: Box = Box::new(json!([{"verb": "TEST"},{"verb": "TEST","x":3}, {"verb": "RUN"}])); - let path: Box = Box::from( + let path: Box> = Box::from( JsonPath::try_from("$.[?(@.verb == 'TEST')].[*].length()") .expect("the path is correct"), ); @@ -600,33 +602,33 @@ mod tests { // length of object returns 0 let json: Box = Box::new(json!({"verb": "TEST"})); - let path: Box = + let path: Box> = Box::from(JsonPath::try_from("$.length()").expect("the path is correct")); assert_eq!(path.find(&json), Value::Null); // length of integer returns null let json: Box = Box::new(json!(1)); - let path: Box = + let path: Box> = Box::from(JsonPath::try_from("$.length()").expect("the path is correct")); assert_eq!(path.find(&json), Value::Null); // length of array returns correct result let json: Box = Box::new(json!([[1], [2], [3]])); - let path: Box = + let path: Box> = Box::from(JsonPath::try_from("$.length()").expect("the path is correct")); assert_eq!(path.find(&json), json!([3])); // path does not exist returns length null let json: Box = Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); - let path: Box = + let path: Box> = Box::from(JsonPath::try_from("$.not.exist.length()").expect("the path is correct")); assert_eq!(path.find(&json), Value::Null); // seraching one value returns correct length let json: Box = Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); - let path: Box = Box::from( + let path: Box> = Box::from( JsonPath::try_from("$.[?(@.verb == 'RUN')].length()").expect("the path is correct"), ); @@ -637,7 +639,7 @@ mod tests { // searching correct path following unexisting key returns length 0 let json: Box = Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); - let path: Box = Box::from( + let path: Box> = Box::from( JsonPath::try_from("$.[?(@.verb == 'RUN')].key123.length()") .expect("the path is correct"), ); @@ -649,7 +651,7 @@ mod tests { // fetching first object returns length null let json: Box = Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); - let path: Box = + let path: Box> = Box::from(JsonPath::try_from("$.[0].length()").expect("the path is correct")); let v = path.find(&json); @@ -658,7 +660,7 @@ mod tests { // length on fetching the index after search gives length of the object (array) let json: Box = Box::new(json!([{"prop": [["a", "b", "c"], "d"]}])); - let path: Box = Box::from( + let path: Box> = Box::from( JsonPath::try_from("$.[?(@.prop)].prop.[0].length()").expect("the path is correct"), ); @@ -668,7 +670,7 @@ mod tests { // length on fetching the index after search gives length of the object (string) let json: Box = Box::new(json!([{"prop": [["a", "b", "c"], "d"]}])); - let path: Box = Box::from( + let path: Box> = Box::from( JsonPath::try_from("$.[?(@.prop)].prop.[1].length()").expect("the path is correct"), ); @@ -683,7 +685,7 @@ mod tests { "field":"field", })); - let path: Box = + let path: Box> = Box::from(JsonPath::try_from("$.field[1]").expect("the path is correct")); let v = path.find_slice(&json); assert_eq!(v, vec![NoValue]); @@ -692,7 +694,7 @@ mod tests { "field":[0], })); - let path: Box = + let path: Box> = Box::from(JsonPath::try_from("$.field[1]").expect("the path is correct")); let v = path.find_slice(&json); assert_eq!(v, vec![NoValue]); @@ -704,7 +706,7 @@ mod tests { "field":"field", })); - let path: Box = + let path: Box> = Box::from(JsonPath::try_from("$.field[?(@ == 0)]").expect("the path is correct")); let v = path.find_slice(&json); assert_eq!(v, vec![NoValue]); @@ -716,7 +718,7 @@ mod tests { "field":[{"f":1},{"f":0}], })); - let path: Box = + let path: Box> = Box::from(JsonPath::try_from("$.field[?(@.f_ == 0)]").expect("the path is correct")); let v = path.find_slice(&json); assert_eq!(v, vec![NoValue]); @@ -728,7 +730,7 @@ mod tests { "field":[{"f":1},{"f":{"f_":1}}], })); - let path: Box = + let path: Box> = Box::from(JsonPath::try_from("$..f_").expect("the path is correct")); let v = path.find_slice(&json); assert_eq!( @@ -743,12 +745,12 @@ mod tests { "field":{"field":[1]}, })); - let path: Box = + let path: Box> = Box::from(JsonPath::try_from("$.field_.field").expect("the path is correct")); let v = path.find_slice(&json); assert_eq!(v, vec![NoValue]); - let path: Box = Box::from( + let path: Box> = Box::from( JsonPath::try_from("$.field_.field[?(@ == 1)]").expect("the path is correct"), ); let v = path.find_slice(&json); @@ -760,7 +762,7 @@ mod tests { // searching unexisting value returns length 0 let json: Box = Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); - let path: Box = Box::from( + let path: Box> = Box::from( JsonPath::try_from("$.[?(@.verb == \"RUN1\")]").expect("the path is correct"), ); let v = path.find(&json); @@ -774,7 +776,7 @@ mod tests { "field":{"field":1}, })); - let path: Box = + let path: Box> = Box::from(JsonPath::try_from("$.field.field.length()").expect("the path is correct")); let v = path.find_slice(&json); assert_eq!(v, vec![NoValue]); @@ -782,7 +784,7 @@ mod tests { let json: Box = Box::new(json!({ "field":[{"a":1},{"a":1}], })); - let path: Box = Box::from( + let path: Box> = Box::from( JsonPath::try_from("$.field[?(@.a == 0)].f.length()").expect("the path is correct"), ); let v = path.find_slice(&json); @@ -813,14 +815,14 @@ mod tests { fn logical_exp_test() { let json: Box = Box::new(json!({"first":{"second":[{"active":1},{"passive":1}]}})); - let path: Box = Box::from( + let path: Box> = Box::from( JsonPath::try_from("$.first[?(@.does_not_exist && @.does_not_exist >= 1.0)]") .expect("the path is correct"), ); let v = path.find_slice(&json); assert_eq!(v, vec![NoValue]); - let path: Box = Box::from( + let path: Box> = Box::from( JsonPath::try_from("$.first[?(@.does_not_exist >= 1.0)]").expect("the path is correct"), ); let v = path.find_slice(&json); @@ -833,7 +835,7 @@ mod tests { "author":"abcd(Rees)", })); - let path: Box = Box::from( + let path: Box> = Box::from( JsonPath::try_from("$.[?(@.author ~= '(?i)d\\(Rees\\)')]") .expect("the path is correct"), ); @@ -846,7 +848,7 @@ mod tests { #[test] fn logical_not_exp_test() { let json: Box = Box::new(json!({"first":{"second":{"active":1}}})); - let path: Box = Box::from( + let path: Box> = Box::from( JsonPath::try_from("$.first[?(!@.does_not_exist >= 1.0)]") .expect("the path is correct"), ); @@ -859,7 +861,7 @@ mod tests { )] ); - let path: Box = Box::from( + let path: Box> = Box::from( JsonPath::try_from("$.first[?(!(@.does_not_exist >= 1.0))]") .expect("the path is correct"), ); @@ -872,7 +874,7 @@ mod tests { )] ); - let path: Box = Box::from( + let path: Box> = Box::from( JsonPath::try_from("$.first[?(!(@.second.active == 1) || @.second.active == 1)]") .expect("the path is correct"), ); @@ -885,7 +887,7 @@ mod tests { )] ); - let path: Box = Box::from( + let path: Box> = Box::from( JsonPath::try_from("$.first[?(!@.second.active == 1 && !@.second.active == 1 || !@.second.active == 2)]") .expect("the path is correct"), ); diff --git a/src/lib.rs b/src/lib.rs index 03baf25..1d11ede 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -309,17 +309,17 @@ impl<'a, Data: Clone + Debug + Default> JsonPathValue<'a, Data> { } impl<'a, Data> JsonPathValue<'a, Data> { - fn only_no_value(input: &[JsonPathValue<'a, Data>]) -> bool { + pub fn only_no_value(input: &[JsonPathValue<'a, Data>]) -> bool { !input.is_empty() && input.iter().filter(|v| v.has_value()).count() == 0 } - fn map_vec(data: Vec<(&'a Data, JsPathStr)>) -> Vec> { + pub fn map_vec(data: Vec<(&'a Data, JsPathStr)>) -> Vec> { data.into_iter() .map(|(data, pref)| Slice(data, pref)) .collect() } - fn map_slice(self, mapper: F) -> Vec> + pub fn map_slice(self, mapper: F) -> Vec> where F: FnOnce(&'a Data, JsPathStr) -> Vec<(&'a Data, JsPathStr)>, { @@ -334,7 +334,7 @@ impl<'a, Data> JsonPathValue<'a, Data> { } } - fn flat_map_slice(self, mapper: F) -> Vec> + pub fn flat_map_slice(self, mapper: F) -> Vec> where F: FnOnce(&'a Data, JsPathStr) -> Vec>, { @@ -378,16 +378,23 @@ impl<'a, Data> JsonPathValue<'a, Data> { #[cfg(test)] mod tests { - // #[test] - // fn no_value_len_field_test() { - // let json: Box = - // Box::new(json!([{"verb": "TEST","a":[1,2,3]},{"verb": "TEST","a":[1,2,3]},{"verb": "TEST"}, {"verb": "RUN"}])); - // let path: Box = Box::from( - // JsonPath::from_str("$.[?(@.verb == 'TEST')].a.length()") - // .expect("the path is correct"), - // ); - // - // let v = json.find_slice(&path); - // assert_eq!(v, vec![NewValue(json!(3))]); - // } + use serde_json::Value; + + use crate::JsonPath; + use std::str::FromStr; + + #[test] + fn to_string_test() { + let path: Box> = Box::from( + JsonPath::from_str( + "$.['a'].a..book[1:3][*][1]['a','b'][?(@)][?(@.verb == 'TEST')].a.length()", + ) + .unwrap(), + ); + + assert_eq!( + path.to_string(), + "$.'a'.'a'..book[1:3:1][*][1]['a','b'][?(@ exists )][?(@.'verb' == \"TEST\")].'a'.length()" + ); + } } diff --git a/src/parser/macros.rs b/src/parser/macros.rs index 284d04b..9bc732d 100644 --- a/src/parser/macros.rs +++ b/src/parser/macros.rs @@ -72,8 +72,9 @@ macro_rules! chain { /// ```rust /// use jsonpath_rust::{path, JsonPath}; /// use std::str::FromStr; +/// use serde_json::Value; /// -/// let path = JsonPath::from_str(".abc.*").unwrap(); +/// let path:JsonPath = JsonPath::from_str(".abc.*").unwrap(); /// let path2 = JsonPath::Chain(vec![path!("abc"), path!(*)]); /// assert_eq!(path, path2); /// ``` diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 65cacde..36db3bd 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -9,4 +9,5 @@ pub(crate) mod parser; pub use errors::JsonPathParserError; pub use model::JsonPath; +pub use model::JsonPathIndex; pub use parser::{parse_json_path, Rule}; diff --git a/src/parser/model.rs b/src/parser/model.rs index 90da1ad..2f40e94 100644 --- a/src/parser/model.rs +++ b/src/parser/model.rs @@ -1,29 +1,32 @@ -use super::parse_json_path; use serde_json::Value; -use std::{convert::TryFrom, str::FromStr}; + +use crate::path::JsonLike; use super::errors::JsonPathParserError; +use super::parse_json_path; +use std::fmt::{Display, Formatter}; +use std::{convert::TryFrom, str::FromStr}; /// The basic structures for parsing json paths. /// The common logic of the structures pursues to correspond the internal parsing structure. /// /// usually it's created by using [`FromStr`] or [`TryFrom<&str>`] #[derive(Debug, Clone)] -pub enum JsonPath { +pub enum JsonPath { /// The $ operator Root, /// Field represents key Field(String), /// The whole chain of the path. - Chain(Vec), + Chain(Vec>), /// The .. operator Descent(String), /// The ..* operator DescentW, /// The indexes for array - Index(JsonPathIndex), + Index(JsonPathIndex), /// The @ operator - Current(Box), + Current(Box>), /// The * operator Wildcard, /// The item uses to define the unresolved state @@ -32,7 +35,30 @@ pub enum JsonPath { Fn(Function), } -impl TryFrom<&str> for JsonPath { +impl Display for JsonPath { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let str = match self { + JsonPath::Root => "$".to_string(), + JsonPath::Field(e) => format!(".'{}'", e), + JsonPath::Chain(elems) => elems.iter().map(ToString::to_string).collect::(), + JsonPath::Descent(e) => { + format!("..{}", e) + } + JsonPath::DescentW => "..*".to_string(), + JsonPath::Index(e) => e.to_string(), + JsonPath::Current(e) => format!("@{}", e), + JsonPath::Wildcard => "[*]".to_string(), + JsonPath::Empty => "".to_string(), + JsonPath::Fn(e) => format!(".{}", e), + }; + write!(f, "{}", str) + } +} + +impl TryFrom<&str> for JsonPath +where + T: JsonLike, +{ type Error = JsonPathParserError; /// Parses a string into a [JsonPath]. @@ -45,7 +71,10 @@ impl TryFrom<&str> for JsonPath { } } -impl FromStr for JsonPath { +impl FromStr for JsonPath +where + T: JsonLike, +{ type Err = JsonPathParserError; /// Parses a string into a [JsonPath]. @@ -63,34 +92,97 @@ pub enum Function { /// length() Length, } + +impl Display for Function { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let str = match self { + Function::Length => "length()".to_string(), + }; + write!(f, "{}", str) + } +} + #[derive(Debug, Clone)] -pub enum JsonPathIndex { +pub enum JsonPathIndex { /// A single element in array - Single(Value), + Single(T), /// Union represents a several indexes - UnionIndex(Vec), + UnionIndex(Vec), /// Union represents a several keys UnionKeys(Vec), /// DEfault slice where the items are start/end/step respectively Slice(i32, i32, usize), /// Filter ?() - Filter(FilterExpression), + Filter(FilterExpression), +} + +impl Display for JsonPathIndex { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let str = match self { + JsonPathIndex::Single(e) => format!("[{}]", e), + JsonPathIndex::UnionIndex(elems) => { + format!( + "[{}]", + elems + .iter() + .map(ToString::to_string) + .collect::>() + .join(",") + ) + } + JsonPathIndex::UnionKeys(elems) => { + format!( + "[{}]", + elems + .iter() + .map(|el| format!("'{}'", el)) + .collect::>() + .join(",") + ) + } + JsonPathIndex::Slice(s, e, st) => { + format!("[{}:{}:{}]", s, e, st) + } + JsonPathIndex::Filter(filter) => format!("[?({})]", filter), + }; + write!(f, "{}", str) + } } #[derive(Debug, Clone, PartialEq)] -pub enum FilterExpression { +pub enum FilterExpression { /// a single expression like a > 2 - Atom(Operand, FilterSign, Operand), + Atom(Operand, FilterSign, Operand), /// and with && - And(Box, Box), + And(Box>, Box>), /// or with || - Or(Box, Box), + Or(Box>, Box>), /// not with ! - Not(Box), + Not(Box>), +} + +impl Display for FilterExpression { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let str = match self { + FilterExpression::Atom(left, sign, right) => { + format!("{} {} {}", left, sign, right) + } + FilterExpression::And(left, right) => { + format!("{} && {}", left, right) + } + FilterExpression::Or(left, right) => { + format!("{} || {}", left, right) + } + FilterExpression::Not(expr) => { + format!("!{}", expr) + } + }; + write!(f, "{}", str) + } } -impl FilterExpression { - pub fn exists(op: Operand) -> Self { +impl FilterExpression { + pub fn exists(op: Operand) -> Self { FilterExpression::Atom( op, FilterSign::Exists, @@ -101,14 +193,24 @@ impl FilterExpression { /// Operand for filtering expressions #[derive(Debug, Clone)] -pub enum Operand { - Static(Value), - Dynamic(Box), +pub enum Operand { + Static(T), + Dynamic(Box>), +} + +impl Display for Operand { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let str = match self { + Operand::Static(e) => e.to_string(), + Operand::Dynamic(e) => e.to_string(), + }; + write!(f, "{}", str) + } } #[allow(dead_code)] -impl Operand { - pub fn val(v: Value) -> Self { +impl Operand { + pub fn val(v: T) -> Self { Operand::Static(v) } } @@ -132,6 +234,28 @@ pub enum FilterSign { Exists, } +impl Display for FilterSign { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let str = match self { + FilterSign::Equal => "==", + FilterSign::Unequal => "!=", + FilterSign::Less => "<", + FilterSign::Greater => ">", + FilterSign::LeOrEq => "<=", + FilterSign::GrOrEq => ">=", + FilterSign::Regex => "~=", + FilterSign::In => "in", + FilterSign::Nin => "nin", + FilterSign::Size => "size", + FilterSign::NoneOf => "noneOf", + FilterSign::AnyOf => "anyOf", + FilterSign::SubSetOf => "subsetOf", + FilterSign::Exists => "exists", + }; + write!(f, "{}", str) + } +} + impl FilterSign { pub fn new(key: &str) -> Self { match key { @@ -153,7 +277,10 @@ impl FilterSign { } } -impl PartialEq for JsonPath { +impl PartialEq for JsonPath +where + T: PartialEq, +{ fn eq(&self, other: &Self) -> bool { match (self, other) { (JsonPath::Root, JsonPath::Root) => true, @@ -171,7 +298,10 @@ impl PartialEq for JsonPath { } } -impl PartialEq for JsonPathIndex { +impl PartialEq for JsonPathIndex +where + T: PartialEq, +{ fn eq(&self, other: &Self) -> bool { match (self, other) { (JsonPathIndex::Slice(s1, e1, st1), JsonPathIndex::Slice(s2, e2, st2)) => { @@ -190,7 +320,10 @@ impl PartialEq for JsonPathIndex { } } -impl PartialEq for Operand { +impl PartialEq for Operand +where + T: PartialEq, +{ fn eq(&self, other: &Self) -> bool { match (self, other) { (Operand::Static(v1), Operand::Static(v2)) => v1 == v2, diff --git a/src/parser/parser.rs b/src/parser/parser.rs index d99f8f9..355f619 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -5,9 +5,9 @@ use crate::parser::model::FilterExpression::{And, Not, Or}; use crate::parser::model::{ FilterExpression, FilterSign, Function, JsonPath, JsonPathIndex, Operand, }; +use crate::path::JsonLike; use pest::iterators::{Pair, Pairs}; use pest::Parser; -use serde_json::Value; #[derive(Parser)] #[grammar = "parser/grammar/json_path.pest"] @@ -18,7 +18,10 @@ struct JsonPathParser; /// # Errors /// /// Returns a variant of [JsonPathParserError] if the parsing operation failed. -pub fn parse_json_path(jp_str: &str) -> Result { +pub fn parse_json_path(jp_str: &str) -> Result, JsonPathParserError> +where + T: JsonLike, +{ JsonPathParser::parse(Rule::path, jp_str) .map_err(Box::new)? .next() @@ -31,7 +34,10 @@ pub fn parse_json_path(jp_str: &str) -> Result { /// # Errors /// /// Returns a variant of [JsonPathParserError] if the parsing operation failed -fn parse_internal(rule: Pair) -> Result { +fn parse_internal(rule: Pair<'_, Rule>) -> Result, JsonPathParserError> +where + T: JsonLike, +{ match rule.as_rule() { Rule::path => rule .into_inner() @@ -75,7 +81,7 @@ fn parse_key(rule: Pair) -> Result, JsonPathParserError> { parsed_key } -fn parse_slice(pairs: Pairs) -> Result { +fn parse_slice(pairs: Pairs) -> Result, JsonPathParserError> { let mut start = 0; let mut end = 0; let mut step = 1; @@ -90,7 +96,7 @@ fn parse_slice(pairs: Pairs) -> Result Ok(JsonPathIndex::Slice(start, end, step)) } -fn parse_unit_keys(pairs: Pairs) -> Result { +fn parse_unit_keys(pairs: Pairs) -> Result, JsonPathParserError> { let mut keys = vec![]; for pair in pairs { @@ -99,26 +105,35 @@ fn parse_unit_keys(pairs: Pairs) -> Result Result { +fn number_to_value(number: &str) -> Result +where + T: From + From, +{ match number .parse::() .ok() - .map(Value::from) - .or_else(|| number.parse::().ok().map(Value::from)) + .map(T::from) + .or_else(|| number.parse::().ok().map(T::from)) { Some(value) => Ok(value), None => Err(JsonPathParserError::InvalidNumber(number.to_string())), } } -fn bool_to_value(boolean: &str) -> Value { +fn bool_to_value(boolean: &str) -> T +where + T: From, +{ boolean .parse::() - .map(Value::from) + .map(T::from) .expect("unreachable: according to .pest this is either `true` or `false`") } -fn parse_unit_indexes(pairs: Pairs) -> Result { +fn parse_unit_indexes(pairs: Pairs) -> Result, JsonPathParserError> +where + T: From + From, +{ let mut keys = vec![]; for pair in pairs { @@ -127,20 +142,21 @@ fn parse_unit_indexes(pairs: Pairs) -> Result) -> Result { - let parsed_chain = match parse_internal(rule)? { +fn parse_chain_in_operand(rule: Pair<'_, Rule>) -> Result, JsonPathParserError> +where + T: JsonLike, +{ + let parsed_chain = match parse_internal::(rule)? { JsonPath::Chain(elems) => { if elems.len() == 1 { match elems.first() { Some(JsonPath::Index(JsonPathIndex::UnionKeys(keys))) => { - Operand::val(Value::from(keys.clone())) + Operand::val(T::from(keys.clone())) } Some(JsonPath::Index(JsonPathIndex::UnionIndex(keys))) => { - Operand::val(Value::from(keys.clone())) - } - Some(JsonPath::Field(f)) => { - Operand::val(Value::Array(vec![Value::from(f.clone())])) + Operand::val(T::from(keys.clone())) } + Some(JsonPath::Field(f)) => Operand::val(T::from(vec![f.to_string()])), _ => Operand::Dynamic(Box::new(JsonPath::Chain(elems))), } } else { @@ -152,12 +168,18 @@ fn parse_chain_in_operand(rule: Pair) -> Result) -> Result { +fn parse_filter_index(pair: Pair<'_, Rule>) -> Result, JsonPathParserError> +where + T: JsonLike, +{ Ok(JsonPathIndex::Filter(parse_logic_or(pair.into_inner())?)) } -fn parse_logic_or(pairs: Pairs) -> Result { - let mut expr: Option = None; +fn parse_logic_or(pairs: Pairs<'_, Rule>) -> Result, JsonPathParserError> +where + T: JsonLike, +{ + let mut expr: Option> = None; // only possible for the loop not to produce any value (except Errors) if pairs.len() == 0 { return Err(JsonPathParserError::UnexpectedNoneLogicError( @@ -175,8 +197,11 @@ fn parse_logic_or(pairs: Pairs) -> Result) -> Result { - let mut expr: Option = None; +fn parse_logic_and(pairs: Pairs<'_, Rule>) -> Result, JsonPathParserError> +where + T: JsonLike, +{ + let mut expr: Option> = None; // only possible for the loop not to produce any value (except Errors) if pairs.len() == 0 { return Err(JsonPathParserError::UnexpectedNoneLogicError( @@ -194,7 +219,12 @@ fn parse_logic_and(pairs: Pairs) -> Result) -> Result { +fn parse_logic_not( + mut pairs: Pairs<'_, Rule>, +) -> Result, JsonPathParserError> +where + T: JsonLike, +{ if let Some(rule) = pairs.peek().map(|x| x.as_rule()) { match rule { Rule::not => { @@ -213,17 +243,22 @@ fn parse_logic_not(mut pairs: Pairs) -> Result) -> Result { +fn parse_logic_atom( + mut pairs: Pairs<'_, Rule>, +) -> Result, JsonPathParserError> +where + T: JsonLike, +{ if let Some(rule) = pairs.peek().map(|x| x.as_rule()) { match rule { 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())?; + let left: Operand = parse_atom(pairs.next().unwrap())?; if pairs.peek().is_none() { Ok(FilterExpression::exists(left)) } else { let sign: FilterSign = FilterSign::new(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").as_str()); - let right: Operand = + let right: Operand = parse_atom(pairs.next().expect("unreachable in arithemetic: should have a right side operand"))?; Ok(FilterExpression::Atom(left, sign, right)) } @@ -238,19 +273,25 @@ fn parse_logic_atom(mut pairs: Pairs) -> Result) -> Result { +fn parse_atom(rule: Pair<'_, Rule>) -> Result, JsonPathParserError> +where + T: JsonLike, +{ let atom = down(rule.clone())?; let parsed_atom = match atom.as_rule() { Rule::number => Operand::Static(number_to_value(rule.as_str())?), - Rule::string_qt => Operand::Static(Value::from(down(atom)?.as_str())), + Rule::string_qt => Operand::Static(T::from(down(atom)?.as_str())), Rule::chain => parse_chain_in_operand(down(rule)?)?, Rule::boolean => Operand::Static(bool_to_value(rule.as_str())), - _ => Operand::Static(Value::Null), + _ => Operand::Static(T::null()), }; Ok(parsed_atom) } -fn parse_index(rule: Pair) -> Result { +fn parse_index(rule: Pair<'_, Rule>) -> Result, JsonPathParserError> +where + T: JsonLike, +{ let next = down(rule)?; let parsed_index = match next.as_rule() { Rule::unsigned => JsonPathIndex::Single(number_to_value(next.as_str())?), @@ -276,18 +317,21 @@ mod tests { use super::*; use crate::parser::macros::{chain, filter, idx, op}; use crate::path; - use serde_json::json; + use serde_json::{json, Value}; use std::panic; fn test_failed(input: &str) { - match parse_json_path(input) { + match parse_json_path::(input) { Ok(elem) => panic!("should be false but got {:?}", elem), Err(e) => println!("{}", e), } } - fn test(input: &str, expected: Vec) { - match parse_json_path(input) { + fn test(input: &str, expected: Vec>) + where + T: JsonLike, + { + match parse_json_path::(input) { Ok(JsonPath::Chain(elems)) => assert_eq!(elems, expected), Ok(e) => panic!("unexpected value {:?}", e), Err(e) => { @@ -298,7 +342,7 @@ mod tests { #[test] fn path_test() { - test("$.k.['k']['k']..k..['k'].*.[*][*][1][1,2]['k','k'][:][10:][:10][10:10:10][?(@)][?(@.abc >= 10)]", + test::("$.k.['k']['k']..k..['k'].*.[*][*][1][1,2]['k','k'][:][10:][:10][10:10:10][?(@)][?(@.abc >= 10)]", vec![ path!($), path!("k"), @@ -319,7 +363,7 @@ mod tests { path!(idx!(?filter!(op!(chain!(path!(@path!()))), "exists", op!(path!())))), path!(idx!(?filter!(op!(chain!(path!(@,path!("abc")))), ">=", op!(10)))), ]); - test( + test::( "$..*[?(@.isbn)].title", vec![ // Root, DescentW, Index(Filter(Atom(Dynamic(Chain([Current(Chain([Field("isbn")]))])), Exists, Dynamic(Empty)))), Field("title") @@ -333,18 +377,18 @@ mod tests { #[test] fn descent_test() { - test("..abc", vec![path!(.."abc")]); - test("..['abc']", vec![path!(.."abc")]); + test::("..abc", vec![path!(.."abc")]); + test::("..['abc']", vec![path!(.."abc")]); test_failed("...['abc']"); test_failed("...abc"); } #[test] fn field_test() { - test(".abc", vec![path!("abc")]); - test(".['abc']", vec![path!("abc")]); - test("['abc']", vec![path!("abc")]); - test(".['abc\\\"abc']", vec![path!("abc\\\"abc")]); + test::(".abc", vec![path!("abc")]); + test::(".['abc']", vec![path!("abc")]); + test::("['abc']", vec![path!("abc")]); + test::(".['abc\\\"abc']", vec![path!("abc\\\"abc")]); test_failed(".abc()abc"); test_failed("..[abc]"); test_failed(".'abc'"); @@ -352,46 +396,46 @@ mod tests { #[test] fn wildcard_test() { - test(".*", vec![path!(*)]); - test(".[*]", vec![path!(*)]); - test(".abc.*", vec![path!("abc"), path!(*)]); - test(".abc.[*]", vec![path!("abc"), path!(*)]); - test(".abc[*]", vec![path!("abc"), path!(*)]); - test("..*", vec![path!(..*)]); + test::(".*", vec![path!(*)]); + test::(".[*]", vec![path!(*)]); + test::(".abc.*", vec![path!("abc"), path!(*)]); + test::(".abc.[*]", vec![path!("abc"), path!(*)]); + test::(".abc[*]", vec![path!("abc"), path!(*)]); + test::("..*", vec![path!(..*)]); test_failed("abc*"); } #[test] fn index_single_test() { - test("[1]", vec![path!(idx!(1))]); + test::("[1]", vec![path!(idx!(1))]); test_failed("[-1]"); test_failed("[1a]"); } #[test] fn index_slice_test() { - test("[1:1000:10]", vec![path!(idx!([1; 1000; 10]))]); - test("[:1000:10]", vec![path!(idx!([0; 1000; 10]))]); - test("[:1000]", vec![path!(idx!([;1000;]))]); - test("[:]", vec![path!(idx!([;;]))]); - test("[::10]", vec![path!(idx!([;;10]))]); + test::("[1:1000:10]", vec![path!(idx!([1; 1000; 10]))]); + test::("[:1000:10]", vec![path!(idx!([0; 1000; 10]))]); + test::("[:1000]", vec![path!(idx!([;1000;]))]); + test::("[:]", vec![path!(idx!([;;]))]); + test::("[::10]", vec![path!(idx!([;;10]))]); test_failed("[::-1]"); test_failed("[:::0]"); } #[test] fn index_union_test() { - test("[1,2,3]", vec![path!(idx!(idx 1,2,3))]); - test("['abc','bcd']", vec![path!(idx!("abc", "bcd"))]); + test::("[1,2,3]", vec![path!(idx!(idx 1,2,3))]); + test::("['abc','bcd']", vec![path!(idx!("abc", "bcd"))]); test_failed("[]"); - test("[-1,-2]", vec![path!(idx!(idx - 1, -2))]); + test::("[-1,-2]", vec![path!(idx!(idx - 1, -2))]); test_failed("[abc,bcd]"); - test("[\"abc\",\"bcd\"]", vec![path!(idx!("abc", "bcd"))]); + test::("[\"abc\",\"bcd\"]", vec![path!(idx!("abc", "bcd"))]); } #[test] fn array_start_test() { - test( + test::( "$.[?(@.verb== \"TEST\")]", vec![ path!($), @@ -402,7 +446,7 @@ mod tests { #[test] fn logical_filter_test() { - test( + test::( "$.[?(@.verb == 'T' || @.size > 0 && @.size < 10)]", vec![ path!($), @@ -418,7 +462,7 @@ mod tests { ))), ], ); - test( + test::( "$.[?((@.verb == 'T' || @.size > 0) && @.size < 10)]", vec![ path!($), @@ -434,7 +478,7 @@ mod tests { ))), ], ); - test( + test::( "$.[?(@.verb == 'T' || @.size > 0 && @.size < 10 && @.elem == 0)]", vec![ path!($), @@ -458,33 +502,33 @@ mod tests { #[test] fn index_filter_test() { - test( + test::( "[?('abc' == 'abc')]", vec![path!(idx!(?filter!(op!("abc"),"==",op!("abc") )))], ); - test( + test::( "[?('abc' == 1)]", vec![path!(idx!(?filter!( op!("abc"),"==",op!(1))))], ); - test( + test::( "[?('abc' == true)]", vec![path!(idx!(?filter!( op!("abc"),"==",op!(true))))], ); - test( + test::( "[?('abc' == null)]", vec![path!( idx!(?filter!( op!("abc"),"==",Operand::Static(Value::Null))) )], ); - test( + test::( "[?(@.abc in ['abc','bcd'])]", vec![path!( idx!(?filter!(op!(chain!(path!(@,path!("abc")))),"in",Operand::val(json!(["abc","bcd"])))) )], ); - test( + test::( "[?(@.abc.[*] in ['abc','bcd'])]", vec![path!(idx!(?filter!( op!(chain!(path!(@,path!("abc"), path!(*)))), @@ -492,7 +536,7 @@ mod tests { op!(s json!(["abc","bcd"])) )))], ); - test( + test::( "[?(@.[*]..next in ['abc','bcd'])]", vec![path!(idx!(?filter!( op!(chain!(path!(@,path!(*), path!(.."next")))), @@ -501,7 +545,7 @@ mod tests { )))], ); - test( + test::( "[?(@[1] in ['abc','bcd'])]", vec![path!(idx!(?filter!( op!(chain!(path!(@,path!(idx!(1))))), @@ -509,19 +553,19 @@ mod tests { op!(s json!(["abc","bcd"])) )))], ); - test( + test::( "[?(@ == 'abc')]", vec![path!(idx!(?filter!( op!(chain!(path!(@path!()))),"==",op!("abc") )))], ); - test( + test::( "[?(@ subsetOf ['abc'])]", vec![path!(idx!(?filter!( op!(chain!(path!(@path!()))),"subsetOf",op!(s json!(["abc"])) )))], ); - test( + test::( "[?(@[1] subsetOf ['abc','abc'])]", vec![path!(idx!(?filter!( op!(chain!(path!(@,path!(idx!(1))))), @@ -529,7 +573,7 @@ mod tests { op!(s json!(["abc","abc"])) )))], ); - test( + test::( "[?(@ subsetOf [1,2,3])]", vec![path!(idx!(?filter!( op!(chain!(path!(@path!()))),"subsetOf",op!(s json!([1,2,3])) @@ -543,12 +587,12 @@ mod tests { #[test] fn fn_size_test() { - test( + test::( "$.k.length()", vec![path!($), path!("k"), JsonPath::Fn(Function::Length)], ); - test( + test::( "$.k.length.field", vec![path!($), path!("k"), path!("length"), path!("field")], ) @@ -556,7 +600,7 @@ mod tests { #[test] fn parser_error_test_invalid_rule() { - let result = parse_json_path("notapath"); + let result = parse_json_path::("notapath"); assert!(result.is_err()); assert!(result @@ -568,7 +612,7 @@ mod tests { #[test] fn parser_error_test_empty_rule() { - let result = parse_json_path(""); + let result = parse_json_path::(""); assert!(result.is_err()); assert!(result diff --git a/src/path/index.rs b/src/path/index.rs index cbb00ea..bb33ce8 100644 --- a/src/path/index.rs +++ b/src/path/index.rs @@ -1,28 +1,30 @@ +use std::fmt::Debug; + use crate::jsp_idx; use crate::parser::model::{FilterExpression, FilterSign, JsonPath}; -use crate::path::json::*; + use crate::path::top::ObjectField; use crate::path::{json_path_instance, process_operand, JsonPathValue, Path, PathInstance}; use crate::JsonPathValue::{NoValue, Slice}; -use serde_json::value::Value::Array; -use serde_json::Value; -use super::TopPaths; +use super::{JsonLike, TopPaths}; /// process the slice like [start:end:step] #[derive(Debug)] -pub(crate) struct ArraySlice { +pub struct ArraySlice { start_index: i32, end_index: i32, step: usize, + _t: std::marker::PhantomData, } -impl ArraySlice { - pub(crate) fn new(start_index: i32, end_index: i32, step: usize) -> ArraySlice { +impl ArraySlice { + pub(crate) fn new(start_index: i32, end_index: i32, step: usize) -> Self { ArraySlice { start_index, end_index, step, + _t: std::marker::PhantomData, } } @@ -54,9 +56,9 @@ impl ArraySlice { } } - fn process<'a, T>(&self, elements: &'a [T]) -> Vec<(&'a T, usize)> { + fn process<'a, F>(&self, elements: &'a [F]) -> Vec<(&'a F, usize)> { let len = elements.len() as i32; - let mut filtered_elems: Vec<(&'a T, usize)> = vec![]; + let mut filtered_elems: Vec<(&'a F, usize)> = vec![]; match (self.start(len), self.end(len)) { (Some(start_idx), Some(end_idx)) => { let end_idx = if end_idx == 0 { @@ -76,8 +78,11 @@ impl ArraySlice { } } -impl<'a> Path<'a> for ArraySlice { - type Data = Value; +impl<'a, T> Path<'a> for ArraySlice +where + T: JsonLike, +{ + type Data = T; fn find(&self, input: JsonPathValue<'a, Self::Data>) -> Vec> { input.flat_map_slice(|data, pref| { @@ -97,18 +102,25 @@ impl<'a> Path<'a> for ArraySlice { } /// process the simple index like [index] -pub(crate) struct ArrayIndex { +pub struct ArrayIndex { index: usize, + _t: std::marker::PhantomData, } -impl ArrayIndex { +impl ArrayIndex { pub(crate) fn new(index: usize) -> Self { - ArrayIndex { index } + ArrayIndex { + index, + _t: std::marker::PhantomData, + } } } -impl<'a> Path<'a> for ArrayIndex { - type Data = Value; +impl<'a, T> Path<'a> for ArrayIndex +where + T: JsonLike, +{ + type Data = T; fn find(&self, input: JsonPathValue<'a, Self::Data>) -> Vec> { input.flat_map_slice(|data, pref| { @@ -121,27 +133,40 @@ impl<'a> Path<'a> for ArrayIndex { } /// process @ element -pub(crate) struct Current<'a> { - tail: Option>, +pub struct Current<'a, T> { + tail: Option>, + _t: std::marker::PhantomData, } -impl<'a> Current<'a> { - pub(crate) fn from(jp: &'a JsonPath, root: &'a Value) -> Self { +impl<'a, T> Current<'a, T> +where + T: JsonLike, +{ + pub(crate) fn from(jp: &'a JsonPath, root: &'a T) -> Self { match jp { JsonPath::Empty => Current::none(), tail => Current::new(Box::new(json_path_instance(tail, root))), } } - pub(crate) fn new(tail: PathInstance<'a>) -> Self { - Current { tail: Some(tail) } + pub(crate) fn new(tail: PathInstance<'a, T>) -> Self { + Current { + tail: Some(tail), + _t: std::marker::PhantomData, + } } pub(crate) fn none() -> Self { - Current { tail: None } + Current { + tail: None, + _t: std::marker::PhantomData, + } } } -impl<'a> Path<'a> for Current<'a> { - type Data = Value; +impl<'a, T> Path<'a> for Current<'a, T> +where + T: JsonLike, +{ + type Data = T; fn find(&self, input: JsonPathValue<'a, Self::Data>) -> Vec> { self.tail @@ -152,13 +177,16 @@ impl<'a> Path<'a> for Current<'a> { } /// the list of indexes like [1,2,3] -pub(crate) struct UnionIndex<'a> { - indexes: Vec>, +pub struct UnionIndex<'a, T> { + indexes: Vec>, } -impl<'a> UnionIndex<'a> { - pub fn from_indexes(elems: &'a [Value]) -> Self { - let mut indexes: Vec> = vec![]; +impl<'a, T> UnionIndex<'a, T> +where + T: JsonLike, +{ + pub fn from_indexes(elems: &'a [T]) -> Self { + let mut indexes: Vec> = vec![]; for idx in elems.iter() { indexes.push(TopPaths::ArrayIndex(ArrayIndex::new( @@ -169,7 +197,7 @@ impl<'a> UnionIndex<'a> { UnionIndex::new(indexes) } pub fn from_keys(elems: &'a [String]) -> Self { - let mut indexes: Vec> = vec![]; + let mut indexes: Vec> = vec![]; for key in elems.iter() { indexes.push(TopPaths::ObjectField(ObjectField::new(key))) @@ -178,13 +206,16 @@ impl<'a> UnionIndex<'a> { UnionIndex::new(indexes) } - pub fn new(indexes: Vec>) -> Self { + pub fn new(indexes: Vec>) -> Self { UnionIndex { indexes } } } -impl<'a> Path<'a> for UnionIndex<'a> { - type Data = Value; +impl<'a, T> Path<'a> for UnionIndex<'a, T> +where + T: JsonLike, +{ + type Data = T; fn find(&self, input: JsonPathValue<'a, Self::Data>) -> Vec> { self.indexes @@ -195,27 +226,30 @@ impl<'a> Path<'a> for UnionIndex<'a> { } /// process filter element like [?(op sign op)] -pub enum FilterPath<'a> { +pub enum FilterPath<'a, T> { Filter { - left: PathInstance<'a>, - right: PathInstance<'a>, + left: PathInstance<'a, T>, + right: PathInstance<'a, T>, op: &'a FilterSign, }, Or { - left: PathInstance<'a>, - right: PathInstance<'a>, + left: PathInstance<'a, T>, + right: PathInstance<'a, T>, }, And { - left: PathInstance<'a>, - right: PathInstance<'a>, + left: PathInstance<'a, T>, + right: PathInstance<'a, T>, }, Not { - exp: PathInstance<'a>, + exp: PathInstance<'a, T>, }, } -impl<'a> FilterPath<'a> { - pub(crate) fn new(expr: &'a FilterExpression, root: &'a Value) -> Self { +impl<'a, T> FilterPath<'a, T> +where + T: JsonLike, +{ + pub(crate) fn new(expr: &'a FilterExpression, root: &'a T) -> Self { match expr { FilterExpression::Atom(left, op, right) => FilterPath::Filter { left: process_operand(left, root), @@ -238,64 +272,64 @@ impl<'a> FilterPath<'a> { fn compound( one: &'a FilterSign, two: &'a FilterSign, - left: Vec>, - right: Vec>, + left: Vec>, + right: Vec>, ) -> bool { FilterPath::process_atom(one, left.clone(), right.clone()) || FilterPath::process_atom(two, left, right) } fn process_atom( op: &'a FilterSign, - left: Vec>, - right: Vec>, + left: Vec>, + right: Vec>, ) -> bool { match op { - FilterSign::Equal => eq( + FilterSign::Equal => ::eq( JsonPathValue::vec_as_data(left), JsonPathValue::vec_as_data(right), ), FilterSign::Unequal => !FilterPath::process_atom(&FilterSign::Equal, left, right), - FilterSign::Less => less( + FilterSign::Less => ::less( JsonPathValue::vec_as_data(left), JsonPathValue::vec_as_data(right), ), FilterSign::LeOrEq => { FilterPath::compound(&FilterSign::Less, &FilterSign::Equal, left, right) } - FilterSign::Greater => less( + FilterSign::Greater => ::less( JsonPathValue::vec_as_data(right), JsonPathValue::vec_as_data(left), ), FilterSign::GrOrEq => { FilterPath::compound(&FilterSign::Greater, &FilterSign::Equal, left, right) } - FilterSign::Regex => regex( + FilterSign::Regex => ::regex( JsonPathValue::vec_as_data(left), JsonPathValue::vec_as_data(right), ), - FilterSign::In => inside( + FilterSign::In => ::inside( JsonPathValue::vec_as_data(left), JsonPathValue::vec_as_data(right), ), FilterSign::Nin => !FilterPath::process_atom(&FilterSign::In, left, right), FilterSign::NoneOf => !FilterPath::process_atom(&FilterSign::AnyOf, left, right), - FilterSign::AnyOf => any_of( + FilterSign::AnyOf => ::any_of( JsonPathValue::vec_as_data(left), JsonPathValue::vec_as_data(right), ), - FilterSign::SubSetOf => sub_set_of( + FilterSign::SubSetOf => ::sub_set_of( JsonPathValue::vec_as_data(left), JsonPathValue::vec_as_data(right), ), FilterSign::Exists => !JsonPathValue::vec_as_data(left).is_empty(), - FilterSign::Size => size( + FilterSign::Size => ::size( JsonPathValue::vec_as_data(left), JsonPathValue::vec_as_data(right), ), } } - fn process(&self, curr_el: &'a Value) -> bool { + fn process(&self, curr_el: &'a T) -> bool { let pref = String::new(); match self { FilterPath::Filter { left, right, op } => FilterPath::process_atom( @@ -324,26 +358,40 @@ impl<'a> FilterPath<'a> { } } -impl<'a> Path<'a> for FilterPath<'a> { - type Data = Value; +impl<'a, T> Path<'a> for FilterPath<'a, T> +where + T: JsonLike, +{ + type Data = T; fn find(&self, input: JsonPathValue<'a, Self::Data>) -> Vec> { input.flat_map_slice(|data, pref| { let mut res = vec![]; - match data { - Array(elems) => { - for (i, el) in elems.iter().enumerate() { - if self.process(el) { - res.push(Slice(el, jsp_idx(&pref, i))) - } - } - } - el => { + if data.is_array() { + let elems = data.as_array().unwrap(); + for (i, el) in elems.iter().enumerate() { if self.process(el) { - res.push(Slice(el, pref)) + res.push(Slice(el, jsp_idx(&pref, i))) } } + } else if self.process(data) { + res.push(Slice(data, pref)) } + + // match data { + // Array(elems) => { + // for (i, el) in elems.iter().enumerate() { + // if self.process(el) { + // res.push(Slice(el, jsp_idx(&pref, i))) + // } + // } + // } + // el => { + // if self.process(el) { + // res.push(Slice(el, pref)) + // } + // } + // } if res.is_empty() { vec![NoValue] } else { @@ -364,13 +412,13 @@ mod tests { use crate::JsonPathValue::NoValue; use crate::path; - use serde_json::json; + use serde_json::{json, Value}; #[test] fn array_slice_end_start_test() { let array = [0, 1, 2, 3, 4, 5]; let len = array.len() as i32; - let mut slice = ArraySlice::new(0, 0, 0); + let mut slice: ArraySlice = ArraySlice::new(0, 0, 0); assert_eq!(slice.start(len).unwrap(), 0); slice.start_index = 1; diff --git a/src/path/json.rs b/src/path/json.rs deleted file mode 100644 index 83d9a7e..0000000 --- a/src/path/json.rs +++ /dev/null @@ -1,295 +0,0 @@ -use regex::Regex; -use serde_json::Value; - -/// compare sizes of json elements -/// The method expects to get a number on the right side and array or string or object on the left -/// where the number of characters, elements or fields will be compared respectively. -pub fn size(left: Vec<&Value>, right: Vec<&Value>) -> bool { - if let Some(Value::Number(n)) = right.first() { - if let Some(sz) = n.as_f64() { - for el in left.iter() { - match el { - Value::String(v) if v.len() == sz as usize => true, - Value::Array(elems) if elems.len() == sz as usize => true, - Value::Object(fields) if fields.len() == sz as usize => true, - _ => return false, - }; - } - return true; - } - } - false -} - -/// ensure the array on the left side is a subset of the array on the right side. -//todo change the naive impl to sets -pub fn sub_set_of(left: Vec<&Value>, right: Vec<&Value>) -> bool { - if left.is_empty() { - return true; - } - if right.is_empty() { - return false; - } - - if let Some(elems) = left.first().and_then(|e| e.as_array()) { - if let Some(Value::Array(right_elems)) = right.first() { - if right_elems.is_empty() { - return false; - } - - for el in elems { - let mut res = false; - - for r in right_elems.iter() { - if el.eq(r) { - res = true - } - } - if !res { - return false; - } - } - return true; - } - } - false -} - -/// ensure at least one element in the array on the left side belongs to the array on the right side. -//todo change the naive impl to sets -pub fn any_of(left: Vec<&Value>, right: Vec<&Value>) -> bool { - if left.is_empty() { - return true; - } - if right.is_empty() { - return false; - } - - if let Some(Value::Array(elems)) = right.first() { - if elems.is_empty() { - return false; - } - - for el in left.iter() { - if let Some(left_elems) = el.as_array() { - for l in left_elems.iter() { - for r in elems.iter() { - if l.eq(r) { - return true; - } - } - } - } else { - for r in elems.iter() { - if el.eq(&r) { - return true; - } - } - } - } - } - - false -} - -/// ensure that the element on the left sides mathes the regex on the right side -pub fn regex(left: Vec<&Value>, right: Vec<&Value>) -> bool { - if left.is_empty() || right.is_empty() { - return false; - } - - match right.first() { - Some(Value::String(str)) => { - if let Ok(regex) = Regex::new(str) { - for el in left.iter() { - if let Some(v) = el.as_str() { - if regex.is_match(v) { - return true; - } - } - } - } - false - } - _ => false, - } -} - -/// ensure that the element on the left side belongs to the array on the right side. -pub fn inside(left: Vec<&Value>, right: Vec<&Value>) -> bool { - if left.is_empty() { - return false; - } - - match right.first() { - Some(Value::Array(elems)) => { - for el in left.iter() { - if elems.contains(el) { - return true; - } - } - false - } - Some(Value::Object(elems)) => { - for el in left.iter() { - for r in elems.values() { - if el.eq(&r) { - return true; - } - } - } - false - } - _ => false, - } -} - -/// ensure the number on the left side is less the number on the right side -pub fn less(left: Vec<&Value>, right: Vec<&Value>) -> bool { - if left.len() == 1 && right.len() == 1 { - match (left.first(), right.first()) { - (Some(Value::Number(l)), Some(Value::Number(r))) => l - .as_f64() - .and_then(|v1| r.as_f64().map(|v2| v1 < v2)) - .unwrap_or(false), - _ => false, - } - } else { - false - } -} - -/// compare elements -pub fn eq(left: Vec<&Value>, right: Vec<&Value>) -> bool { - if left.len() != right.len() { - false - } else { - left.iter().zip(right).map(|(a, b)| a.eq(&b)).all(|a| a) - } -} - -#[cfg(test)] -mod tests { - use crate::path::json::{any_of, eq, less, regex, size, sub_set_of}; - use serde_json::{json, Value}; - - #[test] - fn value_eq_test() { - let left = json!({"value":42}); - let right = json!({"value":42}); - let right_uneq = json!([42]); - - assert!(&left.eq(&right)); - assert!(!&left.eq(&right_uneq)); - } - - #[test] - fn vec_value_test() { - let left = json!({"value":42}); - let left1 = json!(42); - let left2 = json!([1, 2, 3]); - let left3 = json!({"value2":[42],"value":[42]}); - - let right = json!({"value":42}); - let right1 = json!(42); - let right2 = json!([1, 2, 3]); - let right3 = json!({"value":[42],"value2":[42]}); - - assert!(eq(vec![&left], vec![&right])); - - assert!(!eq(vec![], vec![&right])); - assert!(!eq(vec![&right], vec![])); - - assert!(eq( - vec![&left, &left1, &left2, &left3], - vec![&right, &right1, &right2, &right3] - )); - - assert!(!eq( - vec![&left1, &left, &left2, &left3], - vec![&right, &right1, &right2, &right3] - )); - } - - #[test] - fn less_value_test() { - let left = json!(10); - let right = json!(11); - - assert!(less(vec![&left], vec![&right])); - assert!(!less(vec![&right], vec![&left])); - - let left = json!(-10); - let right = json!(-11); - - assert!(!less(vec![&left], vec![&right])); - assert!(less(vec![&right], vec![&left])); - - let left = json!(-10.0); - let right = json!(-11.0); - - assert!(!less(vec![&left], vec![&right])); - assert!(less(vec![&right], vec![&left])); - - assert!(!less(vec![], vec![&right])); - assert!(!less(vec![&right, &right], vec![&left])); - } - - #[test] - fn regex_test() { - let right = json!("[a-zA-Z]+[0-9]#[0-9]+"); - let left1 = json!("a11#"); - let left2 = json!("a1#1"); - let left3 = json!("a#11"); - let left4 = json!("#a11"); - - assert!(regex(vec![&left1, &left2, &left3, &left4], vec![&right])); - assert!(!regex(vec![&left1, &left3, &left4], vec![&right])) - } - - #[test] - fn any_of_test() { - let right = json!([1, 2, 3, 4, 5, 6]); - let left = json!([1, 100, 101]); - assert!(any_of(vec![&left], vec![&right])); - - let left = json!([11, 100, 101]); - assert!(!any_of(vec![&left], vec![&right])); - - let left1 = json!(1); - let left2 = json!(11); - assert!(any_of(vec![&left1, &left2], vec![&right])); - } - - #[test] - fn sub_set_of_test() { - let left1 = json!(1); - let left2 = json!(2); - let left3 = json!(3); - let left40 = json!(40); - let right = json!([1, 2, 3, 4, 5, 6]); - assert!(sub_set_of( - vec![&Value::Array(vec![ - left1.clone(), - left2.clone(), - left3.clone() - ])], - vec![&right] - )); - assert!(!sub_set_of( - vec![&Value::Array(vec![left1, left2, left3, left40])], - vec![&right] - )); - } - - #[test] - fn size_test() { - let left1 = json!("abc"); - let left2 = json!([1, 2, 3]); - let left3 = json!([1, 2, 3, 4]); - let right = json!(3); - assert!(size(vec![&left1], vec![&right])); - assert!(size(vec![&left2], vec![&right])); - assert!(!size(vec![&left3], vec![&right])); - } -} diff --git a/src/path/mod.rs b/src/path/mod.rs index 1309865..f4322f0 100644 --- a/src/path/mod.rs +++ b/src/path/mod.rs @@ -1,17 +1,367 @@ -use crate::JsonPathValue; -use serde_json::Value; +use std::fmt::Debug; + +use crate::{jsp_idx, jsp_obj, JsonPathValue}; +use regex::Regex; +use serde_json::{json, Value}; use crate::parser::model::{Function, JsonPath, JsonPathIndex, Operand}; -use crate::path::index::{ArrayIndex, ArraySlice, Current, FilterPath, UnionIndex}; +pub use crate::path::index::{ArrayIndex, ArraySlice, Current, FilterPath, UnionIndex}; use crate::path::top::*; /// The module is in charge of processing [[JsonPathIndex]] elements mod index; -/// The module is a helper module providing the set of helping funcitons to process a json elements -mod json; /// The module is responsible for processing of the [[JsonPath]] elements mod top; +/// The `JsonLike` trait defines a set of methods and associated types for working with JSON-like data structures. +/// +/// It provides a common interface for accessing and manipulating JSON data, +/// allowing for operations such as +/// - retrieving values by key, +/// - iterating over elements +/// - performing various comparisons and transformations. +/// +/// The trait is implemented for the `serde_json::Value` type already +pub trait JsonLike: + Default + + Clone + + Debug + + for<'a> From<&'a str> + + From> + + From + + From + + From + + From> + + From + + PartialEq + + 'static +{ + /// Retrieves a reference to the value associated with the given key. + fn get(&self, key: &str) -> Option<&Self>; + + /// Iterates over the elements with a given prefix and returns a vector of `JsonPathValue`. + fn itre(&self, pref: String) -> Vec>; + + /// Returns the length of the array as a `JsonPathValue`. + fn array_len(&self) -> JsonPathValue<'static, Self>; + + /// Initializes an instance with a specific size. + fn init_with_usize(cnt: usize) -> Self; + + /// Flattens nested structures and returns a vector of tuples containing references to the elements and their paths. + fn deep_flatten(&self, pref: String) -> Vec<(&Self, String)>; + + /// Performs a deep search by key and returns a vector of tuples containing references to the elements and their paths. + fn deep_path_by_key<'a>( + &'a self, + key: ObjectField<'a, Self>, + pref: String, + ) -> Vec<(&'a Self, String)>; + + /// Converts the element to an `Option`. + fn as_u64(&self) -> Option; + + /// Checks if the element is an array. + fn is_array(&self) -> bool; + + /// Converts the element to an `Option<&Vec>`. + fn as_array(&self) -> Option<&Vec>; + + /// Compares the size of two vectors of references to elements. + fn size(left: Vec<&Self>, right: Vec<&Self>) -> bool; + + /// Checks if the left vector is a subset of the right vector. + fn sub_set_of(left: Vec<&Self>, right: Vec<&Self>) -> bool; + + /// Checks if any element in the left vector is present in the right vector. + fn any_of(left: Vec<&Self>, right: Vec<&Self>) -> bool; + + /// Checks if the elements in the left vector match the regex pattern in the right vector. + fn regex(left: Vec<&Self>, right: Vec<&Self>) -> bool; + + /// Checks if any element in the left vector is inside the right vector. + fn inside(left: Vec<&Self>, right: Vec<&Self>) -> bool; + + /// Ensures the number on the left side is less than the number on the right side. + fn less(left: Vec<&Self>, right: Vec<&Self>) -> bool; + + /// Compares elements for equality. + fn eq(left: Vec<&Self>, right: Vec<&Self>) -> bool; + + /// Returns a null value. + fn null() -> Self; + + /// Creates an array from a vector of elements. + fn array(data: Vec) -> Self; +} + +impl JsonLike for Value { + fn is_array(&self) -> bool { + self.is_array() + } + fn array(data: Vec) -> Self { + Value::Array(data) + } + + fn null() -> Self { + Value::Null + } + + fn get(&self, key: &str) -> Option<&Self> { + self.get(key) + } + + fn itre(&self, pref: String) -> Vec> { + let res = match self { + Value::Array(elems) => { + let mut res = vec![]; + for (idx, el) in elems.iter().enumerate() { + res.push(JsonPathValue::Slice(el, jsp_idx(&pref, idx))); + } + res + } + Value::Object(elems) => { + let mut res = vec![]; + for (key, el) in elems.into_iter() { + res.push(JsonPathValue::Slice(el, jsp_obj(&pref, key))); + } + res + } + _ => vec![], + }; + if res.is_empty() { + vec![JsonPathValue::NoValue] + } else { + res + } + } + fn array_len(&self) -> JsonPathValue<'static, Self> { + match self { + Value::Array(elems) => JsonPathValue::NewValue(json!(elems.len())), + _ => JsonPathValue::NoValue, + } + } + + fn init_with_usize(cnt: usize) -> Self { + json!(cnt) + } + fn deep_flatten(&self, pref: String) -> Vec<(&Self, String)> { + let mut acc = vec![]; + match self { + Value::Object(elems) => { + for (f, v) in elems.into_iter() { + let pref = jsp_obj(&pref, f); + acc.push((v, pref.clone())); + acc.append(&mut v.deep_flatten(pref)); + } + } + Value::Array(elems) => { + for (i, v) in elems.iter().enumerate() { + let pref = jsp_idx(&pref, i); + acc.push((v, pref.clone())); + acc.append(&mut v.deep_flatten(pref)); + } + } + _ => (), + } + acc + } + fn deep_path_by_key<'a>( + &'a self, + key: ObjectField<'a, Self>, + pref: String, + ) -> Vec<(&'a Self, String)> { + let mut result: Vec<(&'a Value, String)> = + JsonPathValue::vec_as_pair(key.find(JsonPathValue::new_slice(self, pref.clone()))); + match self { + Value::Object(elems) => { + let mut next_levels: Vec<(&'a Value, String)> = elems + .into_iter() + .flat_map(|(k, v)| v.deep_path_by_key(key.clone(), jsp_obj(&pref, k))) + .collect(); + result.append(&mut next_levels); + result + } + Value::Array(elems) => { + let mut next_levels: Vec<(&'a Value, String)> = elems + .iter() + .enumerate() + .flat_map(|(i, v)| v.deep_path_by_key(key.clone(), jsp_idx(&pref, i))) + .collect(); + result.append(&mut next_levels); + result + } + _ => result, + } + } + + fn as_u64(&self) -> Option { + self.as_u64() + } + + fn as_array(&self) -> Option<&Vec> { + self.as_array() + } + + fn size(left: Vec<&Self>, right: Vec<&Self>) -> bool { + if let Some(Value::Number(n)) = right.first() { + if let Some(sz) = n.as_f64() { + for el in left.iter() { + match el { + Value::String(v) if v.len() == sz as usize => true, + Value::Array(elems) if elems.len() == sz as usize => true, + Value::Object(fields) if fields.len() == sz as usize => true, + _ => return false, + }; + } + return true; + } + } + false + } + + fn sub_set_of(left: Vec<&Self>, right: Vec<&Self>) -> bool { + if left.is_empty() { + return true; + } + if right.is_empty() { + return false; + } + + if let Some(elems) = left.first().and_then(|e| e.as_array()) { + if let Some(Value::Array(right_elems)) = right.first() { + if right_elems.is_empty() { + return false; + } + + for el in elems { + let mut res = false; + + for r in right_elems.iter() { + if el.eq(r) { + res = true + } + } + if !res { + return false; + } + } + return true; + } + } + false + } + + fn any_of(left: Vec<&Self>, right: Vec<&Self>) -> bool { + if left.is_empty() { + return true; + } + if right.is_empty() { + return false; + } + + if let Some(Value::Array(elems)) = right.first() { + if elems.is_empty() { + return false; + } + + for el in left.iter() { + if let Some(left_elems) = el.as_array() { + for l in left_elems.iter() { + for r in elems.iter() { + if l.eq(r) { + return true; + } + } + } + } else { + for r in elems.iter() { + if el.eq(&r) { + return true; + } + } + } + } + } + + false + } + + fn regex(left: Vec<&Self>, right: Vec<&Self>) -> bool { + if left.is_empty() || right.is_empty() { + return false; + } + + match right.first() { + Some(Value::String(str)) => { + if let Ok(regex) = Regex::new(str) { + for el in left.iter() { + if let Some(v) = el.as_str() { + if regex.is_match(v) { + return true; + } + } + } + } + false + } + _ => false, + } + } + + fn inside(left: Vec<&Self>, right: Vec<&Self>) -> bool { + if left.is_empty() { + return false; + } + + match right.first() { + Some(Value::Array(elems)) => { + for el in left.iter() { + if elems.contains(el) { + return true; + } + } + false + } + Some(Value::Object(elems)) => { + for el in left.iter() { + for r in elems.values() { + if el.eq(&r) { + return true; + } + } + } + false + } + _ => false, + } + } + + /// ensure the number on the left side is less the number on the right side + fn less(left: Vec<&Self>, right: Vec<&Self>) -> bool { + if left.len() == 1 && right.len() == 1 { + match (left.first(), right.first()) { + (Some(Value::Number(l)), Some(Value::Number(r))) => l + .as_f64() + .and_then(|v1| r.as_f64().map(|v2| v1 < v2)) + .unwrap_or(false), + _ => false, + } + } else { + false + } + } + + /// compare elements + fn eq(left: Vec<&Self>, right: Vec<&Self>) -> bool { + if left.len() != right.len() { + false + } else { + left.iter().zip(right).map(|(a, b)| a.eq(&b)).all(|a| a) + } + } +} + /// The trait defining the behaviour of processing every separated element. /// type Data usually stands for json [[Value]] /// The trait also requires to have a root json to process. @@ -37,24 +387,27 @@ pub trait Path<'a> { } /// all known Paths, mostly to avoid a dynamic Box and vtable for internal function -pub(crate) enum TopPaths<'a> { - RootPointer(RootPointer<'a, Value>), - ObjectField(ObjectField<'a>), - Chain(Chain<'a>), - Wildcard(Wildcard), - DescentObject(DescentObject<'a>), - DescentWildcard(DescentWildcard), - Current(Current<'a>), - ArrayIndex(ArrayIndex), - ArraySlice(ArraySlice), - UnionIndex(UnionIndex<'a>), - FilterPath(FilterPath<'a>), - IdentityPath(IdentityPath), - FnPath(FnPath), +pub enum TopPaths<'a, T> { + RootPointer(RootPointer<'a, T>), + ObjectField(ObjectField<'a, T>), + Chain(Chain<'a, T>), + Wildcard(Wildcard), + DescentObject(DescentObject<'a, T>), + DescentWildcard(DescentWildcard), + Current(Current<'a, T>), + ArrayIndex(ArrayIndex), + ArraySlice(ArraySlice), + UnionIndex(UnionIndex<'a, T>), + FilterPath(FilterPath<'a, T>), + IdentityPath(IdentityPath), + FnPath(FnPath), } -impl<'a> Path<'a> for TopPaths<'a> { - type Data = Value; +impl<'a, T> Path<'a> for TopPaths<'a, T> +where + T: JsonLike, +{ + type Data = T; fn find(&self, input: JsonPathValue<'a, Self::Data>) -> Vec> { match self { @@ -116,17 +469,20 @@ impl<'a> Path<'a> for TopPaths<'a> { } /// The basic type for instances. -pub(crate) type PathInstance<'a> = Box + 'a>; +pub(crate) type PathInstance<'a, T> = Box + 'a>; /// The major method to process the top part of json part -pub(crate) fn json_path_instance<'a>(json_path: &'a JsonPath, root: &'a Value) -> TopPaths<'a> { +pub(crate) fn json_path_instance<'a, T>(json_path: &'a JsonPath, root: &'a T) -> TopPaths<'a, T> +where + T: JsonLike, +{ match json_path { JsonPath::Root => TopPaths::RootPointer(RootPointer::new(root)), JsonPath::Field(key) => TopPaths::ObjectField(ObjectField::new(key)), JsonPath::Chain(chain) => TopPaths::Chain(Chain::from(chain, root)), - JsonPath::Wildcard => TopPaths::Wildcard(Wildcard {}), + JsonPath::Wildcard => TopPaths::Wildcard(Wildcard::new()), JsonPath::Descent(key) => TopPaths::DescentObject(DescentObject::new(key)), - JsonPath::DescentW => TopPaths::DescentWildcard(DescentWildcard), + JsonPath::DescentW => TopPaths::DescentWildcard(DescentWildcard::new()), JsonPath::Current(value) => TopPaths::Current(Current::from(value, root)), JsonPath::Index(JsonPathIndex::Single(index)) => { TopPaths::ArrayIndex(ArrayIndex::new(index.as_u64().unwrap() as usize)) @@ -143,15 +499,149 @@ pub(crate) fn json_path_instance<'a>(json_path: &'a JsonPath, root: &'a Value) - JsonPath::Index(JsonPathIndex::Filter(fe)) => { TopPaths::FilterPath(FilterPath::new(fe, root)) } - JsonPath::Empty => TopPaths::IdentityPath(IdentityPath {}), - JsonPath::Fn(Function::Length) => TopPaths::FnPath(FnPath::Size), + JsonPath::Empty => TopPaths::IdentityPath(IdentityPath::new()), + JsonPath::Fn(Function::Length) => TopPaths::FnPath(FnPath::new_size()), } } /// The method processes the operand inside the filter expressions -fn process_operand<'a>(op: &'a Operand, root: &'a Value) -> PathInstance<'a> { +fn process_operand<'a, T>(op: &'a Operand, root: &'a T) -> PathInstance<'a, T> +where + T: JsonLike, +{ Box::new(match op { Operand::Static(v) => json_path_instance(&JsonPath::Root, v), Operand::Dynamic(jp) => json_path_instance(jp, root), }) } + +#[cfg(test)] +mod tests { + + use crate::path::JsonLike; + use serde_json::{json, Value}; + + #[test] + fn value_eq_test() { + let left = json!({"value":42}); + let right = json!({"value":42}); + let right_uneq = json!([42]); + assert!(&left.eq(&right)); + assert!(!&left.eq(&right_uneq)); + } + + #[test] + fn vec_value_test() { + let left = json!({"value":42}); + let left1 = json!(42); + let left2 = json!([1, 2, 3]); + let left3 = json!({"value2":[42],"value":[42]}); + + let right = json!({"value":42}); + let right1 = json!(42); + let right2 = json!([1, 2, 3]); + let right3 = json!({"value":[42],"value2":[42]}); + + assert!(JsonLike::eq(vec![&left], vec![&right])); + + assert!(!JsonLike::eq(vec![], vec![&right])); + assert!(!JsonLike::eq(vec![&right], vec![])); + + assert!(JsonLike::eq( + vec![&left, &left1, &left2, &left3], + vec![&right, &right1, &right2, &right3] + )); + + assert!(!JsonLike::eq( + vec![&left1, &left, &left2, &left3], + vec![&right, &right1, &right2, &right3] + )); + } + + #[test] + fn less_value_test() { + let left = json!(10); + let right = json!(11); + + assert!(JsonLike::less(vec![&left], vec![&right])); + assert!(!JsonLike::less(vec![&right], vec![&left])); + + let left = json!(-10); + let right = json!(-11); + + assert!(!JsonLike::less(vec![&left], vec![&right])); + assert!(JsonLike::less(vec![&right], vec![&left])); + + let left = json!(-10.0); + let right = json!(-11.0); + + assert!(!JsonLike::less(vec![&left], vec![&right])); + assert!(JsonLike::less(vec![&right], vec![&left])); + + assert!(!JsonLike::less(vec![], vec![&right])); + assert!(!JsonLike::less(vec![&right, &right], vec![&left])); + } + + #[test] + fn regex_test() { + let right = json!("[a-zA-Z]+[0-9]#[0-9]+"); + let left1 = json!("a11#"); + let left2 = json!("a1#1"); + let left3 = json!("a#11"); + let left4 = json!("#a11"); + + assert!(JsonLike::regex( + vec![&left1, &left2, &left3, &left4], + vec![&right] + )); + assert!(!JsonLike::regex(vec![&left1, &left3, &left4], vec![&right])) + } + + #[test] + fn any_of_test() { + let right = json!([1, 2, 3, 4, 5, 6]); + let left = json!([1, 100, 101]); + assert!(JsonLike::any_of(vec![&left], vec![&right])); + + let left = json!([11, 100, 101]); + assert!(!JsonLike::any_of(vec![&left], vec![&right])); + + let left1 = json!(1); + let left2 = json!(11); + assert!(JsonLike::any_of(vec![&left1, &left2], vec![&right])); + } + + #[test] + fn sub_set_of_test() { + let left1 = json!(1); + let left2 = json!(2); + let left3 = json!(3); + let left40 = json!(40); + let right = json!([1, 2, 3, 4, 5, 6]); + assert!(JsonLike::sub_set_of( + vec![&Value::Array(vec![ + left1.clone(), + left2.clone(), + left3.clone() + ])], + vec![&right] + )); + assert!(!JsonLike::sub_set_of( + vec![&Value::Array(vec![left1, left2, left3, left40])], + vec![&right] + )); + } + + #[test] + fn size_test() { + let left1 = json!("abc"); + let left2 = json!([1, 2, 3]); + let left3 = json!([1, 2, 3, 4]); + let right = json!(3); + let right1 = json!(4); + assert!(JsonLike::size(vec![&left1], vec![&right])); + assert!(JsonLike::size(vec![&left2], vec![&right])); + assert!(!JsonLike::size(vec![&left3], vec![&right])); + assert!(JsonLike::size(vec![&left3], vec![&right1])); + } +} diff --git a/src/path/top.rs b/src/path/top.rs index 770cd1f..220fa79 100644 --- a/src/path/top.rs +++ b/src/path/top.rs @@ -1,62 +1,73 @@ +use crate::jsp_obj; use crate::parser::model::*; use crate::path::{json_path_instance, JsonPathValue, Path}; use crate::JsonPathValue::{NewValue, NoValue, Slice}; -use crate::{jsp_idx, jsp_obj, JsPathStr}; -use serde_json::value::Value::{Array, Object}; -use serde_json::{json, Value}; -use super::TopPaths; +use super::{JsonLike, TopPaths}; /// to process the element [*] -pub(crate) struct Wildcard {} +pub struct Wildcard { + _t: std::marker::PhantomData, +} -impl<'a> Path<'a> for Wildcard { - type Data = Value; +impl Wildcard { + pub fn new() -> Self { + Self { + _t: std::marker::PhantomData, + } + } +} - fn find(&self, data: JsonPathValue<'a, Self::Data>) -> Vec> { - data.flat_map_slice(|data, pref| { - let res = match data { - Array(elems) => { - let mut res = vec![]; - for (idx, el) in elems.iter().enumerate() { - res.push(Slice(el, jsp_idx(&pref, idx))); - } +impl Default for Wildcard { + fn default() -> Self { + Self::new() + } +} - res - } - Object(elems) => { - let mut res = vec![]; - for (key, el) in elems.into_iter() { - res.push(Slice(el, jsp_obj(&pref, key))); - } - res - } - _ => vec![], - }; - if res.is_empty() { - vec![NoValue] - } else { - res - } - }) +impl<'a, T> Path<'a> for Wildcard +where + T: JsonLike, +{ + type Data = T; + + fn find(&self, data: JsonPathValue<'a, Self::Data>) -> Vec> { + data.flat_map_slice(|data, pref| data.itre(pref)) } } /// empty path. Returns incoming data. -pub(crate) struct IdentityPath {} +pub struct IdentityPath { + _t: std::marker::PhantomData, +} + +impl Default for IdentityPath { + fn default() -> Self { + Self::new() + } +} -impl<'a> Path<'a> for IdentityPath { - type Data = Value; +impl IdentityPath { + pub fn new() -> Self { + Self { + _t: std::marker::PhantomData, + } + } +} + +impl<'a, T> Path<'a> for IdentityPath { + type Data = T; fn find(&self, data: JsonPathValue<'a, Self::Data>) -> Vec> { vec![data] } } -pub(crate) struct EmptyPath {} +pub(crate) struct EmptyPath { + _t: std::marker::PhantomData, +} -impl<'a> Path<'a> for EmptyPath { - type Data = Value; +impl<'a, T> Path<'a> for EmptyPath { + type Data = T; fn find(&self, _data: JsonPathValue<'a, Self::Data>) -> Vec> { vec![] @@ -64,7 +75,7 @@ impl<'a> Path<'a> for EmptyPath { } /// process $ element -pub(crate) struct RootPointer<'a, T> { +pub struct RootPointer<'a, T> { root: &'a T, } @@ -74,8 +85,11 @@ impl<'a, T> RootPointer<'a, T> { } } -impl<'a> Path<'a> for RootPointer<'a, Value> { - type Data = Value; +impl<'a, T> Path<'a> for RootPointer<'a, T> +where + T: JsonLike, +{ + type Data = T; fn find(&self, _data: JsonPathValue<'a, Self::Data>) -> Vec> { vec![JsonPathValue::from_root(self.root)] @@ -83,24 +97,62 @@ impl<'a> Path<'a> for RootPointer<'a, Value> { } /// process object fields like ['key'] or .key -pub(crate) struct ObjectField<'a> { +pub struct ObjectField<'a, T> { key: &'a str, + _t: std::marker::PhantomData, } -impl<'a> ObjectField<'a> { - pub(crate) fn new(key: &'a str) -> ObjectField<'a> { - ObjectField { key } +impl<'a, T> ObjectField<'a, T> { + pub(crate) fn new(key: &'a str) -> ObjectField<'a, T> { + ObjectField { + key, + _t: std::marker::PhantomData, + } } } -impl<'a> Clone for ObjectField<'a> { +impl<'a, T> Clone for ObjectField<'a, T> { fn clone(&self) -> Self { ObjectField::new(self.key) } } -impl<'a> Path<'a> for FnPath { - type Data = Value; +impl<'a, T> Path<'a> for ObjectField<'a, T> +where + T: JsonLike, +{ + type Data = T; + + fn find(&self, data: JsonPathValue<'a, Self::Data>) -> Vec> { + let take_field = |v: &'a T| v.get(self.key); + + let res = match data { + Slice(js, p) => take_field(js) + .map(|v| JsonPathValue::new_slice(v, jsp_obj(&p, self.key))) + .unwrap_or_else(|| NoValue), + _ => NoValue, + }; + vec![res] + } +} + +pub enum FnPath { + Size { _t: std::marker::PhantomData }, +} + +impl FnPath { + pub fn new_size() -> Self { + FnPath::Size { + _t: std::marker::PhantomData, + } + } +} + +impl<'a, T> Path<'a> for FnPath +where + T: JsonLike + 'static, +{ + type Data = T; fn flat_find( &self, @@ -112,17 +164,14 @@ impl<'a> Path<'a> for FnPath { return vec![NoValue]; } let res = if is_search_length { - NewValue(json!(input.iter().filter(|v| v.has_value()).count())) + NewValue(T::init_with_usize( + input.iter().filter(|v| v.has_value()).count(), + )) } else { - let take_len = |v: &Value| match v { - Array(elems) => NewValue(json!(elems.len())), - _ => NoValue, - }; - match input.first() { Some(v) => match v { - NewValue(d) => take_len(d), - Slice(s, _) => take_len(s), + NewValue(d) => d.array_len(), + Slice(s, _) => s.array_len(), NoValue => NoValue, }, None => NoValue, @@ -136,103 +185,51 @@ impl<'a> Path<'a> for FnPath { } } -pub(crate) enum FnPath { - Size, -} - -impl<'a> Path<'a> for ObjectField<'a> { - type Data = Value; - - fn find(&self, data: JsonPathValue<'a, Self::Data>) -> Vec> { - let take_field = |v: &'a Value| match v { - Object(fields) => fields.get(self.key), - _ => None, - }; - - let res = match data { - Slice(js, p) => take_field(js) - .map(|v| JsonPathValue::new_slice(v, jsp_obj(&p, self.key))) - .unwrap_or_else(|| NoValue), - _ => NoValue, - }; - vec![res] - } -} /// the top method of the processing ..* -pub(crate) struct DescentWildcard; - -impl<'a> Path<'a> for DescentWildcard { - type Data = Value; +pub struct DescentWildcard { + _t: std::marker::PhantomData, +} - fn find(&self, data: JsonPathValue<'a, Self::Data>) -> Vec> { - data.map_slice(deep_flatten) +impl Default for DescentWildcard { + fn default() -> Self { + Self::new() } } -// todo rewrite to tail rec -fn deep_flatten(data: &Value, pref: JsPathStr) -> Vec<(&Value, JsPathStr)> { - let mut acc = vec![]; - match data { - Object(elems) => { - for (f, v) in elems.into_iter() { - let pref = jsp_obj(&pref, f); - acc.push((v, pref.clone())); - acc.append(&mut deep_flatten(v, pref)); - } +impl DescentWildcard { + pub fn new() -> Self { + DescentWildcard { + _t: std::marker::PhantomData, } - Array(elems) => { - for (i, v) in elems.iter().enumerate() { - let pref = jsp_idx(&pref, i); - acc.push((v, pref.clone())); - acc.append(&mut deep_flatten(v, pref)); - } - } - _ => (), } - acc } -// todo rewrite to tail rec -fn deep_path_by_key<'a>( - data: &'a Value, - key: ObjectField<'a>, - pref: JsPathStr, -) -> Vec<(&'a Value, JsPathStr)> { - let mut result: Vec<(&'a Value, JsPathStr)> = - JsonPathValue::vec_as_pair(key.find(JsonPathValue::new_slice(data, pref.clone()))); - match data { - Object(elems) => { - let mut next_levels: Vec<(&'a Value, JsPathStr)> = elems - .into_iter() - .flat_map(|(k, v)| deep_path_by_key(v, key.clone(), jsp_obj(&pref, k))) - .collect(); - result.append(&mut next_levels); - result - } - Array(elems) => { - let mut next_levels: Vec<(&'a Value, JsPathStr)> = elems - .iter() - .enumerate() - .flat_map(|(i, v)| deep_path_by_key(v, key.clone(), jsp_idx(&pref, i))) - .collect(); - result.append(&mut next_levels); - result - } - _ => result, +impl<'a, T> Path<'a> for DescentWildcard +where + T: JsonLike, +{ + type Data = T; + + fn find(&self, data: JsonPathValue<'a, Self::Data>) -> Vec> { + data.map_slice(|data, pref| data.deep_flatten(pref)) } } /// processes decent object like .. -pub(crate) struct DescentObject<'a> { +pub struct DescentObject<'a, T> { key: &'a str, + _t: std::marker::PhantomData, } -impl<'a> Path<'a> for DescentObject<'a> { - type Data = Value; +impl<'a, T> Path<'a> for DescentObject<'a, T> +where + T: JsonLike, +{ + type Data = T; fn find(&self, data: JsonPathValue<'a, Self::Data>) -> Vec> { data.flat_map_slice(|data, pref| { - let res_col = deep_path_by_key(data, ObjectField::new(self.key), pref.clone()); + let res_col = data.deep_path_by_key(ObjectField::new(self.key), pref.clone()); if res_col.is_empty() { vec![NoValue] } else { @@ -242,26 +239,32 @@ impl<'a> Path<'a> for DescentObject<'a> { } } -impl<'a> DescentObject<'a> { +impl<'a, T> DescentObject<'a, T> { pub fn new(key: &'a str) -> Self { - DescentObject { key } + DescentObject { + key, + _t: std::marker::PhantomData, + } } } /// the top method of the processing representing the chain of other operators -pub(crate) struct Chain<'a> { - chain: Vec>, +pub struct Chain<'a, T> { + chain: Vec>, is_search_length: bool, } -impl<'a> Chain<'a> { - pub fn new(chain: Vec>, is_search_length: bool) -> Self { +impl<'a, T> Chain<'a, T> +where + T: JsonLike, +{ + pub fn new(chain: Vec>, is_search_length: bool) -> Self { Chain { chain, is_search_length, } } - pub fn from(chain: &'a [JsonPath], root: &'a Value) -> Self { + pub fn from(chain: &'a [JsonPath], root: &'a T) -> Self { let chain_len = chain.len(); let is_search_length = if chain_len > 2 { let mut res = false; @@ -307,8 +310,11 @@ impl<'a> Chain<'a> { } } -impl<'a> Path<'a> for Chain<'a> { - type Data = Value; +impl<'a, T> Path<'a> for Chain<'a, T> +where + T: JsonLike, +{ + type Data = T; fn find(&self, data: JsonPathValue<'a, Self::Data>) -> Vec> { let mut res = vec![data]; @@ -330,7 +336,7 @@ mod tests { use crate::parser::macros::{chain, idx}; use crate::parser::model::{JsonPath, JsonPathIndex}; use crate::path; - use crate::path::top::{deep_flatten, json_path_instance, Function, ObjectField, RootPointer}; + use crate::path::top::{json_path_instance, Function, ObjectField, RootPointer}; use crate::path::{JsonPathValue, Path}; use crate::JsonPathValue::NoValue; use serde_json::json; @@ -459,8 +465,9 @@ mod tests { } #[test] fn deep_path_test() { + use crate::path::JsonLike; let value = json!([1]); - let r = deep_flatten(&value, "".to_string()); + let r = value.deep_flatten("".to_string()); assert_eq!(r, vec![(&json!(1), "[0]".to_string())]) }