diff --git a/src/json_path.rs b/src/json_path.rs index fd07b22..42d20f3 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,11 +29,66 @@ 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(); + first_on_list.map(|first| (first.as_str().to_string(), JsonPathToken::Number)) + } + Rule::string_list => { + let first_on_list = last.into_inner().next(); + first_on_list.map(|first| (first.as_str().to_string(), JsonPathToken::String)) + } + _ => 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 let Some(s) = self.is_static { + return s; + } + let mut size = 0; + let mut is_static = true; + 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 => { + 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!( f, - "Error accured on possition {}, {}", + "Error occurred on position {}, {}", self.location, self.message ) } @@ -50,7 +113,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 { @@ -147,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, @@ -174,7 +244,7 @@ impl UserPathTrackerGenerator for PTrackerGenerator { type PT = PTracker; fn generate(&self) -> Self::PT { PTracker { - elemenets: Vec::new(), + elements: Vec::new(), } } } @@ -199,7 +269,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), @@ -228,7 +301,7 @@ enum TermEvaluationResult<'i, 'j, S: SelectValue> { enum CmpResult { Ord(Ordering), - NotCmparable, + NotComparable, } impl<'i, 'j, S: SelectValue> TermEvaluationResult<'i, 'j, S> { @@ -268,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, } } @@ -325,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, }, } } @@ -836,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() { @@ -883,7 +956,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 +964,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 +980,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 +997,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_bracket_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..9e75a68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,12 +59,12 @@ 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. -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), @@ -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)); )*