From 2d194b40c50e4cfd7965974a9860240599d7b1bd Mon Sep 17 00:00:00 2001 From: meir Date: Tue, 3 May 2022 18:18:27 +0300 Subject: [PATCH 1/2] Added jsonpath functionalities 1. Pop last token from json path 2. Check if the json path is static path (lead to single result) 3. Check the amount of tokens on the json path --- src/json_path.rs | 147 ++++++++++++++++++++++++++++++++++++++++++++--- src/lib.rs | 12 ++-- 2 files changed, 146 insertions(+), 13 deletions(-) diff --git a/src/json_path.rs b/src/json_path.rs index fd07b22..cde10a1 100644 --- a/src/json_path.rs +++ b/src/json_path.rs @@ -9,10 +9,18 @@ use std::fmt::Debug; #[grammar = "grammer.pest"] pub struct JsonPathParser; +#[derive(Debug, PartialEq)] +pub enum JsonPathToken { + String, + Number, +} + #[derive(Debug)] pub struct Query<'i> { // query: QueryElement<'i> - pub query: Pairs<'i, Rule>, + pub root: Pairs<'i, Rule>, + is_static: Option, + size: Option, } #[derive(Debug)] @@ -21,6 +29,67 @@ pub struct QueryCompilationError { message: String, } +impl<'i> Query<'i> { + pub fn pop_last(&mut self) -> Option<(String, JsonPathToken)> { + let last = self.root.next_back(); + match last { + Some(last) => match last.as_rule() { + Rule::literal => Some((last.as_str().to_string(), JsonPathToken::String)), + Rule::number => Some((last.as_str().to_string(), JsonPathToken::Number)), + Rule::numbers_list => { + let first_on_list = last.into_inner().next(); + match first_on_list { + Some(first) => Some((first.as_str().to_string(), JsonPathToken::Number)), + None => None, + } + } + Rule::string_list => { + let first_on_list = last.into_inner().next(); + match first_on_list { + Some(first) => Some((first.as_str().to_string(), JsonPathToken::String)), + None => None, + } + } + _ => panic!("pop last was used in a none static path"), + }, + None => None, + } + } + + pub fn size(&mut self) -> usize { + if self.size.is_some() { + return *self.size.as_ref().unwrap(); + } + self.is_static(); + self.size() + } + + pub fn is_static(&mut self) -> bool { + if self.is_static.is_some() { + return *self.is_static.as_ref().unwrap(); + } + let mut size = 0; + let mut is_static = true; + let mut root_copy = self.root.clone(); + while let Some(n) = root_copy.next() { + size = size + 1; + match n.as_rule() { + Rule::literal | Rule::number => continue, + Rule::numbers_list | Rule::string_list => { + let inner = n.into_inner(); + if inner.count() > 1 { + is_static = false; + } + } + _ => is_static = false, + } + } + self.size = Some(size); + self.is_static = Some(is_static); + self.is_static() + } +} + impl std::fmt::Display for QueryCompilationError { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { write!( @@ -50,7 +119,14 @@ impl std::fmt::Display for Rule { pub(crate) fn compile(path: &str) -> Result { let query = JsonPathParser::parse(Rule::query, path); match query { - Ok(q) => Ok(Query { query: q }), + Ok(mut q) => { + let root = q.next().unwrap(); + Ok(Query { + root: root.into_inner(), + is_static: None, + size: None, + }) + } // pest::error::Error Err(e) => { let pos = match e.location { @@ -199,7 +275,10 @@ const fn create_empty_trucker<'i, 'j>() -> PathTracker<'i, 'j> { } } -const fn create_str_trucker<'i, 'j>(s: &'i str, father: &'j PathTracker<'i, 'j>) -> PathTracker<'i, 'j> { +const fn create_str_trucker<'i, 'j>( + s: &'i str, + father: &'j PathTracker<'i, 'j>, +) -> PathTracker<'i, 'j> { PathTracker { father: Some(father), element: PathTrackerElement::Key(s), @@ -883,7 +962,7 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> { pub fn calc_with_paths_on_root<'j: 'i, S: SelectValue>( &self, json: &'j S, - root: Pair, + root: Pairs<'i, Rule>, ) -> Vec> { let mut calc_data = PathCalculatorData { results: Vec::new(), @@ -891,14 +970,14 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> { }; if self.tracker_generator.is_some() { self.calc_internal( - root.into_inner(), + root, json, Some(create_empty_trucker()), &mut calc_data, true, ); } else { - self.calc_internal(root.into_inner(), json, None, &mut calc_data, true); + self.calc_internal(root, json, None, &mut calc_data, true); } calc_data.results.drain(..).collect() } @@ -907,7 +986,7 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> { &self, json: &'j S, ) -> Vec> { - self.calc_with_paths_on_root(json, self.query.unwrap().query.clone().next().unwrap()) + self.calc_with_paths_on_root(json, self.query.unwrap().root.clone()) } pub fn calc<'j: 'i, S: SelectValue>(&self, json: &'j S) -> Vec<&'j S> { @@ -924,3 +1003,57 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> { .collect() } } + +#[cfg(test)] +mod json_path_compiler_tests { + use crate::json_path::compile; + use crate::json_path::JsonPathToken; + + #[test] + fn test_compiler_pop_last() { + let query = compile("$.foo"); + assert_eq!( + query.unwrap().pop_last().unwrap(), + ("foo".to_string(), JsonPathToken::String) + ); + } + + #[test] + fn test_compiler_pop_last_number() { + let query = compile("$.[1]"); + assert_eq!( + query.unwrap().pop_last().unwrap(), + ("1".to_string(), JsonPathToken::Number) + ); + } + + #[test] + fn test_compiler_pop_last_string_brucket_notation() { + let query = compile("$.[\"foo\"]"); + assert_eq!( + query.unwrap().pop_last().unwrap(), + ("foo".to_string(), JsonPathToken::String) + ); + } + + #[test] + fn test_compiler_is_static() { + let query = compile("$.[\"foo\"]"); + assert!(query.unwrap().is_static()); + + let query = compile("$.[\"foo\", \"bar\"]"); + assert!(!query.unwrap().is_static()); + } + + #[test] + fn test_compiler_size() { + let query = compile("$.[\"foo\"]"); + assert_eq!(query.unwrap().size(), 1); + + let query = compile("$.[\"foo\"].bar"); + assert_eq!(query.unwrap().size(), 2); + + let query = compile("$.[\"foo\"].bar[1]"); + assert_eq!(query.unwrap().size(), 3); + } +} diff --git a/src/lib.rs b/src/lib.rs index 253ddfb..66ec7af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,8 +63,8 @@ pub fn compile(s: &str) -> Result { /// The query ownership is taken so it can not be used after. This allows /// the get a better performance if there is a need to calculate the query /// only once. -pub fn calc_once<'j, 'p, S: SelectValue>(mut q: Query<'j>, json: &'p S) -> Vec<&'p S> { - let root = q.query.next().unwrap(); +pub fn calc_once<'j, 'p, S: SelectValue>(q: Query<'j>, json: &'p S) -> Vec<&'p S> { + let root = q.root; PathCalculator::<'p, DummyTrackerGenerator> { query: None, tracker_generator: None, @@ -77,10 +77,10 @@ pub fn calc_once<'j, 'p, S: SelectValue>(mut q: Query<'j>, json: &'p S) -> Vec<& /// A version of `calc_once` that returns also paths. pub fn calc_once_with_paths<'j, 'p, S: SelectValue>( - mut q: Query<'j>, + q: Query<'j>, json: &'p S, ) -> Vec> { - let root = q.query.next().unwrap(); + let root = q.root; PathCalculator { query: None, tracker_generator: Some(PTrackerGenerator), @@ -89,8 +89,8 @@ pub fn calc_once_with_paths<'j, 'p, S: SelectValue>( } /// A version of `calc_once` that returns only paths as Vec>. -pub fn calc_once_paths(mut q: Query, json: &S) -> Vec> { - let root = q.query.next().unwrap(); +pub fn calc_once_paths(q: Query, json: &S) -> Vec> { + let root = q.root; PathCalculator { query: None, tracker_generator: Some(PTrackerGenerator), From a959db77bebb08a94168bcb7546ec84fb61032b2 Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Wed, 4 May 2022 15:54:21 +0300 Subject: [PATCH 2/2] fix typos and warnings --- src/json_path.rs | 56 +++++++++++++++++++++--------------------------- src/lib.rs | 8 +++---- 2 files changed, 29 insertions(+), 35 deletions(-) diff --git a/src/json_path.rs b/src/json_path.rs index cde10a1..42d20f3 100644 --- a/src/json_path.rs +++ b/src/json_path.rs @@ -38,17 +38,11 @@ impl<'i> Query<'i> { Rule::number => Some((last.as_str().to_string(), JsonPathToken::Number)), Rule::numbers_list => { let first_on_list = last.into_inner().next(); - match first_on_list { - Some(first) => Some((first.as_str().to_string(), JsonPathToken::Number)), - None => None, - } + first_on_list.map(|first| (first.as_str().to_string(), JsonPathToken::Number)) } Rule::string_list => { let first_on_list = last.into_inner().next(); - match first_on_list { - Some(first) => Some((first.as_str().to_string(), JsonPathToken::String)), - None => None, - } + first_on_list.map(|first| (first.as_str().to_string(), JsonPathToken::String)) } _ => panic!("pop last was used in a none static path"), }, @@ -65,14 +59,14 @@ impl<'i> Query<'i> { } pub fn is_static(&mut self) -> bool { - if self.is_static.is_some() { - return *self.is_static.as_ref().unwrap(); + if let Some(s) = self.is_static { + return s; } let mut size = 0; let mut is_static = true; - let mut root_copy = self.root.clone(); - while let Some(n) = root_copy.next() { - size = size + 1; + let root_copy = self.root.clone(); + for n in root_copy { + size += 1; match n.as_rule() { Rule::literal | Rule::number => continue, Rule::numbers_list | Rule::string_list => { @@ -94,7 +88,7 @@ impl std::fmt::Display for QueryCompilationError { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { write!( f, - "Error accured on possition {}, {}", + "Error occurred on position {}, {}", self.location, self.message ) } @@ -223,19 +217,19 @@ pub enum PTrackerElement { #[derive(Debug, PartialEq)] pub struct PTracker { - pub elemenets: Vec, + pub elements: Vec, } impl UserPathTracker for PTracker { fn add_str(&mut self, s: &str) { - self.elemenets.push(PTrackerElement::Key(s.to_string())); + self.elements.push(PTrackerElement::Key(s.to_string())); } fn add_index(&mut self, i: usize) { - self.elemenets.push(PTrackerElement::Index(i)); + self.elements.push(PTrackerElement::Index(i)); } fn to_string_path(self) -> Vec { - self.elemenets + self.elements .into_iter() .map(|e| match e { PTrackerElement::Key(s) => s, @@ -250,7 +244,7 @@ impl UserPathTrackerGenerator for PTrackerGenerator { type PT = PTracker; fn generate(&self) -> Self::PT { PTracker { - elemenets: Vec::new(), + elements: Vec::new(), } } } @@ -307,7 +301,7 @@ enum TermEvaluationResult<'i, 'j, S: SelectValue> { enum CmpResult { Ord(Ordering), - NotCmparable, + NotComparable, } impl<'i, 'j, S: SelectValue> TermEvaluationResult<'i, 'j, S> { @@ -347,45 +341,45 @@ impl<'i, 'j, S: SelectValue> TermEvaluationResult<'i, 'j, S> { SelectValueType::Long => TermEvaluationResult::Integer(v.get_long()).cmp(s), SelectValueType::Double => TermEvaluationResult::Float(v.get_double()).cmp(s), SelectValueType::String => TermEvaluationResult::Str(v.as_str()).cmp(s), - _ => CmpResult::NotCmparable, + _ => CmpResult::NotComparable, }, (_, TermEvaluationResult::Value(v)) => match v.get_type() { SelectValueType::Long => self.cmp(&TermEvaluationResult::Integer(v.get_long())), SelectValueType::Double => self.cmp(&TermEvaluationResult::Float(v.get_double())), SelectValueType::String => self.cmp(&TermEvaluationResult::Str(v.as_str())), - _ => CmpResult::NotCmparable, + _ => CmpResult::NotComparable, }, (TermEvaluationResult::Invalid, _) | (_, TermEvaluationResult::Invalid) => { - CmpResult::NotCmparable + CmpResult::NotComparable } - (_, _) => CmpResult::NotCmparable, + (_, _) => CmpResult::NotComparable, } } fn gt(&self, s: &Self) -> bool { match self.cmp(s) { CmpResult::Ord(o) => o.is_gt(), - CmpResult::NotCmparable => false, + CmpResult::NotComparable => false, } } fn ge(&self, s: &Self) -> bool { match self.cmp(s) { CmpResult::Ord(o) => o.is_ge(), - CmpResult::NotCmparable => false, + CmpResult::NotComparable => false, } } fn lt(&self, s: &Self) -> bool { match self.cmp(s) { CmpResult::Ord(o) => o.is_lt(), - CmpResult::NotCmparable => false, + CmpResult::NotComparable => false, } } fn le(&self, s: &Self) -> bool { match self.cmp(s) { CmpResult::Ord(o) => o.is_le(), - CmpResult::NotCmparable => false, + CmpResult::NotComparable => false, } } @@ -404,7 +398,7 @@ impl<'i, 'j, S: SelectValue> TermEvaluationResult<'i, 'j, S> { (TermEvaluationResult::Value(v1), TermEvaluationResult::Value(v2)) => v1 == v2, (_, _) => match self.cmp(s) { CmpResult::Ord(o) => o.is_eq(), - CmpResult::NotCmparable => false, + CmpResult::NotComparable => false, }, } } @@ -915,7 +909,7 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> { Rule::filter => { if flat_arrays_on_filter && json.get_type() == SelectValueType::Array { /* lets expend the array, this is how most json path engines work. - * Pesonally, I think this if should not exists. */ + * Personally, I think this if should not exists. */ let values = json.values().unwrap(); if let Some(pt) = path_tracker { for (i, v) in values.enumerate() { @@ -1028,7 +1022,7 @@ mod json_path_compiler_tests { } #[test] - fn test_compiler_pop_last_string_brucket_notation() { + fn test_compiler_pop_last_string_bracket_notation() { let query = compile("$.[\"foo\"]"); assert_eq!( query.unwrap().pop_last().unwrap(), diff --git a/src/lib.rs b/src/lib.rs index 66ec7af..9e75a68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,7 +59,7 @@ pub fn compile(s: &str) -> Result { json_path::compile(s) } -/// Calc once allows to performe a one time calculation on the give query. +/// Calc once allows to perform a one time calculation on the give query. /// The query ownership is taken so it can not be used after. This allows /// the get a better performance if there is a need to calculate the query /// only once. @@ -115,7 +115,7 @@ mod json_path_tests { path_calculator.calc(json) } - fn perform_path_search<'a>(path: &str, json: &'a Value) -> Vec> { + fn perform_path_search(path: &str, json: &Value) -> Vec> { let query = crate::compile(path).unwrap(); let path_calculator = crate::create_with_generator(&query); path_calculator.calc_paths(json) @@ -142,9 +142,9 @@ mod json_path_tests { ) => { let j = json!($json); let res = perform_path_search($path, &j); - let mut v = Vec::new(); + let mut v = vec![]; $( - let mut s = Vec::new(); + let mut s = vec![]; $( s.push(stringify!($result)); )*