Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 151 additions & 24 deletions src/json_path.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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<bool>,
size: Option<usize>,
}

#[derive(Debug)]
Expand All@@ -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 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this will consume the option value and will leave it None?

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
)
}
Expand All@@ -50,7 +113,14 @@ impl std::fmt::Display for Rule {
pub(crate) fn compile(path: &str) -> Result<Query, QueryCompilationError> {
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 {
Expand DownExpand Up@@ -147,19 +217,19 @@ pub enum PTrackerElement {

#[derive(Debug, PartialEq)]
pub struct PTracker {
pub elemenets: Vec<PTrackerElement>,
pub elements: Vec<PTrackerElement>,
}
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<String> {
self.elemenets
self.elements
.into_iter()
.map(|e| match e {
PTrackerElement::Key(s) => s,
Expand All@@ -174,7 +244,7 @@ impl UserPathTrackerGenerator for PTrackerGenerator {
type PT = PTracker;
fn generate(&self) -> Self::PT {
PTracker {
elemenets: Vec::new(),
elements: Vec::new(),
}
}
}
Expand All@@ -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),
Expand DownExpand Up@@ -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> {
Expand DownExpand Up@@ -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,
}
}

Expand All@@ -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,
},
}
}
Expand DownExpand Up@@ -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() {
Expand DownExpand Up@@ -883,22 +956,22 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> {
pub fn calc_with_paths_on_root<'j: 'i, S: SelectValue>(
&self,
json: &'j S,
root: Pair<Rule>,
root: Pairs<'i, Rule>,
) -> Vec<CalculationResult<'j, S, UPTG::PT>> {
let mut calc_data = PathCalculatorData {
results: Vec::new(),
root: json,
};
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()
}
Expand All@@ -907,7 +980,7 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> {
&self,
json: &'j S,
) -> Vec<CalculationResult<'j, S, UPTG::PT>> {
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> {
Expand All@@ -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);
}
}
20 changes: 10 additions & 10 deletions src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,12 +59,12 @@ pub fn compile(s: &str) -> Result<Query, QueryCompilationError> {
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,
Expand All@@ -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<CalculationResult<'p, S, PTracker>> {
let root = q.query.next().unwrap();
let root = q.root;
PathCalculator {
query: None,
tracker_generator: Some(PTrackerGenerator),
Expand All@@ -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<Vec<String>>.
pub fn calc_once_paths<S: SelectValue>(mut q: Query, json: &S) -> Vec<Vec<String>> {
let root = q.query.next().unwrap();
pub fn calc_once_paths<S: SelectValue>(q: Query, json: &S) -> Vec<Vec<String>> {
let root = q.root;
PathCalculator {
query: None,
tracker_generator: Some(PTrackerGenerator),
Expand All@@ -115,7 +115,7 @@ mod json_path_tests {
path_calculator.calc(json)
}

fn perform_path_search<'a>(path: &str, json: &'a Value) -> Vec<Vec<String>> {
fn perform_path_search(path: &str, json: &Value) -> Vec<Vec<String>> {
let query = crate::compile(path).unwrap();
let path_calculator = crate::create_with_generator(&query);
path_calculator.calc_paths(json)
Expand All@@ -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));
)*
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix typos and warnings by gkorland · Pull Request #6 · RedisJSON/jsonpath_rs · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 151 additions & 24 deletions src/json_path.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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<bool>,
size: Option<usize>,
}

#[derive(Debug)]
Expand All@@ -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 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this will consume the option value and will leave it None?

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
)
}
Expand All@@ -50,7 +113,14 @@ impl std::fmt::Display for Rule {
pub(crate) fn compile(path: &str) -> Result<Query, QueryCompilationError> {
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 {
Expand DownExpand Up@@ -147,19 +217,19 @@ pub enum PTrackerElement {

#[derive(Debug, PartialEq)]
pub struct PTracker {
pub elemenets: Vec<PTrackerElement>,
pub elements: Vec<PTrackerElement>,
}
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<String> {
self.elemenets
self.elements
.into_iter()
.map(|e| match e {
PTrackerElement::Key(s) => s,
Expand All@@ -174,7 +244,7 @@ impl UserPathTrackerGenerator for PTrackerGenerator {
type PT = PTracker;
fn generate(&self) -> Self::PT {
PTracker {
elemenets: Vec::new(),
elements: Vec::new(),
}
}
}
Expand All@@ -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),
Expand DownExpand Up@@ -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> {
Expand DownExpand Up@@ -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,
}
}

Expand All@@ -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,
},
}
}
Expand DownExpand Up@@ -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() {
Expand DownExpand Up@@ -883,22 +956,22 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> {
pub fn calc_with_paths_on_root<'j: 'i, S: SelectValue>(
&self,
json: &'j S,
root: Pair<Rule>,
root: Pairs<'i, Rule>,
) -> Vec<CalculationResult<'j, S, UPTG::PT>> {
let mut calc_data = PathCalculatorData {
results: Vec::new(),
root: json,
};
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()
}
Expand All@@ -907,7 +980,7 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> {
&self,
json: &'j S,
) -> Vec<CalculationResult<'j, S, UPTG::PT>> {
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> {
Expand All@@ -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);
}
}
20 changes: 10 additions & 10 deletions src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,12 +59,12 @@ pub fn compile(s: &str) -> Result<Query, QueryCompilationError> {
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,
Expand All@@ -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<CalculationResult<'p, S, PTracker>> {
let root = q.query.next().unwrap();
let root = q.root;
PathCalculator {
query: None,
tracker_generator: Some(PTrackerGenerator),
Expand All@@ -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<Vec<String>>.
pub fn calc_once_paths<S: SelectValue>(mut q: Query, json: &S) -> Vec<Vec<String>> {
let root = q.query.next().unwrap();
pub fn calc_once_paths<S: SelectValue>(q: Query, json: &S) -> Vec<Vec<String>> {
let root = q.root;
PathCalculator {
query: None,
tracker_generator: Some(PTrackerGenerator),
Expand All@@ -115,7 +115,7 @@ mod json_path_tests {
path_calculator.calc(json)
}

fn perform_path_search<'a>(path: &str, json: &'a Value) -> Vec<Vec<String>> {
fn perform_path_search(path: &str, json: &Value) -> Vec<Vec<String>> {
let query = crate::compile(path).unwrap();
let path_calculator = crate::create_with_generator(&query);
path_calculator.calc_paths(json)
Expand All@@ -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));
)*
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix typos and warnings by gkorland · Pull Request #6 · RedisJSON/jsonpath_rs · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 151 additions & 24 deletions src/json_path.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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<bool>,
size: Option<usize>,
}

#[derive(Debug)]
Expand All@@ -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 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this will consume the option value and will leave it None?

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
)
}
Expand All@@ -50,7 +113,14 @@ impl std::fmt::Display for Rule {
pub(crate) fn compile(path: &str) -> Result<Query, QueryCompilationError> {
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 {
Expand DownExpand Up@@ -147,19 +217,19 @@ pub enum PTrackerElement {

#[derive(Debug, PartialEq)]
pub struct PTracker {
pub elemenets: Vec<PTrackerElement>,
pub elements: Vec<PTrackerElement>,
}
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<String> {
self.elemenets
self.elements
.into_iter()
.map(|e| match e {
PTrackerElement::Key(s) => s,
Expand All@@ -174,7 +244,7 @@ impl UserPathTrackerGenerator for PTrackerGenerator {
type PT = PTracker;
fn generate(&self) -> Self::PT {
PTracker {
elemenets: Vec::new(),
elements: Vec::new(),
}
}
}
Expand All@@ -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),
Expand DownExpand Up@@ -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> {
Expand DownExpand Up@@ -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,
}
}

Expand All@@ -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,
},
}
}
Expand DownExpand Up@@ -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() {
Expand DownExpand Up@@ -883,22 +956,22 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> {
pub fn calc_with_paths_on_root<'j: 'i, S: SelectValue>(
&self,
json: &'j S,
root: Pair<Rule>,
root: Pairs<'i, Rule>,
) -> Vec<CalculationResult<'j, S, UPTG::PT>> {
let mut calc_data = PathCalculatorData {
results: Vec::new(),
root: json,
};
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()
}
Expand All@@ -907,7 +980,7 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> {
&self,
json: &'j S,
) -> Vec<CalculationResult<'j, S, UPTG::PT>> {
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> {
Expand All@@ -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);
}
}
20 changes: 10 additions & 10 deletions src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,12 +59,12 @@ pub fn compile(s: &str) -> Result<Query, QueryCompilationError> {
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,
Expand All@@ -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<CalculationResult<'p, S, PTracker>> {
let root = q.query.next().unwrap();
let root = q.root;
PathCalculator {
query: None,
tracker_generator: Some(PTrackerGenerator),
Expand All@@ -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<Vec<String>>.
pub fn calc_once_paths<S: SelectValue>(mut q: Query, json: &S) -> Vec<Vec<String>> {
let root = q.query.next().unwrap();
pub fn calc_once_paths<S: SelectValue>(q: Query, json: &S) -> Vec<Vec<String>> {
let root = q.root;
PathCalculator {
query: None,
tracker_generator: Some(PTrackerGenerator),
Expand All@@ -115,7 +115,7 @@ mod json_path_tests {
path_calculator.calc(json)
}

fn perform_path_search<'a>(path: &str, json: &'a Value) -> Vec<Vec<String>> {
fn perform_path_search(path: &str, json: &Value) -> Vec<Vec<String>> {
let query = crate::compile(path).unwrap();
let path_calculator = crate::create_with_generator(&query);
path_calculator.calc_paths(json)
Expand All@@ -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));
)*
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix typos and warnings by gkorland · Pull Request #6 · RedisJSON/jsonpath_rs · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 151 additions & 24 deletions src/json_path.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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<bool>,
size: Option<usize>,
}

#[derive(Debug)]
Expand All@@ -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 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this will consume the option value and will leave it None?

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
)
}
Expand All@@ -50,7 +113,14 @@ impl std::fmt::Display for Rule {
pub(crate) fn compile(path: &str) -> Result<Query, QueryCompilationError> {
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 {
Expand DownExpand Up@@ -147,19 +217,19 @@ pub enum PTrackerElement {

#[derive(Debug, PartialEq)]
pub struct PTracker {
pub elemenets: Vec<PTrackerElement>,
pub elements: Vec<PTrackerElement>,
}
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<String> {
self.elemenets
self.elements
.into_iter()
.map(|e| match e {
PTrackerElement::Key(s) => s,
Expand All@@ -174,7 +244,7 @@ impl UserPathTrackerGenerator for PTrackerGenerator {
type PT = PTracker;
fn generate(&self) -> Self::PT {
PTracker {
elemenets: Vec::new(),
elements: Vec::new(),
}
}
}
Expand All@@ -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),
Expand DownExpand Up@@ -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> {
Expand DownExpand Up@@ -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,
}
}

Expand All@@ -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,
},
}
}
Expand DownExpand Up@@ -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() {
Expand DownExpand Up@@ -883,22 +956,22 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> {
pub fn calc_with_paths_on_root<'j: 'i, S: SelectValue>(
&self,
json: &'j S,
root: Pair<Rule>,
root: Pairs<'i, Rule>,
) -> Vec<CalculationResult<'j, S, UPTG::PT>> {
let mut calc_data = PathCalculatorData {
results: Vec::new(),
root: json,
};
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()
}
Expand All@@ -907,7 +980,7 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> {
&self,
json: &'j S,
) -> Vec<CalculationResult<'j, S, UPTG::PT>> {
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> {
Expand All@@ -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);
}
}
20 changes: 10 additions & 10 deletions src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,12 +59,12 @@ pub fn compile(s: &str) -> Result<Query, QueryCompilationError> {
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,
Expand All@@ -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<CalculationResult<'p, S, PTracker>> {
let root = q.query.next().unwrap();
let root = q.root;
PathCalculator {
query: None,
tracker_generator: Some(PTrackerGenerator),
Expand All@@ -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<Vec<String>>.
pub fn calc_once_paths<S: SelectValue>(mut q: Query, json: &S) -> Vec<Vec<String>> {
let root = q.query.next().unwrap();
pub fn calc_once_paths<S: SelectValue>(q: Query, json: &S) -> Vec<Vec<String>> {
let root = q.root;
PathCalculator {
query: None,
tracker_generator: Some(PTrackerGenerator),
Expand All@@ -115,7 +115,7 @@ mod json_path_tests {
path_calculator.calc(json)
}

fn perform_path_search<'a>(path: &str, json: &'a Value) -> Vec<Vec<String>> {
fn perform_path_search(path: &str, json: &Value) -> Vec<Vec<String>> {
let query = crate::compile(path).unwrap();
let path_calculator = crate::create_with_generator(&query);
path_calculator.calc_paths(json)
Expand All@@ -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));
)*
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' fix typos and warnings by gkorland · Pull Request #6 · RedisJSON/jsonpath_rs · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 151 additions & 24 deletions src/json_path.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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<bool>,
size: Option<usize>,
}

#[derive(Debug)]
Expand All@@ -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 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this will consume the option value and will leave it None?

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
)
}
Expand All@@ -50,7 +113,14 @@ impl std::fmt::Display for Rule {
pub(crate) fn compile(path: &str) -> Result<Query, QueryCompilationError> {
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 {
Expand DownExpand Up@@ -147,19 +217,19 @@ pub enum PTrackerElement {

#[derive(Debug, PartialEq)]
pub struct PTracker {
pub elemenets: Vec<PTrackerElement>,
pub elements: Vec<PTrackerElement>,
}
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<String> {
self.elemenets
self.elements
.into_iter()
.map(|e| match e {
PTrackerElement::Key(s) => s,
Expand All@@ -174,7 +244,7 @@ impl UserPathTrackerGenerator for PTrackerGenerator {
type PT = PTracker;
fn generate(&self) -> Self::PT {
PTracker {
elemenets: Vec::new(),
elements: Vec::new(),
}
}
}
Expand All@@ -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),
Expand DownExpand Up@@ -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> {
Expand DownExpand Up@@ -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,
}
}

Expand All@@ -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,
},
}
}
Expand DownExpand Up@@ -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() {
Expand DownExpand Up@@ -883,22 +956,22 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> {
pub fn calc_with_paths_on_root<'j: 'i, S: SelectValue>(
&self,
json: &'j S,
root: Pair<Rule>,
root: Pairs<'i, Rule>,
) -> Vec<CalculationResult<'j, S, UPTG::PT>> {
let mut calc_data = PathCalculatorData {
results: Vec::new(),
root: json,
};
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()
}
Expand All@@ -907,7 +980,7 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> {
&self,
json: &'j S,
) -> Vec<CalculationResult<'j, S, UPTG::PT>> {
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> {
Expand All@@ -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);
}
}
20 changes: 10 additions & 10 deletions src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,12 +59,12 @@ pub fn compile(s: &str) -> Result<Query, QueryCompilationError> {
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,
Expand All@@ -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<CalculationResult<'p, S, PTracker>> {
let root = q.query.next().unwrap();
let root = q.root;
PathCalculator {
query: None,
tracker_generator: Some(PTrackerGenerator),
Expand All@@ -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<Vec<String>>.
pub fn calc_once_paths<S: SelectValue>(mut q: Query, json: &S) -> Vec<Vec<String>> {
let root = q.query.next().unwrap();
pub fn calc_once_paths<S: SelectValue>(q: Query, json: &S) -> Vec<Vec<String>> {
let root = q.root;
PathCalculator {
query: None,
tracker_generator: Some(PTrackerGenerator),
Expand All@@ -115,7 +115,7 @@ mod json_path_tests {
path_calculator.calc(json)
}

fn perform_path_search<'a>(path: &str, json: &'a Value) -> Vec<Vec<String>> {
fn perform_path_search(path: &str, json: &Value) -> Vec<Vec<String>> {
let query = crate::compile(path).unwrap();
let path_calculator = crate::create_with_generator(&query);
path_calculator.calc_paths(json)
Expand All@@ -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));
)*
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix typos and warnings by gkorland · Pull Request #6 · RedisJSON/jsonpath_rs · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 151 additions & 24 deletions src/json_path.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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<bool>,
size: Option<usize>,
}

#[derive(Debug)]
Expand All@@ -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 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this will consume the option value and will leave it None?

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
)
}
Expand All@@ -50,7 +113,14 @@ impl std::fmt::Display for Rule {
pub(crate) fn compile(path: &str) -> Result<Query, QueryCompilationError> {
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 {
Expand DownExpand Up@@ -147,19 +217,19 @@ pub enum PTrackerElement {

#[derive(Debug, PartialEq)]
pub struct PTracker {
pub elemenets: Vec<PTrackerElement>,
pub elements: Vec<PTrackerElement>,
}
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<String> {
self.elemenets
self.elements
.into_iter()
.map(|e| match e {
PTrackerElement::Key(s) => s,
Expand All@@ -174,7 +244,7 @@ impl UserPathTrackerGenerator for PTrackerGenerator {
type PT = PTracker;
fn generate(&self) -> Self::PT {
PTracker {
elemenets: Vec::new(),
elements: Vec::new(),
}
}
}
Expand All@@ -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),
Expand DownExpand Up@@ -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> {
Expand DownExpand Up@@ -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,
}
}

Expand All@@ -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,
},
}
}
Expand DownExpand Up@@ -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() {
Expand DownExpand Up@@ -883,22 +956,22 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> {
pub fn calc_with_paths_on_root<'j: 'i, S: SelectValue>(
&self,
json: &'j S,
root: Pair<Rule>,
root: Pairs<'i, Rule>,
) -> Vec<CalculationResult<'j, S, UPTG::PT>> {
let mut calc_data = PathCalculatorData {
results: Vec::new(),
root: json,
};
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()
}
Expand All@@ -907,7 +980,7 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> {
&self,
json: &'j S,
) -> Vec<CalculationResult<'j, S, UPTG::PT>> {
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> {
Expand All@@ -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);
}
}
20 changes: 10 additions & 10 deletions src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,12 +59,12 @@ pub fn compile(s: &str) -> Result<Query, QueryCompilationError> {
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,
Expand All@@ -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<CalculationResult<'p, S, PTracker>> {
let root = q.query.next().unwrap();
let root = q.root;
PathCalculator {
query: None,
tracker_generator: Some(PTrackerGenerator),
Expand All@@ -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<Vec<String>>.
pub fn calc_once_paths<S: SelectValue>(mut q: Query, json: &S) -> Vec<Vec<String>> {
let root = q.query.next().unwrap();
pub fn calc_once_paths<S: SelectValue>(q: Query, json: &S) -> Vec<Vec<String>> {
let root = q.root;
PathCalculator {
query: None,
tracker_generator: Some(PTrackerGenerator),
Expand All@@ -115,7 +115,7 @@ mod json_path_tests {
path_calculator.calc(json)
}

fn perform_path_search<'a>(path: &str, json: &'a Value) -> Vec<Vec<String>> {
fn perform_path_search(path: &str, json: &Value) -> Vec<Vec<String>> {
let query = crate::compile(path).unwrap();
let path_calculator = crate::create_with_generator(&query);
path_calculator.calc_paths(json)
Expand All@@ -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));
)*
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); fix typos and warnings by gkorland · Pull Request #6 · RedisJSON/jsonpath_rs · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 151 additions & 24 deletions src/json_path.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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<bool>,
size: Option<usize>,
}

#[derive(Debug)]
Expand All@@ -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 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this will consume the option value and will leave it None?

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
)
}
Expand All@@ -50,7 +113,14 @@ impl std::fmt::Display for Rule {
pub(crate) fn compile(path: &str) -> Result<Query, QueryCompilationError> {
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 {
Expand DownExpand Up@@ -147,19 +217,19 @@ pub enum PTrackerElement {

#[derive(Debug, PartialEq)]
pub struct PTracker {
pub elemenets: Vec<PTrackerElement>,
pub elements: Vec<PTrackerElement>,
}
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<String> {
self.elemenets
self.elements
.into_iter()
.map(|e| match e {
PTrackerElement::Key(s) => s,
Expand All@@ -174,7 +244,7 @@ impl UserPathTrackerGenerator for PTrackerGenerator {
type PT = PTracker;
fn generate(&self) -> Self::PT {
PTracker {
elemenets: Vec::new(),
elements: Vec::new(),
}
}
}
Expand All@@ -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),
Expand DownExpand Up@@ -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> {
Expand DownExpand Up@@ -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,
}
}

Expand All@@ -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,
},
}
}
Expand DownExpand Up@@ -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() {
Expand DownExpand Up@@ -883,22 +956,22 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> {
pub fn calc_with_paths_on_root<'j: 'i, S: SelectValue>(
&self,
json: &'j S,
root: Pair<Rule>,
root: Pairs<'i, Rule>,
) -> Vec<CalculationResult<'j, S, UPTG::PT>> {
let mut calc_data = PathCalculatorData {
results: Vec::new(),
root: json,
};
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()
}
Expand All@@ -907,7 +980,7 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> {
&self,
json: &'j S,
) -> Vec<CalculationResult<'j, S, UPTG::PT>> {
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> {
Expand All@@ -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);
}
}
20 changes: 10 additions & 10 deletions src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,12 +59,12 @@ pub fn compile(s: &str) -> Result<Query, QueryCompilationError> {
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,
Expand All@@ -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<CalculationResult<'p, S, PTracker>> {
let root = q.query.next().unwrap();
let root = q.root;
PathCalculator {
query: None,
tracker_generator: Some(PTrackerGenerator),
Expand All@@ -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<Vec<String>>.
pub fn calc_once_paths<S: SelectValue>(mut q: Query, json: &S) -> Vec<Vec<String>> {
let root = q.query.next().unwrap();
pub fn calc_once_paths<S: SelectValue>(q: Query, json: &S) -> Vec<Vec<String>> {
let root = q.root;
PathCalculator {
query: None,
tracker_generator: Some(PTrackerGenerator),
Expand All@@ -115,7 +115,7 @@ mod json_path_tests {
path_calculator.calc(json)
}

fn perform_path_search<'a>(path: &str, json: &'a Value) -> Vec<Vec<String>> {
fn perform_path_search(path: &str, json: &Value) -> Vec<Vec<String>> {
let query = crate::compile(path).unwrap();
let path_calculator = crate::create_with_generator(&query);
path_calculator.calc_paths(json)
Expand All@@ -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));
)*
Expand Down