From 1d375e6dfa34cedbba233cc6b144852acd365962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Tue, 4 Jun 2024 23:45:16 +0200 Subject: [PATCH] get rid of JsonPathFinder and Boxes (#58) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Revert "61 regex perf (#62)" This reverts commit a07c7b6085c0f26833ef7004fda96b402feb01fa. * try to get rid of JsonPathFinder and Boxes - JsonPathFinder interface does not really benefit from storing the json or path internally - trying to get rid of the Box<> that is used inside of JsonPathFinder * some clippy fixes * add benches for find slicing equal bench with reuse time: [510.30 ns 512.16 ns 514.26 ns] equal bench without reuse time: [21.436 µs 21.456 µs 21.479 µs] regex bench with reuse time: [58.875 µs 58.925 µs 58.975 µs] regex bench without reuse time: [85.324 µs 85.416 µs 85.517 µs] JsonPathInst generation time: [23.988 µs 24.019 µs 24.052 µs] --- CHANGELOG.md | 96 ++++----- Cargo.toml | 13 +- README.md | 43 ---- benches/equal.rs | 41 ++++ benches/regex.rs | 48 +++++ benches/regex_bench.rs | 40 ---- src/lib.rs | 412 +++++++++++++-------------------------- src/path/config.rs | 16 -- src/path/config/cache.rs | 115 ----------- src/path/index.rs | 83 ++++---- src/path/json.rs | 43 ++-- src/path/mod.rs | 34 +--- src/path/top.rs | 43 ++-- 13 files changed, 358 insertions(+), 669 deletions(-) create mode 100644 benches/equal.rs create mode 100644 benches/regex.rs delete mode 100644 benches/regex_bench.rs delete mode 100644 src/path/config.rs delete mode 100644 src/path/config/cache.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0919803..c10378f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,48 +1,48 @@ -* **`0.1.0`** - * Initial implementation -* **`0.1.1`** - * Technical improvements -* **`0.1.2`** - * added a trait to obtain the result from value - * added a method to get the cloned as Value - * change the name of the general method* -* **`0.1.4`** - * add an ability to use references instead of values - * fix some clippy issues -* **`0.1.5`** - * correct grammar for `$.[..]` -* **`0.1.6`** - * add logical OR and logical And to filters - * fix bugs with objects in filters - * add internal macros to generate path objects -* **`0.2.0`** - * add json path value as a result for the library - * add functions (size) - * change a logical operator `size` into function `size()` -* **`0.2.1`** - * changed the contract for length() function. -* **`0.2.2`** - * add ..* -* **`0.2.5`** - * build for tags -* **`0.2.6`** - * make parser mod public -* **`0.3.0`** - * introduce the different behaviour for empty results and non-existing result -* **`0.3.2`** - * make jsonpath inst cloneable. -* **`0.3.3`** - * fix a bug with the logical operators -* **`0.3.4`** - * add a result as a path -* **`0.3.5`** - * add `!` negation operation in filters - * allow using () in filters -* **`0.5`** - * add config for jsonpath - * add an option to add a regex cache for boosting performance -* **`0.5.1`** - * add double quotes for the expressions (before it was only possible to use single quotes) - * add Debug on the JsonPathFinder - - +- **`0.1.0`** + - Initial implementation +- **`0.1.1`** + - Technical improvements +- **`0.1.2`** + - added a trait to obtain the result from value + - added a method to get the cloned as Value + - change the name of the general method\* +- **`0.1.4`** + - add an ability to use references instead of values + - fix some clippy issues +- **`0.1.5`** + - correct grammar for `$.[..]` +- **`0.1.6`** + - add logical OR and logical And to filters + - fix bugs with objects in filters + - add internal macros to generate path objects +- **`0.2.0`** + - add json path value as a result for the library + - add functions (size) + - change a logical operator `size` into function `size()` +- **`0.2.1`** + - changed the contract for length() function. +- **`0.2.2`** + - add ..\* +- **`0.2.5`** + - build for tags +- **`0.2.6`** + - make parser mod public +- **`0.3.0`** + - introduce the different behaviour for empty results and non-existing result +- **`0.3.2`** + - make jsonpath inst cloneable. +- **`0.3.3`** + - fix a bug with the logical operators +- **`0.3.4`** + - add a result as a path +- **`0.3.5`** + - add `!` negation operation in filters + - allow using () in filters +- **`0.5`** + - add config for jsonpath + - add an option to add a regex cache for boosting performance +- **`0.5.1`** + - add double quotes for the expressions (before it was only possible to use single quotes) + - add Debug on the JsonPathFinder +- **`0.6`** + - allow to reuse regex, that improves performance without needing an internal cache diff --git a/Cargo.toml b/Cargo.toml index 5057e98..d709ff7 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.5.1" +version = "0.6.0" authors = ["BorisZhguchev "] edition = "2018" license-file = "LICENSE" @@ -17,12 +17,15 @@ regex = "1" pest = "2.0" pest_derive = "2.0" thiserror = "1.0.50" -lazy_static = "1.4" -once_cell = "1.19.0" [dev-dependencies] +lazy_static = "1.0" criterion = "0.5.1" [[bench]] -name = "regex_bench" -harness = false \ No newline at end of file +name = "regex" +harness = false + +[[bench]] +name = "equal" +harness = false diff --git a/README.md b/README.md index cc09de2..8384f80 100644 --- a/README.md +++ b/README.md @@ -389,50 +389,7 @@ fn test() { ** If the value has been modified during the search, there is no way to find a path of a new value. It can happen if we try to find a length() of array, for in stance.** -## Configuration -The JsonPath provides a wat to configure the search by using `JsonPathConfig`. - -```rust -pub fn main() { - let cfg = JsonPathConfig::new(RegexCache::Implemented(DefaultRegexCacheInst::default())); -} -``` - -### Regex cache -The configuration provides an ability to use a regex cache to improve the [performance](https://github.com/besok/jsonpath-rust/issues/61) - -To instantiate the cache needs to use `RegexCache` enum with the implementation of the trait `RegexCacheInst`. -Default implementation `DefaultRegexCacheInst` uses `Arc>>`. -The pair of Box or Value and config can be used: -```rust -pub fn main(){ - let cfg = JsonPathConfig::new(RegexCache::Implemented(DefaultRegexCacheInst::default())); - let json = Box::new(json!({ - "author":"abcd(Rees)", - })); - - let _v = (json, cfg).path("$.[?(@.author ~= '.*(?i)d\\(Rees\\)')]") - .expect("the path is correct"); - - -} -``` -or using `JsonPathFinder` : - -```rust -fn main() { - let cfg = JsonPathConfig::new(RegexCache::Implemented(DefaultRegexCacheInst::default())); - let finder = JsonPathFinder::from_str_with_cfg( - r#"{"first":{"second":[{"active":1},{"passive":1}]}}"#, - "$.first.second[?(@.active)]", - cfg, - ).unwrap(); - let slice_of_data: Vec<&Value> = finder.find_slice(); - let js = json!({"active":1}); - assert_eq!(slice_of_data, vec![JsonPathValue::Slice(&js, "$.first.second[0]".to_string())]); -} -``` ## The structure diff --git a/benches/equal.rs b/benches/equal.rs new file mode 100644 index 0000000..10104dc --- /dev/null +++ b/benches/equal.rs @@ -0,0 +1,41 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use jsonpath_rust::{JsonPathInst, JsonPathQuery}; +use serde_json::json; +use std::str::FromStr; + +struct SearchData { + json: serde_json::Value, + path: JsonPathInst, +} + +const PATH: &'static str = "$.[?(@.author == 'abcd(Rees)')]"; + +fn equal_perf_test_with_reuse(cfg: &SearchData) { + let _v = jsonpath_rust::find(&cfg.path, &cfg.json); +} + +fn equal_perf_test_without_reuse() { + let json = Box::new(json!({ + "author":"abcd(Rees)", + })); + + let _v = json.path(PATH).expect("the path is correct"); +} + +pub fn criterion_benchmark(c: &mut Criterion) { + let data = SearchData { + json: json!({ + "author":"abcd(Rees)", + }), + path: JsonPathInst::from_str(PATH).unwrap(), + }; + c.bench_function("equal bench with reuse", |b| { + b.iter(|| equal_perf_test_with_reuse(&data)) + }); + c.bench_function("equal bench without reuse", |b| { + b.iter(|| equal_perf_test_without_reuse()) + }); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/benches/regex.rs b/benches/regex.rs new file mode 100644 index 0000000..6e0941d --- /dev/null +++ b/benches/regex.rs @@ -0,0 +1,48 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use jsonpath_rust::{JsonPathInst, JsonPathQuery}; +use serde_json::json; +use std::str::FromStr; + +struct SearchData { + json: serde_json::Value, + path: JsonPathInst, +} + +const PATH: &'static str = "$.[?(@.author ~= '.*(?i)d\\(Rees\\)')]"; + +fn regex_perf_test_with_reuse(cfg: &SearchData) { + let _v = jsonpath_rust::find(&cfg.path, &cfg.json); +} + +fn regex_perf_test_without_reuse() { + let json = Box::new(json!({ + "author":"abcd(Rees)", + })); + + let _v = json.path(PATH).expect("the path is correct"); +} + +fn json_path_inst_compiling() { + let _v = JsonPathInst::from_str(PATH).unwrap(); +} + +pub fn criterion_benchmark(c: &mut Criterion) { + let data = SearchData { + json: json!({ + "author":"abcd(Rees)", + }), + path: JsonPathInst::from_str(PATH).unwrap(), + }; + c.bench_function("regex bench with reuse", |b| { + b.iter(|| regex_perf_test_with_reuse(&data)) + }); + c.bench_function("regex bench without reuse", |b| { + b.iter(|| regex_perf_test_without_reuse()) + }); + c.bench_function("JsonPathInst generation", |b| { + b.iter(|| json_path_inst_compiling()) + }); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/benches/regex_bench.rs b/benches/regex_bench.rs deleted file mode 100644 index 2b88e7f..0000000 --- a/benches/regex_bench.rs +++ /dev/null @@ -1,40 +0,0 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use jsonpath_rust::path::config::cache::{DefaultRegexCacheInst, RegexCache}; -use jsonpath_rust::path::config::JsonPathConfig; -use jsonpath_rust::{JsonPathFinder, JsonPathInst, JsonPathQuery}; -use once_cell::sync::Lazy; -use serde_json::{json, Value}; -use std::str::FromStr; - -fn regex_perf_test_with_cache(cfg: JsonPathConfig) { - let json = Box::new(json!({ - "author":"abcd(Rees)", - })); - - let _v = (json, cfg) - .path("$.[?(@.author ~= '.*(?i)d\\(Rees\\)')]") - .expect("the path is correct"); -} - -fn regex_perf_test_without_cache() { - let json = Box::new(json!({ - "author":"abcd(Rees)", - })); - - let _v = json - .path("$.[?(@.author ~= '.*(?i)d\\(Rees\\)')]") - .expect("the path is correct"); -} - -pub fn criterion_benchmark(c: &mut Criterion) { - let cfg = JsonPathConfig::new(RegexCache::Implemented(DefaultRegexCacheInst::default())); - c.bench_function("regex bench without cache", |b| { - b.iter(|| regex_perf_test_without_cache()) - }); - c.bench_function("regex bench with cache", |b| { - b.iter(|| regex_perf_test_with_cache(cfg.clone())) - }); -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); diff --git a/src/lib.rs b/src/lib.rs index 7008164..1e0fd25 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,31 +81,17 @@ //! //! # Examples //!```rust -//! use serde_json::{json,Value}; -//! use jsonpath_rust::jp_v; -//! use self::jsonpath_rust::JsonPathFinder; -//! use self::jsonpath_rust::JsonPathValue; +//! use std::str::FromStr; +//! use serde_json::{json, Value}; +//! use jsonpath_rust::{jp_v, find_slice, JsonPathValue, JsonPathInst}; //! -//! fn test(){ -//! let finder = JsonPathFinder::from_str(r#"{"first":{"second":[{"active":1},{"passive":1}]}}"#, "$.first.second[?(@.active)]").unwrap(); -//! let slice_of_data:Vec> = finder.find_slice(); +//! fn test() -> Result<(), Box> { +//! let json = serde_json::from_str(r#"{"first":{"second":[{"active":1},{"passive":1}]}}"#)?; +//! let path = JsonPathInst::from_str("$.first.second[?(@.active)]")?; +//! let slice_of_data:Vec> = find_slice(&path, &json); //! let js = json!({"active":1}); //! assert_eq!(slice_of_data, jp_v![&js;"$.first.second[0]",]); -//! } -//! ``` -//! or even simpler: -//! -//!``` -//! use serde_json::{json,Value}; -//! use self::jsonpath_rust::JsonPathFinder; -//! use self::jsonpath_rust::JsonPathValue; -//! fn test(json: &str, path: &str, expected: Vec>) { -//! match JsonPathFinder::from_str(json, path) { -//! Ok(finder) => assert_eq!(finder.find_slice(), expected), -//! Err(e) => panic!("error while parsing json or jsonpath: {}", e) -//! } -//! -//! +//! # Ok(()) //! } //! ``` //! @@ -116,12 +102,10 @@ use crate::parser::model::JsonPath; use crate::parser::parser::parse_json_path; -use crate::path::config::JsonPathConfig; -use crate::path::{json_path_instance, PathInstance}; +use crate::path::json_path_instance; use serde_json::Value; use std::convert::TryInto; -use std::fmt; -use std::fmt::{Debug, Formatter}; +use std::fmt::Debug; use std::ops::Deref; use std::str::FromStr; use JsonPathValue::{NewValue, NoValue, Slice}; @@ -139,27 +123,28 @@ extern crate pest; /// # Examples: /// ``` /// use std::str::FromStr; -/// use serde_json::{json,Value}; +/// use serde_json::{json, Value}; /// use jsonpath_rust::jp_v; -/// use crate::jsonpath_rust::{JsonPathFinder,JsonPathQuery,JsonPathInst,JsonPathValue}; -///fn test(){ -/// let json: Value = serde_json::from_str("{}").unwrap(); -/// let v = json.path("$..book[?(@.author size 10)].title").unwrap(); -/// assert_eq!(v, json!([])); +/// use jsonpath_rust::{find_slice, JsonPathQuery, JsonPathInst, JsonPathValue}; +/// +/// fn test() -> Result<(), Box> { +/// let json: Value = serde_json::from_str("{}")?; +/// let v = json.path("$..book[?(@.author size 10)].title")?; +/// assert_eq!(v, json!([])); /// -/// let json: Value = serde_json::from_str("{}").unwrap(); -/// let path = json.path("$..book[?(@.author size 10)].title").unwrap(); +/// let json: Value = serde_json::from_str("{}")?; +/// let path = json.path("$..book[?(@.author size 10)].title")?; /// -/// assert_eq!(path, json!(["Sayings of the Century"])); +/// assert_eq!(path, json!(["Sayings of the Century"])); /// -/// let json: Box = serde_json::from_str("{}").unwrap(); -/// let path: Box = Box::from(JsonPathInst::from_str("$..book[?(@.author size 10)].title").unwrap()); -/// let finder = JsonPathFinder::new(json, path); +/// let json: Value = serde_json::from_str("{}")?; +/// let path: JsonPathInst = JsonPathInst::from_str("$..book[?(@.author size 10)].title")?; /// -/// let v = finder.find_slice(); -/// let js = json!("Sayings of the Century"); -/// assert_eq!(v, jp_v![&js;"",]); -/// } +/// let v = find_slice(&path, &json); +/// let js = json!("Sayings of the Century"); +/// assert_eq!(v, jp_v![&js;"",]); +/// # Ok(()) +/// } /// /// ``` /// #Note: @@ -184,12 +169,8 @@ impl FromStr for JsonPathInst { } impl JsonPathInst { - pub fn find_slice<'a>( - &'a self, - value: &'a Value, - cfg: JsonPathConfig, - ) -> Vec> { - json_path_instance(&self.inner, value, cfg) + pub fn find_slice<'a>(&'a self, value: &'a Value) -> Vec> { + json_path_instance(&self.inner, value) .find(JsonPathValue::from_root(value)) .into_iter() .filter(|v| v.has_value()) @@ -223,54 +204,44 @@ impl<'a> Deref for JsonPtr<'a, Value> { } } -impl JsonPathQuery for Box { - fn path(self, query: &str) -> Result { - let p = JsonPathInst::from_str(query)?; - Ok(JsonPathFinder::new(self, Box::new(p)).find()) - } -} - -impl JsonPathQuery for (Box, JsonPathConfig) { - fn path(self, query: &str) -> Result { - let p = JsonPathInst::from_str(query)?; - Ok(JsonPathFinder::new_with_cfg(self.0, Box::new(p), self.1).find()) - } -} - impl JsonPathQuery for Value { fn path(self, query: &str) -> Result { let p = JsonPathInst::from_str(query)?; - Ok(JsonPathFinder::new(Box::new(self), Box::new(p)).find()) + Ok(find(&p, &self)) } } -impl JsonPathQuery for (Value, JsonPathConfig) { +/* +impl JsonPathQuery for T + where T: Deref { fn path(self, query: &str) -> Result { let p = JsonPathInst::from_str(query)?; - Ok(JsonPathFinder::new_with_cfg(Box::new(self.0), Box::new(p), self.1).find()) + Ok(find(&p, self.deref())) } } + */ /// just to create a json path value of data /// Example: -/// - json_path_value(&json) = `JsonPathValue::Slice(&json)` -/// - json_path_value(&json,) = `vec![JsonPathValue::Slice(&json)]` -/// - `json_path_value[&json1,&json1]` = `vec![JsonPathValue::Slice(&json1),JsonPathValue::Slice(&json2)]` -/// - json_path_value(json) = `JsonPathValue::NewValue(json)` +/// - `jp_v(&json) = JsonPathValue::Slice(&json)` +/// - `jp_v(&json;"foo") = JsonPathValue::Slice(&json, "foo".to_string())` +/// - `jp_v(&json,) = vec![JsonPathValue::Slice(&json)]` +/// - `jp_v[&json1,&json1] = vec![JsonPathValue::Slice(&json1),JsonPathValue::Slice(&json2)]` +/// - `jp_v(json) = JsonPathValue::NewValue(json)` /// ``` /// use std::str::FromStr; -/// use serde_json::{json,Value}; -/// use jsonpath_rust::jp_v; -/// use crate::jsonpath_rust::{JsonPathFinder,JsonPathQuery,JsonPathInst,JsonPathValue}; -///fn test(){ -/// let json: Box = serde_json::from_str("{}").unwrap(); -/// let path: Box = Box::from(JsonPathInst::from_str("$..book[?(@.author size 10)].title").unwrap()); -/// let finder = JsonPathFinder::new(json, path); +/// use serde_json::{json, Value}; +/// use jsonpath_rust::{jp_v, find_slice, JsonPathQuery, JsonPathInst, JsonPathValue}; +/// +/// fn test() -> Result<(), Box> { +/// let json: Value = serde_json::from_str("{}")?; +/// let path: JsonPathInst = JsonPathInst::from_str("$..book[?(@.author size 10)].title")?; +/// let v = find_slice(&path, &json); /// -/// let v = finder.find_slice(); -/// let js = json!("Sayings of the Century"); -/// assert_eq!(v, jp_v![&js;"",]); -/// } +/// let js = json!("Sayings of the Century"); +/// assert_eq!(v, jp_v![&js;"",]); +/// # Ok(()) +/// } /// ``` #[macro_export] macro_rules! jp_v { @@ -278,9 +249,9 @@ macro_rules! jp_v { JsonPathValue::Slice(&$v, String::new()) }; - (&$v:expr ; $s:expr) =>{ + (&$v:expr ; $s:expr) =>{ JsonPathValue::Slice(&$v, $s.to_string()) - }; + }; ($(&$v:expr;$s:expr),+ $(,)?) =>{ { @@ -314,7 +285,6 @@ type JsPathStr = String; pub(crate) fn jsp_idx(prefix: &str, idx: usize) -> String { format!("{}[{}]", prefix, idx) } - pub(crate) fn jsp_obj(prefix: &str, key: &str) -> String { format!("{}.['{}']", prefix, key) } @@ -361,6 +331,7 @@ impl<'a, Data> JsonPathValue<'a, Data> { 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> { data.into_iter() .map(|(data, pref)| Slice(data, pref)) @@ -424,140 +395,72 @@ impl<'a, Data> JsonPathValue<'a, Data> { } } -/// The base structure stitching the json instance and jsonpath instance -pub struct JsonPathFinder { - json: Box, - path: Box, - cfg: JsonPathConfig, -} - -impl Debug for JsonPathFinder { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let json_as_str = serde_json::to_string(&*self.json).map_err(|_| fmt::Error)?; - - f.write_str("JsonPathFinder:")?; - f.write_str(format!(" json:{}", json_as_str).as_str())?; - f.write_str(format!(" path:{:?}", self.path).as_str())?; - Ok(()) +/// finds a slice of data in the set json. +/// The result is a vector of references to the incoming structure. +pub fn find_slice<'a>(path: &'a JsonPathInst, json: &'a Value) -> Vec> { + let instance = json_path_instance(&path.inner, json); + let res = instance.find(JsonPathValue::from_root(json)); + let has_v: Vec> = res.into_iter().filter(|v| v.has_value()).collect(); + + if has_v.is_empty() { + vec![NoValue] + } else { + has_v } } -impl JsonPathFinder { - /// creates a new instance of [JsonPathFinder] - pub fn new(json: Box, path: Box) -> Self { - JsonPathFinder { - json, - path, - cfg: JsonPathConfig::default(), - } - } - - pub fn new_with_cfg(json: Box, path: Box, cfg: JsonPathConfig) -> Self { - JsonPathFinder { json, path, cfg } - } - - /// sets a cfg with a new one - pub fn set_cfg(&mut self, cfg: JsonPathConfig) { - self.cfg = cfg - } - - /// updates a path with a new one - pub fn set_path(&mut self, path: Box) { - self.path = path - } - /// updates a json with a new one - pub fn set_json(&mut self, json: Box) { - self.json = json - } - /// updates a json from string and therefore can be some parsing errors - pub fn set_json_str(&mut self, json: &str) -> Result<(), String> { - self.json = serde_json::from_str(json).map_err(|e| e.to_string())?; - Ok(()) - } - /// updates a path from string and therefore can be some parsing errors - pub fn set_path_str(&mut self, path: &str) -> Result<(), String> { - self.path = Box::new(JsonPathInst::from_str(path)?); - Ok(()) - } - - /// create a new instance from string and therefore can be some parsing errors - pub fn from_str(json: &str, path: &str) -> Result { - let json = serde_json::from_str(json).map_err(|e| e.to_string())?; - let path = Box::new(JsonPathInst::from_str(path)?); - Ok(JsonPathFinder::new(json, path)) - } - pub fn from_str_with_cfg(json: &str, path: &str, cfg: JsonPathConfig) -> Result { - let json = serde_json::from_str(json).map_err(|e| e.to_string())?; - let path = Box::new(JsonPathInst::from_str(path)?); - Ok(JsonPathFinder::new_with_cfg(json, path, cfg)) - } - - /// creates an instance to find a json slice from the json - pub fn instance(&self) -> PathInstance { - json_path_instance(&self.path.inner, &self.json, self.cfg.clone()) - } - /// finds a slice of data in the set json. - /// The result is a vector of references to the incoming structure. - pub fn find_slice(&self) -> Vec> { - let res = self.instance().find(JsonPathValue::from_root(&self.json)); - let has_v: Vec> = - res.into_iter().filter(|v| v.has_value()).collect(); - - if has_v.is_empty() { - vec![NoValue] - } else { - has_v - } - } - - /// finds a slice of data and wrap it with Value::Array by cloning the data. - /// Returns either an array of elements or Json::Null if the match is incorrect. - pub fn find(&self) -> Value { - let slice = self.find_slice(); - if !slice.is_empty() { - if JsonPathValue::only_no_value(&slice) { - Value::Null - } else { - Value::Array( - self.find_slice() - .into_iter() - .filter(|v| v.has_value()) - .map(|v| v.to_data()) - .collect(), - ) - } +/// finds a slice of data and wrap it with Value::Array by cloning the data. +/// Returns either an array of elements or Json::Null if the match is incorrect. +pub fn find(path: &JsonPathInst, json: &Value) -> Value { + let slice = find_slice(path, json); + if !slice.is_empty() { + if JsonPathValue::only_no_value(&slice) { + Value::Null } else { - Value::Array(vec![]) + Value::Array( + slice + .into_iter() + .filter(|v| v.has_value()) + .map(|v| v.to_data()) + .collect(), + ) } + } else { + Value::Array(vec![]) } - /// finds a path of the values. - /// If the values has been obtained by moving the data out of the initial json the path is absent. - pub fn find_as_path(&self) -> Value { - Value::Array( - self.find_slice() - .into_iter() - .flat_map(|v| v.to_path()) - .map(|v| v.into()) - .collect(), - ) - } +} +/// finds a path of the values. +/// If the values has been obtained by moving the data out of the initial json the path is absent. +pub fn find_as_path(path: &JsonPathInst, json: &Value) -> Value { + Value::Array( + find_slice(path, json) + .into_iter() + .flat_map(|v| v.to_path()) + .map(|v| v.into()) + .collect(), + ) } #[cfg(test)] mod tests { - use crate::path::config::JsonPathConfig; use crate::JsonPathQuery; use crate::JsonPathValue::{NoValue, Slice}; - use crate::{jp_v, JsonPathFinder, JsonPathInst, JsonPathValue}; + use crate::{jp_v, JsonPathInst, JsonPathValue}; use serde_json::{json, Value}; use std::ops::Deref; use std::str::FromStr; fn test(json: &str, path: &str, expected: Vec>) { - match JsonPathFinder::from_str(json, path) { - Ok(finder) => assert_eq!(finder.find_slice(), expected), - Err(e) => panic!("error while parsing json or jsonpath: {}", e), - } + let json: Value = match serde_json::from_str(json) { + Ok(json) => json, + Err(e) => panic!("error while parsing json: {}", e), + }; + let path = match JsonPathInst::from_str(path) { + Ok(path) => path, + Err(e) => panic!("error while parsing jsonpath: {}", e), + }; + + assert_eq!(super::find_slice(&path, &json), expected) } fn template_json<'a>() -> &'a str { @@ -972,9 +875,7 @@ mod tests { JsonPathInst::from_str("$..book[?(@.author size 10)].title") .expect("the path is correct"), ); - let finder = JsonPathFinder::new(json, path); - - let v = finder.find_slice(); + let v = super::find_slice(&path, &json); let js = json!("Sayings of the Century"); assert_eq!(v, jp_v![&js;"$.['store'].['book'][0].['title']",]); } @@ -985,9 +886,7 @@ mod tests { let path: Box = Box::from( JsonPathInst::from_str("$.[?(@.verb == 'TEST')]").expect("the path is correct"), ); - let finder = JsonPathFinder::new(json, path); - - let v = finder.find_slice(); + let v = super::find_slice(&path, &json); let js = json!({"verb":"TEST"}); assert_eq!(v, jp_v![&js;"$[0]",]); } @@ -1000,9 +899,7 @@ mod tests { JsonPathInst::from_str("$.[?(@.verb == 'TEST')].length()") .expect("the path is correct"), ); - let finder = JsonPathFinder::new(json, path); - - let v = finder.find(); + let v = super::find(&path, &json); let js = json!([2]); assert_eq!(v, js); @@ -1010,8 +907,7 @@ mod tests { Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); let path: Box = Box::from(JsonPathInst::from_str("$.length()").expect("the path is correct")); - let finder = JsonPathFinder::new(json, path); - assert_eq!(finder.find(), json!([3])); + assert_eq!(super::find(&path, &json), json!([3])); // length of search following the wildcard returns correct result let json: Box = @@ -1020,37 +916,32 @@ mod tests { JsonPathInst::from_str("$.[?(@.verb == 'TEST')].[*].length()") .expect("the path is correct"), ); - let finder = JsonPathFinder::new(json, path); - assert_eq!(finder.find(), json!([3])); + assert_eq!(super::find(&path, &json), json!([3])); // length of object returns 0 let json: Box = Box::new(json!({"verb": "TEST"})); let path: Box = Box::from(JsonPathInst::from_str("$.length()").expect("the path is correct")); - let finder = JsonPathFinder::new(json, path); - assert_eq!(finder.find(), Value::Null); + assert_eq!(super::find(&path, &json), Value::Null); // length of integer returns null let json: Box = Box::new(json!(1)); let path: Box = Box::from(JsonPathInst::from_str("$.length()").expect("the path is correct")); - let finder = JsonPathFinder::new(json, path); - assert_eq!(finder.find(), Value::Null); + assert_eq!(super::find(&path, &json), Value::Null); // length of array returns correct result let json: Box = Box::new(json!([[1], [2], [3]])); let path: Box = Box::from(JsonPathInst::from_str("$.length()").expect("the path is correct")); - let finder = JsonPathFinder::new(json, path); - assert_eq!(finder.find(), json!([3])); + assert_eq!(super::find(&path, &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 = Box::from(JsonPathInst::from_str("$.not.exist.length()").expect("the path is correct")); - let finder = JsonPathFinder::new(json, path); - assert_eq!(finder.find(), Value::Null); + assert_eq!(super::find(&path, &json), Value::Null); // seraching one value returns correct length let json: Box = @@ -1058,9 +949,8 @@ mod tests { let path: Box = Box::from( JsonPathInst::from_str("$.[?(@.verb == 'RUN')].length()").expect("the path is correct"), ); - let finder = JsonPathFinder::new(json, path); - let v = finder.find(); + let v = super::find(&path, &json); let js = json!([1]); assert_eq!(v, js); @@ -1071,9 +961,8 @@ mod tests { JsonPathInst::from_str("$.[?(@.verb == 'RUN')].key123.length()") .expect("the path is correct"), ); - let finder = JsonPathFinder::new(json, path); - let v = finder.find(); + let v = super::find(&path, &json); let js = json!(null); assert_eq!(v, js); @@ -1082,9 +971,8 @@ mod tests { Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}])); let path: Box = Box::from(JsonPathInst::from_str("$.[0].length()").expect("the path is correct")); - let finder = JsonPathFinder::new(json, path); - let v = finder.find(); + let v = super::find(&path, &json); let js = Value::Null; assert_eq!(v, js); @@ -1093,9 +981,8 @@ mod tests { let path: Box = Box::from( JsonPathInst::from_str("$.[?(@.prop)].prop.[0].length()").expect("the path is correct"), ); - let finder = JsonPathFinder::new(json, path); - let v = finder.find(); + let v = super::find(&path, &json); let js = json!([3]); assert_eq!(v, js); @@ -1104,9 +991,8 @@ mod tests { let path: Box = Box::from( JsonPathInst::from_str("$.[?(@.prop)].prop.[1].length()").expect("the path is correct"), ); - let finder = JsonPathFinder::new(json, path); - let v = finder.find(); + let v = super::find(&path, &json); let js = Value::Null; assert_eq!(v, js); } @@ -1119,8 +1005,7 @@ mod tests { let path: Box = Box::from(JsonPathInst::from_str("$.field[1]").expect("the path is correct")); - let finder = JsonPathFinder::new(json, path); - let v = finder.find_slice(); + let v = super::find_slice(&path, &json); assert_eq!(v, vec![NoValue]); let json: Box = Box::new(json!({ @@ -1129,8 +1014,7 @@ mod tests { let path: Box = Box::from(JsonPathInst::from_str("$.field[1]").expect("the path is correct")); - let finder = JsonPathFinder::new(json, path); - let v = finder.find_slice(); + let v = super::find_slice(&path, &json); assert_eq!(v, vec![NoValue]); } @@ -1142,8 +1026,7 @@ mod tests { let path: Box = Box::from(JsonPathInst::from_str("$.field[?(@ == 0)]").expect("the path is correct")); - let finder = JsonPathFinder::new(json, path); - let v = finder.find_slice(); + let v = super::find_slice(&path, &json); assert_eq!(v, vec![NoValue]); } @@ -1156,8 +1039,7 @@ mod tests { let path: Box = Box::from( JsonPathInst::from_str("$.field[?(@.f_ == 0)]").expect("the path is correct"), ); - let finder = JsonPathFinder::new(json, path); - let v = finder.find_slice(); + let v = super::find_slice(&path, &json); assert_eq!(v, vec![NoValue]); } @@ -1169,8 +1051,7 @@ mod tests { let path: Box = Box::from(JsonPathInst::from_str("$..f_").expect("the path is correct")); - let finder = JsonPathFinder::new(json, path); - let v = finder.find_slice(); + let v = super::find_slice(&path, &json); assert_eq!( v, vec![Slice(&json!(1), "$.['field'][1].['f'].['f_']".to_string())] @@ -1185,15 +1066,13 @@ mod tests { let path: Box = Box::from(JsonPathInst::from_str("$.field_.field").expect("the path is correct")); - let finder = JsonPathFinder::new(json.clone(), path); - let v = finder.find_slice(); + let v = super::find_slice(&path, &json); assert_eq!(v, vec![NoValue]); let path: Box = Box::from( JsonPathInst::from_str("$.field_.field[?(@ == 1)]").expect("the path is correct"), ); - let finder = JsonPathFinder::new(json, path); - let v = finder.find_slice(); + let v = super::find_slice(&path, &json); assert_eq!(v, vec![NoValue]); } @@ -1205,9 +1084,7 @@ mod tests { let path: Box = Box::from( JsonPathInst::from_str("$.[?(@.verb == \"RUN1\")]").expect("the path is correct"), ); - let finder = JsonPathFinder::new(json, path); - - let v = finder.find(); + let v = super::find(&path, &json); let js = json!(null); assert_eq!(v, js); } @@ -1221,8 +1098,7 @@ mod tests { let path: Box = Box::from( JsonPathInst::from_str("$.field.field.length()").expect("the path is correct"), ); - let finder = JsonPathFinder::new(json, path); - let v = finder.find_slice(); + let v = super::find_slice(&path, &json); assert_eq!(v, vec![NoValue]); let json: Box = Box::new(json!({ @@ -1231,8 +1107,7 @@ mod tests { let path: Box = Box::from( JsonPathInst::from_str("$.field[?(@.a == 0)].f.length()").expect("the path is correct"), ); - let finder = JsonPathFinder::new(json, path); - let v = finder.find_slice(); + let v = super::find_slice(&path, &json); assert_eq!(v, vec![NoValue]); } @@ -1246,7 +1121,7 @@ mod tests { let query = JsonPathInst::from_str("$..book[?(@.author size 10)].title") .expect("the path is correct"); - let results = query.find_slice(&json, JsonPathConfig::default()); + let results = query.find_slice(&json); let v = results.first().expect("to get value"); // V can be implicitly converted to &Value @@ -1264,18 +1139,14 @@ mod tests { JsonPathInst::from_str("$.first[?(@.does_not_exist && @.does_not_exist >= 1.0)]") .expect("the path is correct"), ); - let finder = JsonPathFinder::new(json.clone(), path); - - let v = finder.find_slice(); + let v = super::find_slice(&path, &json); 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, path); - - let v = finder.find_slice(); + let v = super::find_slice(&path, &json); assert_eq!(v, vec![NoValue]); } @@ -1289,9 +1160,8 @@ mod tests { JsonPathInst::from_str("$.[?(@.author ~= '(?i)d\\(Rees\\)')]") .expect("the path is correct"), ); - let finder = JsonPathFinder::new(json.clone(), path); assert_eq!( - finder.find_slice(), + super::find_slice(&path, &json.clone()), vec![Slice(&json!({"author":"abcd(Rees)"}), "$".to_string())] ); } @@ -1303,13 +1173,12 @@ mod tests { 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(); + let v = super::find_slice(&path, &json); assert_eq!( v, vec![Slice( &json!({"second":{"active": 1}}), - "$.['first']".to_string(), + "$.['first']".to_string() )] ); @@ -1317,13 +1186,12 @@ mod tests { 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(); + let v = super::find_slice(&path, &json); assert_eq!( v, vec![Slice( &json!({"second":{"active": 1}}), - "$.['first']".to_string(), + "$.['first']".to_string() )] ); @@ -1331,13 +1199,12 @@ mod tests { 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(); + let v = super::find_slice(&path, &json); assert_eq!( v, vec![Slice( &json!({"second":{"active": 1}}), - "$.['first']".to_string(), + "$.['first']".to_string() )] ); @@ -1345,13 +1212,12 @@ mod tests { 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(); + let v = super::find_slice(&path, &json); assert_eq!( v, vec![Slice( &json!({"second":{"active": 1}}), - "$.['first']".to_string(), + "$.['first']".to_string() )] ); } diff --git a/src/path/config.rs b/src/path/config.rs deleted file mode 100644 index b534712..0000000 --- a/src/path/config.rs +++ /dev/null @@ -1,16 +0,0 @@ -pub mod cache; - -use crate::path::config::cache::RegexCache; - -/// Configuration to adjust the jsonpath search -#[derive(Clone, Default)] -pub struct JsonPathConfig { - /// cache to provide - pub regex_cache: RegexCache, -} - -impl JsonPathConfig { - pub fn new(regex_cache: RegexCache) -> Self { - Self { regex_cache } - } -} diff --git a/src/path/config/cache.rs b/src/path/config/cache.rs deleted file mode 100644 index ebe7e23..0000000 --- a/src/path/config/cache.rs +++ /dev/null @@ -1,115 +0,0 @@ -use regex::{Error, Regex}; -use serde_json::Value; -use std::collections::HashMap; -use std::sync::{Arc, Mutex, PoisonError}; - -/// The option to provide a cache for regex -/// ``` -/// use serde_json::json; -/// use jsonpath_rust::JsonPathQuery; -/// use jsonpath_rust::path::config::cache::{DefaultRegexCacheInst, RegexCache}; -/// use jsonpath_rust::path::config::JsonPathConfig; -/// -/// let cfg = JsonPathConfig::new(RegexCache::Implemented(DefaultRegexCacheInst::default())); -/// let json = Box::new(json!({ -/// "author":"abcd(Rees)", -/// })); -/// -/// let _v = (json, cfg).path("$.[?(@.author ~= '.*(?i)d\\(Rees\\)')]") -/// .expect("the path is correct"); -#[derive(Clone)] -pub enum RegexCache -where - T: Clone + RegexCacheInst, -{ - Absent, - Implemented(T), -} - -impl RegexCache -where - T: Clone + RegexCacheInst, -{ - pub fn is_implemented(&self) -> bool { - match self { - RegexCache::Absent => false, - RegexCache::Implemented(_) => true, - } - } - pub fn get_instance(&self) -> Result<&T, RegexCacheError> { - match self { - RegexCache::Absent => Err(RegexCacheError::new("the instance is absent".to_owned())), - RegexCache::Implemented(inst) => Ok(inst), - } - } - - pub fn instance(instance: T) -> Self { - RegexCache::Implemented(instance) - } -} -#[allow(clippy::derivable_impls)] -impl Default for RegexCache { - fn default() -> Self { - RegexCache::Absent - } -} - -/// A trait that defines the behavior for regex cache -pub trait RegexCacheInst { - fn validate(&self, regex: &str, values: Vec<&Value>) -> Result; -} - -/// Default implementation for regex cache. It uses Arc and Mutex to be capable of working -/// among the threads. -#[derive(Default, Debug, Clone)] -pub struct DefaultRegexCacheInst { - cache: Arc>>, -} - -impl RegexCacheInst for DefaultRegexCacheInst { - fn validate(&self, regex: &str, values: Vec<&Value>) -> Result { - let mut cache = self.cache.lock()?; - if cache.contains_key(regex) { - let r = cache.get(regex).unwrap(); - Ok(validate(r, values)) - } else { - let new_reg = Regex::new(regex)?; - let result = validate(&new_reg, values); - cache.insert(regex.to_owned(), new_reg); - Ok(result) - } - } -} - -fn validate(r: &Regex, values: Vec<&Value>) -> bool { - for el in values.iter() { - if let Some(v) = el.as_str() { - if r.is_match(v) { - return true; - } - } - } - false -} - -pub struct RegexCacheError { - pub reason: String, -} - -impl From for RegexCacheError { - fn from(value: Error) -> Self { - RegexCacheError::new(value.to_string()) - } -} - -impl From> for RegexCacheError { - fn from(value: PoisonError) -> Self { - RegexCacheError::new(value.to_string()) - } -} - -impl RegexCacheError { - pub fn new(reason: String) -> Self { - Self { reason } - } -} diff --git a/src/path/index.rs b/src/path/index.rs index cc018f0..cabf9d7 100644 --- a/src/path/index.rs +++ b/src/path/index.rs @@ -1,9 +1,9 @@ +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 crate::{jsp_idx, JsonPathConfig}; use serde_json::value::Value::Array; use serde_json::Value; @@ -124,10 +124,10 @@ pub(crate) struct Current<'a> { } impl<'a> Current<'a> { - pub(crate) fn from(jp: &'a JsonPath, root: &'a Value, cfg: JsonPathConfig) -> Self { + pub(crate) fn from(jp: &'a JsonPath, root: &'a Value) -> Self { match jp { JsonPath::Empty => Current::none(), - tail => Current::new(json_path_instance(tail, root, cfg)), + tail => Current::new(json_path_instance(tail, root)), } } pub(crate) fn new(tail: PathInstance<'a>) -> Self { @@ -196,7 +196,6 @@ pub enum FilterPath<'a> { left: PathInstance<'a>, right: PathInstance<'a>, op: &'a FilterSign, - cfg: JsonPathConfig, }, Or { left: PathInstance<'a>, @@ -212,24 +211,23 @@ pub enum FilterPath<'a> { } impl<'a> FilterPath<'a> { - pub(crate) fn new(expr: &'a FilterExpression, root: &'a Value, cfg: JsonPathConfig) -> Self { + pub(crate) fn new(expr: &'a FilterExpression, root: &'a Value) -> Self { match expr { FilterExpression::Atom(left, op, right) => FilterPath::Filter { - left: process_operand(left, root, cfg.clone()), - right: process_operand(right, root, cfg.clone()), + left: process_operand(left, root), + right: process_operand(right, root), op, - cfg, }, FilterExpression::And(l, r) => FilterPath::And { - left: Box::new(FilterPath::new(l, root, cfg.clone())), - right: Box::new(FilterPath::new(r, root, cfg.clone())), + left: Box::new(FilterPath::new(l, root)), + right: Box::new(FilterPath::new(r, root)), }, FilterExpression::Or(l, r) => FilterPath::Or { - left: Box::new(FilterPath::new(l, root, cfg.clone())), - right: Box::new(FilterPath::new(r, root, cfg.clone())), + 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, cfg)), + exp: Box::new(FilterPath::new(exp, root)), }, } } @@ -238,48 +236,45 @@ impl<'a> FilterPath<'a> { two: &'a FilterSign, left: Vec>, right: Vec>, - cfg: JsonPathConfig, ) -> bool { - FilterPath::process_atom(one, left.clone(), right.clone(), cfg.clone()) - || FilterPath::process_atom(two, left, right, cfg) + FilterPath::process_atom(one, left.clone(), right.clone()) + || FilterPath::process_atom(two, left, right) } fn process_atom( op: &'a FilterSign, left: Vec>, right: Vec>, - cfg: JsonPathConfig, ) -> bool { match op { FilterSign::Equal => eq( JsonPathValue::vec_as_data(left), JsonPathValue::vec_as_data(right), ), - FilterSign::Unequal => !FilterPath::process_atom(&FilterSign::Equal, left, right, cfg), + FilterSign::Unequal => !FilterPath::process_atom(&FilterSign::Equal, left, right), FilterSign::Less => less( JsonPathValue::vec_as_data(left), JsonPathValue::vec_as_data(right), ), FilterSign::LeOrEq => { - FilterPath::compound(&FilterSign::Less, &FilterSign::Equal, left, right, cfg) + FilterPath::compound(&FilterSign::Less, &FilterSign::Equal, left, right) } FilterSign::Greater => less( JsonPathValue::vec_as_data(right), JsonPathValue::vec_as_data(left), ), FilterSign::GrOrEq => { - FilterPath::compound(&FilterSign::Greater, &FilterSign::Equal, left, right, cfg) + FilterPath::compound(&FilterSign::Greater, &FilterSign::Equal, left, right) } FilterSign::Regex => regex( JsonPathValue::vec_as_data(left), JsonPathValue::vec_as_data(right), - &cfg.regex_cache, ), FilterSign::In => inside( JsonPathValue::vec_as_data(left), JsonPathValue::vec_as_data(right), ), - FilterSign::Nin => !FilterPath::process_atom(&FilterSign::In, left, right, cfg), - FilterSign::NoneOf => !FilterPath::process_atom(&FilterSign::AnyOf, left, right, cfg), + FilterSign::Nin => !FilterPath::process_atom(&FilterSign::In, left, right), + FilterSign::NoneOf => !FilterPath::process_atom(&FilterSign::AnyOf, left, right), FilterSign::AnyOf => any_of( JsonPathValue::vec_as_data(left), JsonPathValue::vec_as_data(right), @@ -299,16 +294,10 @@ impl<'a> FilterPath<'a> { fn process(&self, curr_el: &'a Value) -> bool { let pref = String::new(); match self { - FilterPath::Filter { - left, - right, - op, - cfg, - } => FilterPath::process_atom( + FilterPath::Filter { left, right, op } => FilterPath::process_atom( op, left.find(Slice(curr_el, pref.clone())), right.find(Slice(curr_el, pref)), - cfg.clone(), ), FilterPath::Or { left, right } => { if !JsonPathValue::vec_as_data(left.find(Slice(curr_el, pref.clone()))).is_empty() { @@ -485,7 +474,7 @@ mod tests { let chain = chain!(path!($), path!("object"), path!(@)); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let res = json!({ "field_1":[1,2,3], "field_2":42, @@ -498,7 +487,7 @@ mod tests { let cur = path!(@,path!("field_3"),path!("a")); let chain = chain!(path!($), path!("object"), cur); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let res1 = json!("b"); let expected_res = vec![JsonPathValue::new_slice( @@ -518,7 +507,7 @@ mod tests { let index = path!(idx!(?filter!(op!(path!(@, path!("field"))), "exists", op!()))); let chain = chain!(path!($), path!("key"), index, path!("field")); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let exp1 = json!([1, 2, 3, 4, 5]); let exp2 = json!(42); @@ -549,7 +538,7 @@ mod tests { let chain = chain!(path!($), path!("key"), index); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let exp1 = json!( {"field":10}); let exp2 = json!( {"field":5}); @@ -568,7 +557,7 @@ mod tests { idx!(?filter!(op!(path!(@, path!("field"))), ">=", op!(chain!(path!($), path!("threshold"))))) ); let chain = chain!(path!($), path!("key"), index); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let expected_res = jp_v![ &exp1;"$.['key'][1]", &exp3;"$.['key'][2]", &exp2;"$.['key'][3]"]; assert_eq!( @@ -580,7 +569,7 @@ mod tests { idx!(?filter!(op!(path!(@, path!("field"))), "<", op!(chain!(path!($), path!("threshold"))))) ); let chain = chain!(path!($), path!("key"), index); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let expected_res = jp_v![&exp4;"$.['key'][0]", &exp4;"$.['key'][4]"]; assert_eq!( path_inst.find(JsonPathValue::from_root(&json)), @@ -591,7 +580,7 @@ mod tests { idx!(?filter!(op!(path!(@, path!("field"))), "<=", op!(chain!(path!($), path!("threshold"))))) ); let chain = chain!(path!($), path!("key"), index); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let expected_res = jp_v![ &exp4;"$.['key'][0]", &exp3;"$.['key'][2]", @@ -616,7 +605,7 @@ mod tests { let index = idx!(?filter!(op!(path!(@,path!("field"))),"~=", op!("[a-zA-Z]+[0-9]#[0-9]+"))); let chain = chain!(path!($), path!("key"), path!(index)); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let exp2 = json!( {"field":"a1#1"}); let expected_res = jp_v![&exp2;"$.['key'][1]",]; @@ -645,7 +634,7 @@ mod tests { let chain = chain!(path!($), JsonPath::Field(String::from("key")), path!(index)); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let exp2 = json!( {"field":"a11#"}); let expected_res = jp_v![&exp2;"$.['key'][0]",]; @@ -669,7 +658,7 @@ mod tests { let index = idx!(?filter!(op!(path!(@, path!("field"))),"size",op!(4))); let chain = chain!(path!($), path!("key"), path!(index)); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let f1 = json!( {"field":"aaaa"}); let f2 = json!( {"field":"dddd"}); @@ -695,7 +684,7 @@ mod tests { op!(path!(@,path!("not_id"))), "==",op!(2) )); let chain = chain!(path!($), path!("obj"), path!(index)); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let js = json!({ "id":1, "not_id": 2, @@ -727,7 +716,7 @@ mod tests { ) ); let chain = chain!(path!($), path!("key"), path!(index), path!("city")); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let a = json!("Athlon"); let d = json!("Dortmund"); let dd = json!("Dublin"); @@ -756,7 +745,7 @@ mod tests { ) ); let chain = chain!(path!($), path!("key"), path!(index), path!("id")); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let j1 = json!(1); assert_eq!( path_inst.find(JsonPathValue::from_root(&json)), @@ -780,7 +769,7 @@ mod tests { ) ); let chain = chain!(path!($), path!("key"), path!(index), path!("id")); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let j1 = json!(1); assert_eq!( path_inst.find(JsonPathValue::from_root(&json)), @@ -808,7 +797,7 @@ mod tests { ) ); let chain = chain!(path!($), path!("key"), path!(index), path!("city")); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let a = json!("Athlon"); let value = jp_v!( &a;"$.['key'][4].['city']",); assert_eq!(path_inst.find(JsonPathValue::from_root(&json)), value) @@ -830,7 +819,7 @@ mod tests { ) ); let chain = chain!(path!($), path!("key"), path!(index), path!("id")); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let j1 = json!(1); assert_eq!( path_inst.find(JsonPathValue::from_root(&json)), @@ -854,7 +843,7 @@ mod tests { ) ); let chain = chain!(path!($), path!("key"), path!(index), path!("id")); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); assert_eq!( path_inst.find(JsonPathValue::from_root(&json)), vec![NoValue] diff --git a/src/path/json.rs b/src/path/json.rs index c29da5d..83d9a7e 100644 --- a/src/path/json.rs +++ b/src/path/json.rs @@ -1,4 +1,3 @@ -use crate::path::config::cache::{RegexCache, RegexCacheInst}; use regex::Regex; use serde_json::Value; @@ -93,24 +92,15 @@ pub fn any_of(left: Vec<&Value>, right: Vec<&Value>) -> bool { false } -/// ensure that the element on the left sides matches the regex on the right side -pub fn regex( - left: Vec<&Value>, - right: Vec<&Value>, - cache: &RegexCache, -) -> bool { +/// 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 cache.is_implemented() { - cache - .get_instance() - .and_then(|inst| inst.validate(str, left)) - .unwrap_or(false) - } else if let Ok(regex) = Regex::new(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) { @@ -118,10 +108,8 @@ pub fn regex( } } } - false - } else { - false } + false } _ => false, } @@ -182,7 +170,6 @@ pub fn eq(left: Vec<&Value>, right: Vec<&Value>) -> bool { #[cfg(test)] mod tests { - use crate::path::config::cache::RegexCache; use crate::path::json::{any_of, eq, less, regex, size, sub_set_of}; use serde_json::{json, Value}; @@ -215,12 +202,12 @@ mod tests { assert!(eq( vec![&left, &left1, &left2, &left3], - vec![&right, &right1, &right2, &right3], + vec![&right, &right1, &right2, &right3] )); assert!(!eq( vec![&left1, &left, &left2, &left3], - vec![&right, &right1, &right2, &right3], + vec![&right, &right1, &right2, &right3] )); } @@ -256,16 +243,8 @@ mod tests { let left3 = json!("a#11"); let left4 = json!("#a11"); - assert!(regex( - vec![&left1, &left2, &left3, &left4], - vec![&right], - &RegexCache::default() - )); - assert!(!regex( - vec![&left1, &left3, &left4], - vec![&right], - &RegexCache::default() - )) + assert!(regex(vec![&left1, &left2, &left3, &left4], vec![&right])); + assert!(!regex(vec![&left1, &left3, &left4], vec![&right])) } #[test] @@ -293,13 +272,13 @@ mod tests { vec![&Value::Array(vec![ left1.clone(), left2.clone(), - left3.clone(), + left3.clone() ])], - vec![&right], + vec![&right] )); assert!(!sub_set_of( vec![&Value::Array(vec![left1, left2, left3, left40])], - vec![&right], + vec![&right] )); } diff --git a/src/path/mod.rs b/src/path/mod.rs index aeda6b9..fbe5e92 100644 --- a/src/path/mod.rs +++ b/src/path/mod.rs @@ -1,12 +1,10 @@ -use crate::{JsonPathConfig, JsonPathValue}; +use crate::JsonPathValue; use serde_json::Value; use crate::parser::model::{Function, JsonPath, JsonPathIndex, Operand}; use crate::path::index::{ArrayIndex, ArraySlice, Current, FilterPath, UnionIndex}; use crate::path::top::*; -/// The module provides the ability to adjust the behavior of the search -pub mod config; /// 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 @@ -32,10 +30,6 @@ pub trait Path<'a> { ) -> Vec> { input.into_iter().flat_map(|d| self.find(d)).collect() } - fn cfg(&self) -> JsonPathConfig { - JsonPathConfig::default() - } - /// defines when we need to invoke `find` or `flat_find` fn needs_all(&self) -> bool { false @@ -46,44 +40,36 @@ pub trait Path<'a> { pub type PathInstance<'a> = Box + 'a>; /// The major method to process the top part of json part -pub fn json_path_instance<'a>( - json_path: &'a JsonPath, - root: &'a Value, - cfg: JsonPathConfig, -) -> PathInstance<'a> { +pub fn json_path_instance<'a>(json_path: &'a JsonPath, root: &'a Value) -> PathInstance<'a> { match json_path { JsonPath::Root => Box::new(RootPointer::new(root)), JsonPath::Field(key) => Box::new(ObjectField::new(key)), - JsonPath::Chain(chain) => Box::new(Chain::from(chain, root, cfg)), + JsonPath::Chain(chain) => Box::new(Chain::from(chain, root)), JsonPath::Wildcard => Box::new(Wildcard {}), JsonPath::Descent(key) => Box::new(DescentObject::new(key)), JsonPath::DescentW => Box::new(DescentWildcard), - JsonPath::Current(value) => Box::new(Current::from(value, root, cfg)), - JsonPath::Index(index) => process_index(index, root, cfg), + JsonPath::Current(value) => Box::new(Current::from(value, root)), + JsonPath::Index(index) => process_index(index, root), JsonPath::Empty => Box::new(IdentityPath {}), JsonPath::Fn(Function::Length) => Box::new(FnPath::Size), } } /// The method processes the indexes(all expressions indie []) -fn process_index<'a>( - json_path_index: &'a JsonPathIndex, - root: &'a Value, - cfg: JsonPathConfig, -) -> PathInstance<'a> { +fn process_index<'a>(json_path_index: &'a JsonPathIndex, root: &'a Value) -> PathInstance<'a> { match json_path_index { JsonPathIndex::Single(index) => Box::new(ArrayIndex::new(index.as_u64().unwrap() as usize)), JsonPathIndex::Slice(s, e, step) => Box::new(ArraySlice::new(*s, *e, *step)), JsonPathIndex::UnionKeys(elems) => Box::new(UnionIndex::from_keys(elems)), JsonPathIndex::UnionIndex(elems) => Box::new(UnionIndex::from_indexes(elems)), - JsonPathIndex::Filter(fe) => Box::new(FilterPath::new(fe, root, cfg)), + JsonPathIndex::Filter(fe) => Box::new(FilterPath::new(fe, root)), } } /// The method processes the operand inside the filter expressions -fn process_operand<'a>(op: &'a Operand, root: &'a Value, cfg: JsonPathConfig) -> PathInstance<'a> { +fn process_operand<'a>(op: &'a Operand, root: &'a Value) -> PathInstance<'a> { match op { - Operand::Static(v) => json_path_instance(&JsonPath::Root, v, cfg), - Operand::Dynamic(jp) => json_path_instance(jp, root, cfg), + Operand::Static(v) => json_path_instance(&JsonPath::Root, v), + Operand::Dynamic(jp) => json_path_instance(jp, root), } } diff --git a/src/path/top.rs b/src/path/top.rs index 2c185e3..1d3fbf9 100644 --- a/src/path/top.rs +++ b/src/path/top.rs @@ -1,5 +1,4 @@ use crate::parser::model::*; -use crate::path::config::JsonPathConfig; use crate::path::{json_path_instance, JsonPathValue, Path, PathInstance}; use crate::JsonPathValue::{NewValue, NoValue, Slice}; use crate::{jsp_idx, jsp_obj, JsPathStr}; @@ -157,7 +156,6 @@ impl<'a> Path<'a> for ObjectField<'a> { vec![res] } } - /// the top method of the processing ..* pub(crate) struct DescentWildcard; @@ -261,7 +259,7 @@ impl<'a> Chain<'a> { is_search_length, } } - pub fn from(chain: &'a [JsonPath], root: &'a Value, cfg: JsonPathConfig) -> Self { + pub fn from(chain: &'a [JsonPath], root: &'a Value) -> Self { let chain_len = chain.len(); let is_search_length = if chain_len > 2 { let mut res = false; @@ -301,10 +299,7 @@ impl<'a> Chain<'a> { }; Chain::new( - chain - .iter() - .map(|p| json_path_instance(p, root, cfg.clone())) - .collect(), + chain.iter().map(|p| json_path_instance(p, root)).collect(), is_search_length, ) } @@ -373,17 +368,17 @@ mod tests { let field4 = path!("array"); let field5 = path!("object"); - let path_inst = json_path_instance(&path!($), &json, Default::default()); + let path_inst = json_path_instance(&path!($), &json); assert_eq!(path_inst.find(jp_v!(&json)), jp_v!(&json;"$",)); - let path_inst = json_path_instance(&field1, &json, Default::default()); + let path_inst = json_path_instance(&field1, &json); let exp_json = json!({"k":{"f":42,"array":[0,1,2,3,4,5],"object":{"field1":"val1","field2":"val2"}}}); assert_eq!(path_inst.find(jp_v!(&json)), jp_v!(&exp_json;".['v']",)); let chain = chain!(path!($), field1.clone(), field2.clone(), field3); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let exp_json = json!(42); assert_eq!( path_inst.find(jp_v!(&json)), @@ -397,7 +392,7 @@ mod tests { field4.clone(), path!(idx!(3)) ); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let exp_json = json!(3); assert_eq!( path_inst.find(jp_v!(&json)), @@ -412,7 +407,7 @@ mod tests { field4.clone(), path!(index) ); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let one = json!(1); let tree = json!(3); assert_eq!( @@ -428,7 +423,7 @@ mod tests { field4, path!(union) ); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let tree = json!(1); let two = json!(2); assert_eq!( @@ -438,7 +433,7 @@ mod tests { let union = idx!("field1", "field2"); let chain = chain!(path!($), field1.clone(), field2, field5, path!(union)); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let one = json!("val1"); let two = json!("val2"); assert_eq!( @@ -448,18 +443,16 @@ mod tests { &two;"$.['v'].['k'].['object'].['field2']") ); } - #[test] fn path_descent_arr_test() { let json = json!([{"a":1}]); let chain = chain!(path!($), path!(.."a")); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let one = json!(1); let expected_res = jp_v!(&one;"$[0].['a']",); assert_eq!(path_inst.find(jp_v!(&json)), expected_res) } - #[test] fn deep_path_test() { let value = json!([1]); @@ -474,7 +467,7 @@ mod tests { "key1": [1] }); let chain = chain!(path!($), path!(..*)); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let arr = json!([1]); let one = json!(1); @@ -482,7 +475,6 @@ mod tests { let expected_res = jp_v!(&arr;"$.['key1']",&one;"$.['key1'][0]"); assert_eq!(path_inst.find(jp_v!(&json)), expected_res) } - #[test] fn path_descent_w_nested_array_test() { let json = json!( @@ -490,7 +482,7 @@ mod tests { "key2" : [{"a":1},{}] }); let chain = chain!(path!($), path!(..*)); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let arr2 = json!([{"a": 1},{}]); let obj = json!({"a": 1}); @@ -523,7 +515,7 @@ mod tests { } }); let chain = chain!(path!($), path!(..*)); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let key1 = json!([1]); let one = json!(1); @@ -560,7 +552,6 @@ mod tests { ]; assert_eq!(path_inst.find(jp_v!(&json)), expected_res) } - #[test] fn path_descent_test() { let json = json!( @@ -577,7 +568,7 @@ mod tests { } }); let chain = chain!(path!($), path!(.."key1")); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let res1 = json!([1, 2, 3]); let res2 = json!("key1"); @@ -602,7 +593,7 @@ mod tests { }); let chain = chain!(path!($), path!(*)); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); let res1 = json!([1, 2, 3]); let res2 = json!("key"); @@ -621,7 +612,7 @@ mod tests { }); let chain = chain!(path!($), path!(*), function!(length)); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); assert_eq!( path_inst.flat_find(vec![jp_v!(&json)], true), @@ -629,7 +620,7 @@ mod tests { ); let chain = chain!(path!($), path!("key1"), function!(length)); - let path_inst = json_path_instance(&chain, &json, Default::default()); + let path_inst = json_path_instance(&chain, &json); assert_eq!( path_inst.flat_find(vec![jp_v!(&json)], false), vec![jp_v!(json!(3))]