From 39ca71a3398bef321153f3a6ac8a38661e047d8b Mon Sep 17 00:00:00 2001 From: Boris Zhguchev Date: Sun, 4 Feb 2024 14:01:24 +0100 Subject: [PATCH 1/5] add rgex bench --- Cargo.toml | 5 +++++ benches/regex_bench.rs | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 benches/regex_bench.rs diff --git a/Cargo.toml b/Cargo.toml index 832e1ed..702fafc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,8 @@ thiserror = "1.0.50" [dev-dependencies] lazy_static = "1.0" +criterion = "0.5.1" + +[[bench]] +name = "regex_bench" +harness = false \ No newline at end of file diff --git a/benches/regex_bench.rs b/benches/regex_bench.rs new file mode 100644 index 0000000..9cabcdf --- /dev/null +++ b/benches/regex_bench.rs @@ -0,0 +1,20 @@ +use std::str::FromStr; +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use serde_json::{json, Value}; +use jsonpath_rust::{JsonPathFinder, JsonPathInst, JsonPathQuery}; + +fn regex_speed_test() { + 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) { + c.bench_function("regex bench", |b| b.iter(|| regex_speed_test())); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); \ No newline at end of file From d28f506c584e064c4025e90a43d846da567be481 Mon Sep 17 00:00:00 2001 From: Boris Zhguchev Date: Sun, 4 Feb 2024 15:55:58 +0100 Subject: [PATCH 2/5] fix inter --- Cargo.toml | 2 +- benches/regex_bench.rs | 17 +++++-- src/lib.rs | 101 +++++++++++++++++++++++++++++++++++++---- src/path/index.rs | 36 +++++++++------ src/path/json.rs | 42 +++++++++++------ src/path/mod.rs | 14 ++++-- src/path/top.rs | 2 +- 7 files changed, 165 insertions(+), 49 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 702fafc..99f830a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,9 @@ regex = "1" pest = "2.0" pest_derive = "2.0" thiserror = "1.0.50" +lazy_static = "1.4" [dev-dependencies] -lazy_static = "1.0" criterion = "0.5.1" [[bench]] diff --git a/benches/regex_bench.rs b/benches/regex_bench.rs index 9cabcdf..d37d280 100644 --- a/benches/regex_bench.rs +++ b/benches/regex_bench.rs @@ -1,9 +1,19 @@ use std::str::FromStr; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use serde_json::{json, Value}; -use jsonpath_rust::{JsonPathFinder, JsonPathInst, JsonPathQuery}; +use jsonpath_rust::{cache_off, JsonPathFinder, JsonPathInst, JsonPathQuery}; -fn regex_speed_test() { +fn regex_speed_test_after() { + let json = Box::new(json!({ + "author":"abcd(Rees)", + })); + + let _v = json.path("$.[?(@.author ~= '.*(?i)d\\(Rees\\)')]") + .expect("the path is correct"); +} + +fn regex_speed_test_before() { + cache_off(); let json = Box::new(json!({ "author":"abcd(Rees)", })); @@ -13,7 +23,8 @@ fn regex_speed_test() { } pub fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("regex bench", |b| b.iter(|| regex_speed_test())); + c.bench_function("regex bench after", |b| b.iter(|| regex_speed_test_after())); + c.bench_function("regex bench before", |b| b.iter(|| regex_speed_test_before())); } criterion_group!(benches, criterion_benchmark); diff --git a/src/lib.rs b/src/lib.rs index aba2969..bddb554 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,14 +114,22 @@ #![allow(clippy::vec_init_then_push)] +use std::cell::{Cell, RefCell}; +use std::collections::HashMap; use crate::parser::model::JsonPath; use crate::parser::parser::parse_json_path; use crate::path::{json_path_instance, PathInstance}; use serde_json::Value; use std::convert::TryInto; use std::fmt::Debug; +use std::mem; use std::ops::Deref; use std::str::FromStr; +use std::sync::{Arc, Mutex, RwLock}; +use std::sync::atomic::{AtomicBool, Ordering}; +use lazy_static::lazy_static; + +use regex::{Error, Regex}; use JsonPathValue::{NewValue, NoValue, Slice}; pub mod parser; @@ -166,11 +174,75 @@ pub trait JsonPathQuery { fn path(self, query: &str) -> Result; } +#[derive(Debug, Clone)] +pub struct JsonPathConfig { + pub with_regex_cache: bool, + pub regex_cache: JsPathRegexCache, +} +lazy_static!( + static ref CACHE:JsPathRegexCache = JsPathRegexCache::new(); + static ref cache_on:Arc = Arc::new(AtomicBool::new(true)); +); +pub fn cache_off() { + cache_on.store(false, Ordering::Relaxed); +} + +impl Default for JsonPathConfig { + fn default() -> Self { + JsonPathConfig { + with_regex_cache: cache_on.clone().load(Ordering::Relaxed), + regex_cache: CACHE.clone(), + } + } +} + + +#[derive(Default, Debug, Clone)] +struct JsPathRegexCache { + cache: Arc>>, +} + + +impl JsPathRegexCache { + pub fn new() -> Self { + JsPathRegexCache { + cache: Arc::new(Mutex::new(Default::default())), + } + } + pub fn contains(&self, str: &String) -> bool { + self + .cache + .lock() + .unwrap() + .contains_key(str) + } + pub fn matched(&self, str: &String, left: Vec<&Value>) -> bool { + let guard = self.cache.lock().unwrap(); + let r = guard.get(str).unwrap(); + for el in left.iter() { + if let Some(v) = el.as_str() { + if r.is_match(v) { + return true; + } + } + } + false + } + pub fn set(&self, str: &String, regex: Regex) { + self + .cache + .lock() + .unwrap() + .insert(str.to_owned(), regex); + } +} + #[derive(Clone)] pub struct JsonPathInst { inner: JsonPath, } + impl FromStr for JsonPathInst { type Err = String; @@ -294,6 +366,7 @@ 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) } @@ -347,8 +420,8 @@ impl<'a, Data> JsonPathValue<'a, Data> { } fn map_slice(self, mapper: F) -> Vec> - where - F: FnOnce(&'a Data, JsPathStr) -> Vec<(&'a Data, JsPathStr)>, + where + F: FnOnce(&'a Data, JsPathStr) -> Vec<(&'a Data, JsPathStr)>, { match self { Slice(r, pref) => mapper(r, pref) @@ -362,8 +435,8 @@ impl<'a, Data> JsonPathValue<'a, Data> { } fn flat_map_slice(self, mapper: F) -> Vec> - where - F: FnOnce(&'a Data, JsPathStr) -> Vec>, + where + F: FnOnce(&'a Data, JsPathStr) -> Vec>, { match self { Slice(r, pref) => mapper(r, pref), @@ -407,12 +480,22 @@ impl<'a, Data> JsonPathValue<'a, Data> { pub struct JsonPathFinder { json: Box, path: Box, + cfg: JsonPathConfig, } impl JsonPathFinder { /// creates a new instance of [JsonPathFinder] pub fn new(json: Box, path: Box) -> Self { - JsonPathFinder { json, path } + 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 @@ -1257,7 +1340,7 @@ mod tests { v, vec![Slice( &json!({"second":{"active": 1}}), - "$.['first']".to_string() + "$.['first']".to_string(), )] ); @@ -1271,7 +1354,7 @@ mod tests { v, vec![Slice( &json!({"second":{"active": 1}}), - "$.['first']".to_string() + "$.['first']".to_string(), )] ); @@ -1285,7 +1368,7 @@ mod tests { v, vec![Slice( &json!({"second":{"active": 1}}), - "$.['first']".to_string() + "$.['first']".to_string(), )] ); @@ -1299,7 +1382,7 @@ mod tests { v, vec![Slice( &json!({"second":{"active": 1}}), - "$.['first']".to_string() + "$.['first']".to_string(), )] ); } diff --git a/src/path/index.rs b/src/path/index.rs index cabf9d7..2cc91b0 100644 --- a/src/path/index.rs +++ b/src/path/index.rs @@ -1,4 +1,4 @@ -use crate::jsp_idx; +use crate::{JsonPathConfig, jsp_idx}; use crate::parser::model::{FilterExpression, FilterSign, JsonPath}; use crate::path::json::*; use crate::path::top::ObjectField; @@ -196,6 +196,7 @@ pub enum FilterPath<'a> { left: PathInstance<'a>, right: PathInstance<'a>, op: &'a FilterSign, + cfg: JsonPathConfig, }, Or { left: PathInstance<'a>, @@ -211,23 +212,24 @@ pub enum FilterPath<'a> { } impl<'a> FilterPath<'a> { - pub(crate) fn new(expr: &'a FilterExpression, root: &'a Value) -> Self { + pub(crate) fn new(expr: &'a FilterExpression, root: &'a Value, cfg: JsonPathConfig) -> Self { match expr { FilterExpression::Atom(left, op, right) => FilterPath::Filter { 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)), - right: Box::new(FilterPath::new(r, root)), + left: Box::new(FilterPath::new(l, root, cfg.clone())), + right: Box::new(FilterPath::new(r, root, cfg.clone())), }, FilterExpression::Or(l, r) => FilterPath::Or { - left: Box::new(FilterPath::new(l, root)), - right: Box::new(FilterPath::new(r, root)), + left: Box::new(FilterPath::new(l, root, cfg.clone())), + right: Box::new(FilterPath::new(r, root, cfg.clone())), }, FilterExpression::Not(exp) => FilterPath::Not { - exp: Box::new(FilterPath::new(exp, root)), + exp: Box::new(FilterPath::new(exp, root, cfg)), }, } } @@ -236,45 +238,48 @@ impl<'a> FilterPath<'a> { two: &'a FilterSign, left: Vec>, right: Vec>, + cfg:JsonPathConfig ) -> bool { - FilterPath::process_atom(one, left.clone(), right.clone()) - || FilterPath::process_atom(two, left, right) + FilterPath::process_atom(one, left.clone(), right.clone(),cfg.clone()) + || FilterPath::process_atom(two, left, right,cfg) } 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), + FilterSign::Unequal => !FilterPath::process_atom(&FilterSign::Equal, left, right,cfg), FilterSign::Less => less( JsonPathValue::vec_as_data(left), JsonPathValue::vec_as_data(right), ), FilterSign::LeOrEq => { - FilterPath::compound(&FilterSign::Less, &FilterSign::Equal, left, right) + FilterPath::compound(&FilterSign::Less, &FilterSign::Equal, left, right,cfg) } FilterSign::Greater => less( JsonPathValue::vec_as_data(right), JsonPathValue::vec_as_data(left), ), FilterSign::GrOrEq => { - FilterPath::compound(&FilterSign::Greater, &FilterSign::Equal, left, right) + FilterPath::compound(&FilterSign::Greater, &FilterSign::Equal, left, right,cfg) } FilterSign::Regex => regex( JsonPathValue::vec_as_data(left), JsonPathValue::vec_as_data(right), + cfg ), 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::Nin => !FilterPath::process_atom(&FilterSign::In, left, right,cfg), + FilterSign::NoneOf => !FilterPath::process_atom(&FilterSign::AnyOf, left, right,cfg), FilterSign::AnyOf => any_of( JsonPathValue::vec_as_data(left), JsonPathValue::vec_as_data(right), @@ -294,10 +299,11 @@ impl<'a> FilterPath<'a> { fn process(&self, curr_el: &'a Value) -> bool { let pref = String::new(); match self { - FilterPath::Filter { left, right, op } => FilterPath::process_atom( + FilterPath::Filter { left, right, op, cfg } => 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() { diff --git a/src/path/json.rs b/src/path/json.rs index 83d9a7e..275caf9 100644 --- a/src/path/json.rs +++ b/src/path/json.rs @@ -1,5 +1,6 @@ use regex::Regex; use serde_json::Value; +use crate::JsonPathConfig; /// 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 @@ -92,24 +93,34 @@ pub fn any_of(left: Vec<&Value>, right: Vec<&Value>) -> bool { 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 { +/// ensure that the element on the left sides matches the regex on the right side +pub fn regex(left: Vec<&Value>, right: Vec<&Value>, cfg: JsonPathConfig) -> 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; + if cfg.with_regex_cache { + if cfg.regex_cache.contains(str) { + return cfg.regex_cache.matched(str, left) + } else { + cfg.regex_cache.set(str, Regex::new(str).unwrap()); + return regex(left, right, cfg); + } + } else { + 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 } _ => false, } @@ -172,6 +183,7 @@ pub fn eq(left: Vec<&Value>, right: Vec<&Value>) -> bool { mod tests { use crate::path::json::{any_of, eq, less, regex, size, sub_set_of}; use serde_json::{json, Value}; + use crate::JsonPathConfig; #[test] fn value_eq_test() { @@ -202,12 +214,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], )); } @@ -243,8 +255,8 @@ mod tests { 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])) + assert!(regex(vec![&left1, &left2, &left3, &left4], vec![&right], JsonPathConfig::default())); + assert!(!regex(vec![&left1, &left3, &left4], vec![&right], JsonPathConfig::default())) } #[test] @@ -272,13 +284,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 fbe5e92..6455731 100644 --- a/src/path/mod.rs +++ b/src/path/mod.rs @@ -1,4 +1,4 @@ -use crate::JsonPathValue; +use crate::{JsonPathConfig, JsonPathFinder, JsonPathValue}; use serde_json::Value; use crate::parser::model::{Function, JsonPath, JsonPathIndex, Operand}; @@ -30,6 +30,10 @@ 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 @@ -37,7 +41,7 @@ pub trait Path<'a> { } /// The basic type for instances. -pub type PathInstance<'a> = Box + '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) -> PathInstance<'a> { @@ -49,20 +53,20 @@ pub fn json_path_instance<'a>(json_path: &'a JsonPath, root: &'a Value) -> PathI JsonPath::Descent(key) => Box::new(DescentObject::new(key)), JsonPath::DescentW => Box::new(DescentWildcard), JsonPath::Current(value) => Box::new(Current::from(value, root)), - JsonPath::Index(index) => process_index(index, root), + JsonPath::Index(index) => process_index(index, root,JsonPathConfig::default()), 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) -> PathInstance<'a> { +fn process_index<'a>(json_path_index: &'a JsonPathIndex, root: &'a Value, cfg: JsonPathConfig) -> 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)), + JsonPathIndex::Filter(fe) => Box::new(FilterPath::new(fe, root, cfg)), } } diff --git a/src/path/top.rs b/src/path/top.rs index 1d3fbf9..35de88d 100644 --- a/src/path/top.rs +++ b/src/path/top.rs @@ -103,7 +103,7 @@ impl<'a> Path<'a> for FnPath { fn flat_find( &self, input: Vec>, - is_search_length: bool, + is_search_length: bool ) -> Vec> { // todo rewrite if JsonPathValue::only_no_value(&input) { From 15e7d03e354cc64941037fc205efeee85ace5edc Mon Sep 17 00:00:00 2001 From: Boris Zhguchev Date: Sun, 18 Feb 2024 15:18:57 +0100 Subject: [PATCH 3/5] init impl --- Cargo.toml | 1 + benches/regex_bench.rs | 20 ++++++--- src/lib.rs | 90 ++++++++----------------------------- src/path/config.rs | 15 +++++++ src/path/config/cache.rs | 97 ++++++++++++++++++++++++++++++++++++++++ src/path/index.rs | 2 +- src/path/json.rs | 22 +++++---- src/path/mod.rs | 4 +- 8 files changed, 158 insertions(+), 93 deletions(-) create mode 100644 src/path/config.rs create mode 100644 src/path/config/cache.rs diff --git a/Cargo.toml b/Cargo.toml index 99f830a..cfc3df6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ pest = "2.0" pest_derive = "2.0" thiserror = "1.0.50" lazy_static = "1.4" +once_cell = "1.19.0" [dev-dependencies] criterion = "0.5.1" diff --git a/benches/regex_bench.rs b/benches/regex_bench.rs index d37d280..0b63ba5 100644 --- a/benches/regex_bench.rs +++ b/benches/regex_bench.rs @@ -1,19 +1,23 @@ use std::str::FromStr; use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use once_cell::sync::Lazy; use serde_json::{json, Value}; -use jsonpath_rust::{cache_off, JsonPathFinder, JsonPathInst, JsonPathQuery}; +use jsonpath_rust::{CONFIG, JsonPathFinder, JsonPathInst, JsonPathQuery}; +use jsonpath_rust::path::config::cache::{DefaultRegexCacheInst, RegexCache}; +use jsonpath_rust::path::config::JsonPathConfig; -fn regex_speed_test_after() { + + +fn regex_perf_test_after() { let json = Box::new(json!({ "author":"abcd(Rees)", })); - let _v = json.path("$.[?(@.author ~= '.*(?i)d\\(Rees\\)')]") + let _v = (json, CONFIG.clone()).path("$.[?(@.author ~= '.*(?i)d\\(Rees\\)')]") .expect("the path is correct"); } -fn regex_speed_test_before() { - cache_off(); +fn regex_perf_test_before() { let json = Box::new(json!({ "author":"abcd(Rees)", })); @@ -23,8 +27,10 @@ fn regex_speed_test_before() { } pub fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("regex bench after", |b| b.iter(|| regex_speed_test_after())); - c.bench_function("regex bench before", |b| b.iter(|| regex_speed_test_before())); + c.bench_function("regex bench before", |b| b.iter(|| regex_perf_test_before())); + c.bench_function("regex bench after", |b| { + b.iter(|| regex_perf_test_after()) + }); } criterion_group!(benches, criterion_benchmark); diff --git a/src/lib.rs b/src/lib.rs index bddb554..aa1b9a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,23 +114,19 @@ #![allow(clippy::vec_init_then_push)] -use std::cell::{Cell, RefCell}; -use std::collections::HashMap; use crate::parser::model::JsonPath; use crate::parser::parser::parse_json_path; use crate::path::{json_path_instance, PathInstance}; use serde_json::Value; use std::convert::TryInto; use std::fmt::Debug; -use std::mem; use std::ops::Deref; use std::str::FromStr; -use std::sync::{Arc, Mutex, RwLock}; -use std::sync::atomic::{AtomicBool, Ordering}; use lazy_static::lazy_static; - -use regex::{Error, Regex}; +use once_cell::sync::Lazy; use JsonPathValue::{NewValue, NoValue, Slice}; +use crate::path::config::cache::{DefaultRegexCacheInst, RegexCache}; +use crate::path::config::JsonPathConfig; pub mod parser; pub mod path; @@ -173,76 +169,12 @@ extern crate pest; pub trait JsonPathQuery { fn path(self, query: &str) -> Result; } - -#[derive(Debug, Clone)] -pub struct JsonPathConfig { - pub with_regex_cache: bool, - pub regex_cache: JsPathRegexCache, -} -lazy_static!( - static ref CACHE:JsPathRegexCache = JsPathRegexCache::new(); - static ref cache_on:Arc = Arc::new(AtomicBool::new(true)); -); -pub fn cache_off() { - cache_on.store(false, Ordering::Relaxed); -} - -impl Default for JsonPathConfig { - fn default() -> Self { - JsonPathConfig { - with_regex_cache: cache_on.clone().load(Ordering::Relaxed), - regex_cache: CACHE.clone(), - } - } -} - - -#[derive(Default, Debug, Clone)] -struct JsPathRegexCache { - cache: Arc>>, -} - - -impl JsPathRegexCache { - pub fn new() -> Self { - JsPathRegexCache { - cache: Arc::new(Mutex::new(Default::default())), - } - } - pub fn contains(&self, str: &String) -> bool { - self - .cache - .lock() - .unwrap() - .contains_key(str) - } - pub fn matched(&self, str: &String, left: Vec<&Value>) -> bool { - let guard = self.cache.lock().unwrap(); - let r = guard.get(str).unwrap(); - for el in left.iter() { - if let Some(v) = el.as_str() { - if r.is_match(v) { - return true; - } - } - } - false - } - pub fn set(&self, str: &String, regex: Regex) { - self - .cache - .lock() - .unwrap() - .insert(str.to_owned(), regex); - } -} - +pub static CONFIG: Lazy = Lazy::new(|| JsonPathConfig::new(RegexCache::instance(DefaultRegexCacheInst::default()))); #[derive(Clone)] pub struct JsonPathInst { inner: JsonPath, } - impl FromStr for JsonPathInst { type Err = String; @@ -296,6 +228,13 @@ impl JsonPathQuery for Box { } } +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)?; @@ -303,6 +242,13 @@ impl JsonPathQuery for Value { } } +impl JsonPathQuery for (Value, JsonPathConfig) { + 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()) + } +} + /// just to create a json path value of data /// Example: /// - json_path_value(&json) = `JsonPathValue::Slice(&json)` diff --git a/src/path/config.rs b/src/path/config.rs new file mode 100644 index 0000000..1cb41fb --- /dev/null +++ b/src/path/config.rs @@ -0,0 +1,15 @@ +pub mod cache; + +use crate::path::config::cache::{RegexCache}; + +#[derive(Clone, Default)] +pub struct JsonPathConfig { + 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 new file mode 100644 index 0000000..ca5687a --- /dev/null +++ b/src/path/config/cache.rs @@ -0,0 +1,97 @@ +use std::collections::HashMap; +use std::sync::{Arc, Mutex, PoisonError}; +use regex::{Error, Regex}; +use serde_json::Value; + +#[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) + } +} + +impl Default for RegexCache { + fn default() -> Self { + RegexCache::Absent + } +} + +pub trait RegexCacheInst { + fn validate(&self, regex: &str, values: Vec<&Value>) -> Result; +} + + +#[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 2cc91b0..c4731b2 100644 --- a/src/path/index.rs +++ b/src/path/index.rs @@ -272,7 +272,7 @@ impl<'a> FilterPath<'a> { FilterSign::Regex => regex( JsonPathValue::vec_as_data(left), JsonPathValue::vec_as_data(right), - cfg + &cfg.regex_cache ), FilterSign::In => inside( JsonPathValue::vec_as_data(left), diff --git a/src/path/json.rs b/src/path/json.rs index 275caf9..da563a7 100644 --- a/src/path/json.rs +++ b/src/path/json.rs @@ -1,6 +1,6 @@ use regex::Regex; use serde_json::Value; -use crate::JsonPathConfig; +use crate::path::config::cache::{RegexCache, RegexCacheError, RegexCacheInst}; /// 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 @@ -94,21 +94,18 @@ pub fn any_of(left: Vec<&Value>, right: Vec<&Value>) -> bool { } /// ensure that the element on the left sides matches the regex on the right side -pub fn regex(left: Vec<&Value>, right: Vec<&Value>, cfg: JsonPathConfig) -> bool { +pub fn regex(left: Vec<&Value>, right: Vec<&Value>, cache: &RegexCache) -> bool { if left.is_empty() || right.is_empty() { return false; } - match right.first() { Some(Value::String(str)) => { - if cfg.with_regex_cache { - if cfg.regex_cache.contains(str) { - return cfg.regex_cache.matched(str, left) - } else { - cfg.regex_cache.set(str, Regex::new(str).unwrap()); - return regex(left, right, cfg); - } + 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) { for el in left.iter() { @@ -184,6 +181,7 @@ mod tests { use crate::path::json::{any_of, eq, less, regex, size, sub_set_of}; use serde_json::{json, Value}; use crate::JsonPathConfig; + use crate::path::config::cache::RegexCache; #[test] fn value_eq_test() { @@ -255,8 +253,8 @@ mod tests { let left3 = json!("a#11"); let left4 = json!("#a11"); - assert!(regex(vec![&left1, &left2, &left3, &left4], vec![&right], JsonPathConfig::default())); - assert!(!regex(vec![&left1, &left3, &left4], vec![&right], JsonPathConfig::default())) + assert!(regex(vec![&left1, &left2, &left3, &left4], vec![&right], &RegexCache::default())); + assert!(!regex(vec![&left1, &left3, &left4], vec![&right], &RegexCache::default())) } #[test] diff --git a/src/path/mod.rs b/src/path/mod.rs index 6455731..9aa1bf8 100644 --- a/src/path/mod.rs +++ b/src/path/mod.rs @@ -1,4 +1,4 @@ -use crate::{JsonPathConfig, JsonPathFinder, JsonPathValue}; +use crate::{JsonPathConfig, JsonPathValue}; use serde_json::Value; use crate::parser::model::{Function, JsonPath, JsonPathIndex, Operand}; @@ -11,6 +11,8 @@ mod index; mod json; /// The module is responsible for processing of the [[JsonPath]] elements mod top; +/// The module provides the ability to adjust the behavior of the search +pub mod config; /// The trait defining the behaviour of processing every separated element. /// type Data usually stands for json [[Value]] From e4d5e2586332f1e3c9638ab24014509e24c015bb Mon Sep 17 00:00:00 2001 From: Boris Zhguchev Date: Tue, 20 Feb 2024 00:40:06 +0100 Subject: [PATCH 4/5] add config --- CHANGELOG.md | 4 +++ Cargo.toml | 2 +- README.md | 43 +++++++++++++++++++++++++++ benches/regex_bench.rs | 16 +++++----- src/lib.rs | 18 +++++++---- src/path/config.rs | 2 ++ src/path/config/cache.rs | 18 ++++++++++- src/path/index.rs | 64 ++++++++++++++++++++-------------------- src/path/json.rs | 21 +++++++------ src/path/mod.rs | 14 ++++----- src/path/top.rs | 42 +++++++++++++++----------- 11 files changed, 160 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcc1bdd..d2eec56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,4 +38,8 @@ * **`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 + diff --git a/Cargo.toml b/Cargo.toml index cfc3df6..22854dc 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.4.0" +version = "0.5.0" authors = ["BorisZhguchev "] edition = "2018" license-file = "LICENSE" diff --git a/README.md b/README.md index 8384f80..cc09de2 100644 --- a/README.md +++ b/README.md @@ -389,7 +389,50 @@ 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/regex_bench.rs b/benches/regex_bench.rs index 0b63ba5..78b8083 100644 --- a/benches/regex_bench.rs +++ b/benches/regex_bench.rs @@ -2,22 +2,21 @@ use std::str::FromStr; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use once_cell::sync::Lazy; use serde_json::{json, Value}; -use jsonpath_rust::{CONFIG, JsonPathFinder, JsonPathInst, JsonPathQuery}; +use jsonpath_rust::{JsonPathFinder, JsonPathInst, JsonPathQuery}; use jsonpath_rust::path::config::cache::{DefaultRegexCacheInst, RegexCache}; use jsonpath_rust::path::config::JsonPathConfig; - -fn regex_perf_test_after() { +fn regex_perf_test_with_cache(cfg: JsonPathConfig) { let json = Box::new(json!({ "author":"abcd(Rees)", })); - let _v = (json, CONFIG.clone()).path("$.[?(@.author ~= '.*(?i)d\\(Rees\\)')]") + let _v = (json, cfg).path("$.[?(@.author ~= '.*(?i)d\\(Rees\\)')]") .expect("the path is correct"); } -fn regex_perf_test_before() { +fn regex_perf_test_without_cache() { let json = Box::new(json!({ "author":"abcd(Rees)", })); @@ -27,9 +26,10 @@ fn regex_perf_test_before() { } pub fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("regex bench before", |b| b.iter(|| regex_perf_test_before())); - c.bench_function("regex bench after", |b| { - b.iter(|| regex_perf_test_after()) + 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())) }); } diff --git a/src/lib.rs b/src/lib.rs index aa1b9a3..d57ecf7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -122,7 +122,6 @@ use std::convert::TryInto; use std::fmt::Debug; use std::ops::Deref; use std::str::FromStr; -use lazy_static::lazy_static; use once_cell::sync::Lazy; use JsonPathValue::{NewValue, NoValue, Slice}; use crate::path::config::cache::{DefaultRegexCacheInst, RegexCache}; @@ -169,7 +168,8 @@ extern crate pest; pub trait JsonPathQuery { fn path(self, query: &str) -> Result; } -pub static CONFIG: Lazy = Lazy::new(|| JsonPathConfig::new(RegexCache::instance(DefaultRegexCacheInst::default()))); + + #[derive(Clone)] pub struct JsonPathInst { inner: JsonPath, @@ -186,8 +186,8 @@ impl FromStr for JsonPathInst { } impl JsonPathInst { - pub fn find_slice<'a>(&'a self, value: &'a Value) -> Vec> { - json_path_instance(&self.inner, value) + pub fn find_slice<'a>(&'a self, value: &'a Value, cfg: JsonPathConfig) -> Vec> { + json_path_instance(&self.inner, value, cfg) .find(JsonPathValue::from_root(value)) .into_iter() .filter(|v| v.has_value()) @@ -469,10 +469,15 @@ impl JsonPathFinder { 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) + 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. @@ -529,6 +534,7 @@ mod tests { use serde_json::{json, Value}; use std::ops::Deref; use std::str::FromStr; + use crate::path::config::JsonPathConfig; fn test(json: &str, path: &str, expected: Vec>) { match JsonPathFinder::from_str(json, path) { @@ -1223,7 +1229,7 @@ mod tests { let query = JsonPathInst::from_str("$..book[?(@.author size 10)].title") .expect("the path is correct"); - let results = query.find_slice(&json); + let results = query.find_slice(&json, JsonPathConfig::default()); let v = results.first().expect("to get value"); // V can be implicitly converted to &Value diff --git a/src/path/config.rs b/src/path/config.rs index 1cb41fb..5e6157d 100644 --- a/src/path/config.rs +++ b/src/path/config.rs @@ -2,8 +2,10 @@ 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, } diff --git a/src/path/config/cache.rs b/src/path/config/cache.rs index ca5687a..26ce443 100644 --- a/src/path/config/cache.rs +++ b/src/path/config/cache.rs @@ -3,6 +3,20 @@ use std::sync::{Arc, Mutex, PoisonError}; use regex::{Error, Regex}; use serde_json::Value; +/// 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 @@ -38,11 +52,13 @@ impl Default for RegexCache { } } +/// 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>>, diff --git a/src/path/index.rs b/src/path/index.rs index c4731b2..9db1f1a 100644 --- a/src/path/index.rs +++ b/src/path/index.rs @@ -124,10 +124,10 @@ pub(crate) struct Current<'a> { } impl<'a> Current<'a> { - pub(crate) fn from(jp: &'a JsonPath, root: &'a Value) -> Self { + pub(crate) fn from(jp: &'a JsonPath, root: &'a Value, cfg: JsonPathConfig) -> Self { match jp { JsonPath::Empty => Current::none(), - tail => Current::new(json_path_instance(tail, root)), + tail => Current::new(json_path_instance(tail, root, cfg)), } } pub(crate) fn new(tail: PathInstance<'a>) -> Self { @@ -215,8 +215,8 @@ impl<'a> FilterPath<'a> { pub(crate) fn new(expr: &'a FilterExpression, root: &'a Value, cfg: JsonPathConfig) -> Self { match expr { FilterExpression::Atom(left, op, right) => FilterPath::Filter { - left: process_operand(left, root), - right: process_operand(right, root), + left: process_operand(left, root, cfg.clone()), + right: process_operand(right, root, cfg.clone()), op, cfg, }, @@ -238,48 +238,48 @@ impl<'a> FilterPath<'a> { two: &'a FilterSign, left: Vec>, right: Vec>, - cfg:JsonPathConfig + 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(), cfg.clone()) + || FilterPath::process_atom(two, left, right, cfg) } fn process_atom( op: &'a FilterSign, left: Vec>, right: Vec>, - cfg:JsonPathConfig + 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, cfg), 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, cfg) } 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, cfg) } FilterSign::Regex => regex( JsonPathValue::vec_as_data(left), JsonPathValue::vec_as_data(right), - &cfg.regex_cache + &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, cfg), + FilterSign::NoneOf => !FilterPath::process_atom(&FilterSign::AnyOf, left, right, cfg), FilterSign::AnyOf => any_of( JsonPathValue::vec_as_data(left), JsonPathValue::vec_as_data(right), @@ -303,7 +303,7 @@ impl<'a> FilterPath<'a> { op, left.find(Slice(curr_el, pref.clone())), right.find(Slice(curr_el, pref)), - cfg.clone() + cfg.clone(), ), FilterPath::Or { left, right } => { if !JsonPathValue::vec_as_data(left.find(Slice(curr_el, pref.clone()))).is_empty() { @@ -480,7 +480,7 @@ mod tests { let chain = chain!(path!($), path!("object"), path!(@)); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json, Default::default()); let res = json!({ "field_1":[1,2,3], "field_2":42, @@ -493,7 +493,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); + let path_inst = json_path_instance(&chain, &json, Default::default()); let res1 = json!("b"); let expected_res = vec![JsonPathValue::new_slice( @@ -513,7 +513,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); + let path_inst = json_path_instance(&chain, &json, Default::default()); let exp1 = json!([1, 2, 3, 4, 5]); let exp2 = json!(42); @@ -544,7 +544,7 @@ mod tests { let chain = chain!(path!($), path!("key"), index); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json, Default::default()); let exp1 = json!( {"field":10}); let exp2 = json!( {"field":5}); @@ -563,7 +563,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); + let path_inst = json_path_instance(&chain, &json, Default::default()); let expected_res = jp_v![ &exp1;"$.['key'][1]", &exp3;"$.['key'][2]", &exp2;"$.['key'][3]"]; assert_eq!( @@ -575,7 +575,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); + let path_inst = json_path_instance(&chain, &json, Default::default()); let expected_res = jp_v![&exp4;"$.['key'][0]", &exp4;"$.['key'][4]"]; assert_eq!( path_inst.find(JsonPathValue::from_root(&json)), @@ -586,7 +586,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); + let path_inst = json_path_instance(&chain, &json, Default::default()); let expected_res = jp_v![ &exp4;"$.['key'][0]", &exp3;"$.['key'][2]", @@ -611,7 +611,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); + let path_inst = json_path_instance(&chain, &json, Default::default()); let exp2 = json!( {"field":"a1#1"}); let expected_res = jp_v![&exp2;"$.['key'][1]",]; @@ -640,7 +640,7 @@ mod tests { let chain = chain!(path!($), JsonPath::Field(String::from("key")), path!(index)); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json, Default::default()); let exp2 = json!( {"field":"a11#"}); let expected_res = jp_v![&exp2;"$.['key'][0]",]; @@ -664,7 +664,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); + let path_inst = json_path_instance(&chain, &json, Default::default()); let f1 = json!( {"field":"aaaa"}); let f2 = json!( {"field":"dddd"}); @@ -690,7 +690,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); + let path_inst = json_path_instance(&chain, &json, Default::default()); let js = json!({ "id":1, "not_id": 2, @@ -722,7 +722,7 @@ mod tests { ) ); let chain = chain!(path!($), path!("key"), path!(index), path!("city")); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json, Default::default()); let a = json!("Athlon"); let d = json!("Dortmund"); let dd = json!("Dublin"); @@ -751,7 +751,7 @@ mod tests { ) ); let chain = chain!(path!($), path!("key"), path!(index), path!("id")); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json, Default::default()); let j1 = json!(1); assert_eq!( path_inst.find(JsonPathValue::from_root(&json)), @@ -775,7 +775,7 @@ mod tests { ) ); let chain = chain!(path!($), path!("key"), path!(index), path!("id")); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json, Default::default()); let j1 = json!(1); assert_eq!( path_inst.find(JsonPathValue::from_root(&json)), @@ -803,7 +803,7 @@ mod tests { ) ); let chain = chain!(path!($), path!("key"), path!(index), path!("city")); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json, Default::default()); let a = json!("Athlon"); let value = jp_v!( &a;"$.['key'][4].['city']",); assert_eq!(path_inst.find(JsonPathValue::from_root(&json)), value) @@ -825,7 +825,7 @@ mod tests { ) ); let chain = chain!(path!($), path!("key"), path!(index), path!("id")); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json, Default::default()); let j1 = json!(1); assert_eq!( path_inst.find(JsonPathValue::from_root(&json)), @@ -849,7 +849,7 @@ mod tests { ) ); let chain = chain!(path!($), path!("key"), path!(index), path!("id")); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json, Default::default()); assert_eq!( path_inst.find(JsonPathValue::from_root(&json)), vec![NoValue] diff --git a/src/path/json.rs b/src/path/json.rs index da563a7..20e207d 100644 --- a/src/path/json.rs +++ b/src/path/json.rs @@ -1,6 +1,6 @@ use regex::Regex; use serde_json::Value; -use crate::path::config::cache::{RegexCache, RegexCacheError, RegexCacheInst}; +use crate::path::config::cache::{RegexCache, RegexCacheInst}; /// 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 @@ -100,25 +100,24 @@ pub fn regex(left: Vec<&Value>, right: Vec<&Value>, cache: &RegexCache { + 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) { - for el in left.iter() { - if let Some(v) = el.as_str() { - if regex.is_match(v) { - return true; - } + } else 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 - } - } + } else { + false + }, _ => false, } } diff --git a/src/path/mod.rs b/src/path/mod.rs index 9aa1bf8..506d6f2 100644 --- a/src/path/mod.rs +++ b/src/path/mod.rs @@ -46,16 +46,16 @@ 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) -> PathInstance<'a> { +pub fn json_path_instance<'a>(json_path: &'a JsonPath, root: &'a Value, cfg: JsonPathConfig) -> 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)), + JsonPath::Chain(chain) => Box::new(Chain::from(chain, root, cfg)), 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)), - JsonPath::Index(index) => process_index(index, root,JsonPathConfig::default()), + JsonPath::Current(value) => Box::new(Current::from(value, root, cfg)), + JsonPath::Index(index) => process_index(index, root, cfg), JsonPath::Empty => Box::new(IdentityPath {}), JsonPath::Fn(Function::Length) => Box::new(FnPath::Size), } @@ -73,9 +73,9 @@ fn process_index<'a>(json_path_index: &'a JsonPathIndex, root: &'a Value, cfg: J } /// 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>(op: &'a Operand, root: &'a Value, cfg: JsonPathConfig) -> PathInstance<'a> { match op { - Operand::Static(v) => json_path_instance(&JsonPath::Root, v), - Operand::Dynamic(jp) => json_path_instance(jp, root), + Operand::Static(v) => json_path_instance(&JsonPath::Root, v, cfg), + Operand::Dynamic(jp) => json_path_instance(jp, root, cfg), } } diff --git a/src/path/top.rs b/src/path/top.rs index 35de88d..f57db2f 100644 --- a/src/path/top.rs +++ b/src/path/top.rs @@ -4,6 +4,7 @@ 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 crate::path::config::JsonPathConfig; /// to process the element [*] pub(crate) struct Wildcard {} @@ -103,7 +104,7 @@ impl<'a> Path<'a> for FnPath { fn flat_find( &self, input: Vec>, - is_search_length: bool + is_search_length: bool, ) -> Vec> { // todo rewrite if JsonPathValue::only_no_value(&input) { @@ -156,6 +157,7 @@ impl<'a> Path<'a> for ObjectField<'a> { vec![res] } } + /// the top method of the processing ..* pub(crate) struct DescentWildcard; @@ -259,7 +261,7 @@ impl<'a> Chain<'a> { is_search_length, } } - pub fn from(chain: &'a [JsonPath], root: &'a Value) -> Self { + pub fn from(chain: &'a [JsonPath], root: &'a Value, cfg: JsonPathConfig) -> Self { let chain_len = chain.len(); let is_search_length = if chain_len > 2 { let mut res = false; @@ -299,7 +301,7 @@ impl<'a> Chain<'a> { }; Chain::new( - chain.iter().map(|p| json_path_instance(p, root)).collect(), + chain.iter().map(|p| json_path_instance(p, root, cfg.clone())).collect(), is_search_length, ) } @@ -368,17 +370,17 @@ mod tests { let field4 = path!("array"); let field5 = path!("object"); - let path_inst = json_path_instance(&path!($), &json); + let path_inst = json_path_instance(&path!($), &json,Default::default()); assert_eq!(path_inst.find(jp_v!(&json)), jp_v!(&json;"$",)); - let path_inst = json_path_instance(&field1, &json); + let path_inst = json_path_instance(&field1, &json,Default::default()); 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); + let path_inst = json_path_instance(&chain, &json,Default::default()); let exp_json = json!(42); assert_eq!( path_inst.find(jp_v!(&json)), @@ -392,7 +394,7 @@ mod tests { field4.clone(), path!(idx!(3)) ); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json,Default::default()); let exp_json = json!(3); assert_eq!( path_inst.find(jp_v!(&json)), @@ -407,7 +409,7 @@ mod tests { field4.clone(), path!(index) ); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json,Default::default()); let one = json!(1); let tree = json!(3); assert_eq!( @@ -423,7 +425,7 @@ mod tests { field4, path!(union) ); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json,Default::default()); let tree = json!(1); let two = json!(2); assert_eq!( @@ -433,7 +435,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); + let path_inst = json_path_instance(&chain, &json,Default::default()); let one = json!("val1"); let two = json!("val2"); assert_eq!( @@ -443,16 +445,18 @@ 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); + let path_inst = json_path_instance(&chain, &json,Default::default()); 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]); @@ -467,7 +471,7 @@ mod tests { "key1": [1] }); let chain = chain!(path!($), path!(..*)); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json,Default::default()); let arr = json!([1]); let one = json!(1); @@ -475,6 +479,7 @@ 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!( @@ -482,7 +487,7 @@ mod tests { "key2" : [{"a":1},{}] }); let chain = chain!(path!($), path!(..*)); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json,Default::default()); let arr2 = json!([{"a": 1},{}]); let obj = json!({"a": 1}); @@ -515,7 +520,7 @@ mod tests { } }); let chain = chain!(path!($), path!(..*)); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json,Default::default()); let key1 = json!([1]); let one = json!(1); @@ -552,6 +557,7 @@ mod tests { ]; assert_eq!(path_inst.find(jp_v!(&json)), expected_res) } + #[test] fn path_descent_test() { let json = json!( @@ -568,7 +574,7 @@ mod tests { } }); let chain = chain!(path!($), path!(.."key1")); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json,Default::default()); let res1 = json!([1, 2, 3]); let res2 = json!("key1"); @@ -593,7 +599,7 @@ mod tests { }); let chain = chain!(path!($), path!(*)); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json,Default::default()); let res1 = json!([1, 2, 3]); let res2 = json!("key"); @@ -612,7 +618,7 @@ mod tests { }); let chain = chain!(path!($), path!(*), function!(length)); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json,Default::default()); assert_eq!( path_inst.flat_find(vec![jp_v!(&json)], true), @@ -620,7 +626,7 @@ mod tests { ); let chain = chain!(path!($), path!("key1"), function!(length)); - let path_inst = json_path_instance(&chain, &json); + let path_inst = json_path_instance(&chain, &json,Default::default()); assert_eq!( path_inst.flat_find(vec![jp_v!(&json)], false), vec![jp_v!(json!(3))] From c7a3b60f9c86d6944ed0092746e31b94d96ec18e Mon Sep 17 00:00:00 2001 From: Boris Zhguchev Date: Tue, 20 Feb 2024 00:53:26 +0100 Subject: [PATCH 5/5] fix complains --- benches/regex_bench.rs | 29 ++++++++++++++++------------- src/lib.rs | 29 +++++++++++++++++------------ src/path/config.rs | 3 +-- src/path/config/cache.rs | 18 ++++++++++-------- src/path/index.rs | 9 +++++++-- src/path/json.rs | 28 ++++++++++++++++++++-------- src/path/mod.rs | 18 +++++++++++++----- src/path/top.rs | 37 ++++++++++++++++++++----------------- 8 files changed, 104 insertions(+), 67 deletions(-) diff --git a/benches/regex_bench.rs b/benches/regex_bench.rs index 78b8083..2b88e7f 100644 --- a/benches/regex_bench.rs +++ b/benches/regex_bench.rs @@ -1,37 +1,40 @@ -use std::str::FromStr; use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use once_cell::sync::Lazy; -use serde_json::{json, Value}; -use jsonpath_rust::{JsonPathFinder, JsonPathInst, JsonPathQuery}; 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)", - })); + "author":"abcd(Rees)", + })); - let _v = (json, cfg).path("$.[?(@.author ~= '.*(?i)d\\(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)", - })); + "author":"abcd(Rees)", + })); - let _v = json.path("$.[?(@.author ~= '.*(?i)d\\(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 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); \ No newline at end of file +criterion_main!(benches); diff --git a/src/lib.rs b/src/lib.rs index d57ecf7..215f613 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,16 +116,14 @@ 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 serde_json::Value; use std::convert::TryInto; use std::fmt::Debug; use std::ops::Deref; use std::str::FromStr; -use once_cell::sync::Lazy; use JsonPathValue::{NewValue, NoValue, Slice}; -use crate::path::config::cache::{DefaultRegexCacheInst, RegexCache}; -use crate::path::config::JsonPathConfig; pub mod parser; pub mod path; @@ -169,7 +167,6 @@ pub trait JsonPathQuery { fn path(self, query: &str) -> Result; } - #[derive(Clone)] pub struct JsonPathInst { inner: JsonPath, @@ -186,7 +183,11 @@ impl FromStr for JsonPathInst { } impl JsonPathInst { - pub fn find_slice<'a>(&'a self, value: &'a Value, cfg: JsonPathConfig) -> Vec> { + pub fn find_slice<'a>( + &'a self, + value: &'a Value, + cfg: JsonPathConfig, + ) -> Vec> { json_path_instance(&self.inner, value, cfg) .find(JsonPathValue::from_root(value)) .into_iter() @@ -356,7 +357,7 @@ impl<'a, Data: Clone + Debug + Default> JsonPathValue<'a, Data> { } impl<'a, Data> JsonPathValue<'a, Data> { - fn only_no_value(input: &Vec>) -> bool { + 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> { @@ -366,8 +367,8 @@ impl<'a, Data> JsonPathValue<'a, Data> { } fn map_slice(self, mapper: F) -> Vec> - where - F: FnOnce(&'a Data, JsPathStr) -> Vec<(&'a Data, JsPathStr)>, + where + F: FnOnce(&'a Data, JsPathStr) -> Vec<(&'a Data, JsPathStr)>, { match self { Slice(r, pref) => mapper(r, pref) @@ -381,8 +382,8 @@ impl<'a, Data> JsonPathValue<'a, Data> { } fn flat_map_slice(self, mapper: F) -> Vec> - where - F: FnOnce(&'a Data, JsPathStr) -> Vec>, + where + F: FnOnce(&'a Data, JsPathStr) -> Vec>, { match self { Slice(r, pref) => mapper(r, pref), @@ -432,7 +433,11 @@ pub struct JsonPathFinder { impl JsonPathFinder { /// creates a new instance of [JsonPathFinder] pub fn new(json: Box, path: Box) -> Self { - JsonPathFinder { json, path, cfg: JsonPathConfig::default() } + JsonPathFinder { + json, + path, + cfg: JsonPathConfig::default(), + } } pub fn new_with_cfg(json: Box, path: Box, cfg: JsonPathConfig) -> Self { @@ -528,13 +533,13 @@ impl JsonPathFinder { #[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 serde_json::{json, Value}; use std::ops::Deref; use std::str::FromStr; - use crate::path::config::JsonPathConfig; fn test(json: &str, path: &str, expected: Vec>) { match JsonPathFinder::from_str(json, path) { diff --git a/src/path/config.rs b/src/path/config.rs index 5e6157d..b534712 100644 --- a/src/path/config.rs +++ b/src/path/config.rs @@ -1,6 +1,6 @@ pub mod cache; -use crate::path::config::cache::{RegexCache}; +use crate::path::config::cache::RegexCache; /// Configuration to adjust the jsonpath search #[derive(Clone, Default)] @@ -14,4 +14,3 @@ impl JsonPathConfig { Self { regex_cache } } } - diff --git a/src/path/config/cache.rs b/src/path/config/cache.rs index 26ce443..ebe7e23 100644 --- a/src/path/config/cache.rs +++ b/src/path/config/cache.rs @@ -1,7 +1,7 @@ -use std::collections::HashMap; -use std::sync::{Arc, Mutex, PoisonError}; 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 /// ``` @@ -19,25 +19,27 @@ use serde_json::Value; /// .expect("the path is correct"); #[derive(Clone)] pub enum RegexCache - where T: Clone + RegexCacheInst +where + T: Clone + RegexCacheInst, { Absent, Implemented(T), } impl RegexCache - where T: Clone + RegexCacheInst +where + T: Clone + RegexCacheInst, { pub fn is_implemented(&self) -> bool { match self { RegexCache::Absent => false, - RegexCache::Implemented(_) => true + 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) + RegexCache::Implemented(inst) => Ok(inst), } } @@ -45,7 +47,7 @@ impl RegexCache RegexCache::Implemented(instance) } } - +#[allow(clippy::derivable_impls)] impl Default for RegexCache { fn default() -> Self { RegexCache::Absent @@ -69,7 +71,7 @@ impl RegexCacheInst for DefaultRegexCacheInst { let mut cache = self.cache.lock()?; if cache.contains_key(regex) { let r = cache.get(regex).unwrap(); - Ok(validate(r, values)) + Ok(validate(r, values)) } else { let new_reg = Regex::new(regex)?; let result = validate(&new_reg, values); diff --git a/src/path/index.rs b/src/path/index.rs index 9db1f1a..cc018f0 100644 --- a/src/path/index.rs +++ b/src/path/index.rs @@ -1,9 +1,9 @@ -use crate::{JsonPathConfig, 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; @@ -299,7 +299,12 @@ 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, + cfg, + } => FilterPath::process_atom( op, left.find(Slice(curr_el, pref.clone())), right.find(Slice(curr_el, pref)), diff --git a/src/path/json.rs b/src/path/json.rs index 20e207d..c29da5d 100644 --- a/src/path/json.rs +++ b/src/path/json.rs @@ -1,6 +1,6 @@ +use crate::path::config::cache::{RegexCache, RegexCacheInst}; use regex::Regex; use serde_json::Value; -use crate::path::config::cache::{RegexCache, RegexCacheInst}; /// 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 @@ -94,13 +94,17 @@ pub fn any_of(left: Vec<&Value>, right: Vec<&Value>) -> bool { } /// 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 { +pub fn regex( + left: Vec<&Value>, + right: Vec<&Value>, + cache: &RegexCache, +) -> bool { if left.is_empty() || right.is_empty() { return false; } match right.first() { - Some(Value::String(str)) => + Some(Value::String(str)) => { if cache.is_implemented() { cache .get_instance() @@ -117,7 +121,8 @@ pub fn regex(left: Vec<&Value>, right: Vec<&Value>, cache: &RegexCache false, } } @@ -177,10 +182,9 @@ 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}; - use crate::JsonPathConfig; - use crate::path::config::cache::RegexCache; #[test] fn value_eq_test() { @@ -252,8 +256,16 @@ 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], + &RegexCache::default() + )); + assert!(!regex( + vec![&left1, &left3, &left4], + vec![&right], + &RegexCache::default() + )) } #[test] diff --git a/src/path/mod.rs b/src/path/mod.rs index 506d6f2..aeda6b9 100644 --- a/src/path/mod.rs +++ b/src/path/mod.rs @@ -5,14 +5,14 @@ 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 mod json; /// The module is responsible for processing of the [[JsonPath]] elements mod top; -/// The module provides the ability to adjust the behavior of the search -pub mod config; /// The trait defining the behaviour of processing every separated element. /// type Data usually stands for json [[Value]] @@ -43,10 +43,14 @@ pub trait Path<'a> { } /// The basic type for instances. -pub type PathInstance<'a> = Box + '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, + cfg: JsonPathConfig, +) -> PathInstance<'a> { match json_path { JsonPath::Root => Box::new(RootPointer::new(root)), JsonPath::Field(key) => Box::new(ObjectField::new(key)), @@ -62,7 +66,11 @@ pub fn json_path_instance<'a>(json_path: &'a JsonPath, root: &'a Value, cfg: Jso } /// 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, + cfg: JsonPathConfig, +) -> 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)), diff --git a/src/path/top.rs b/src/path/top.rs index f57db2f..2c185e3 100644 --- a/src/path/top.rs +++ b/src/path/top.rs @@ -1,10 +1,10 @@ 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}; use serde_json::value::Value::{Array, Object}; use serde_json::{json, Value}; -use crate::path::config::JsonPathConfig; /// to process the element [*] pub(crate) struct Wildcard {} @@ -301,7 +301,10 @@ 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, cfg.clone())) + .collect(), is_search_length, ) } @@ -370,17 +373,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, Default::default()); 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, Default::default()); 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, Default::default()); let exp_json = json!(42); assert_eq!( path_inst.find(jp_v!(&json)), @@ -394,7 +397,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, Default::default()); let exp_json = json!(3); assert_eq!( path_inst.find(jp_v!(&json)), @@ -409,7 +412,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, Default::default()); let one = json!(1); let tree = json!(3); assert_eq!( @@ -425,7 +428,7 @@ mod tests { field4, path!(union) ); - let path_inst = json_path_instance(&chain, &json,Default::default()); + let path_inst = json_path_instance(&chain, &json, Default::default()); let tree = json!(1); let two = json!(2); assert_eq!( @@ -435,7 +438,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, Default::default()); let one = json!("val1"); let two = json!("val2"); assert_eq!( @@ -450,7 +453,7 @@ mod tests { 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, Default::default()); let one = json!(1); let expected_res = jp_v!(&one;"$[0].['a']",); @@ -471,7 +474,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, Default::default()); let arr = json!([1]); let one = json!(1); @@ -487,7 +490,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, Default::default()); let arr2 = json!([{"a": 1},{}]); let obj = json!({"a": 1}); @@ -520,7 +523,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, Default::default()); let key1 = json!([1]); let one = json!(1); @@ -574,7 +577,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, Default::default()); let res1 = json!([1, 2, 3]); let res2 = json!("key1"); @@ -599,7 +602,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, Default::default()); let res1 = json!([1, 2, 3]); let res2 = json!("key"); @@ -618,7 +621,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, Default::default()); assert_eq!( path_inst.flat_find(vec![jp_v!(&json)], true), @@ -626,7 +629,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, Default::default()); assert_eq!( path_inst.flat_find(vec![jp_v!(&json)], false), vec![jp_v!(json!(3))]