Skip to content

Commit

Permalink
feat: add LocatedNodeList type
Browse files Browse the repository at this point in the history
The LocatedNodeList is similar to NodeList but provides the location along with each node.

Queryable::query_paths was renamed to query_located to coincide with LocatedNodeList

Trait implementations added to NormalizedPath and PathElement types.
  • Loading branch information
hiltontj committed Jan 28, 2024
1 parent cca17b4 commit f2c23c8
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 33 deletions.
10 changes: 6 additions & 4 deletions serde_json_path/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::str::FromStr;
use serde::{de::Visitor, Deserialize, Serialize};
use serde_json::Value;
use serde_json_path_core::{
node::NodeList,
node::{LocatedNodeList, NodeList},
spec::{
path::NormalizedPath,
query::{Query, Queryable},
Expand Down Expand Up @@ -79,8 +79,10 @@ impl JsonPath {
self.0.query(value, value).into()
}

pub fn query_paths<'b>(&self, value: &'b Value) -> Vec<(NormalizedPath<'b>, &'b Value)> {
self.0.query_paths_init(value, value)
pub fn query_located<'b>(&self, value: &'b Value) -> LocatedNodeList<'b> {
self.0
.query_located(value, value, Default::default())
.into()
}
}

Expand Down Expand Up @@ -167,7 +169,7 @@ mod tests {
"bar": [1, 2, 3]
}});
let p = JsonPath::parse("$.foo.bar.*").unwrap();
let r = p.query_paths(&j);
let r = p.query_located(&j);
for (np, _) in r {
println!("{pointer}", pointer = np.as_json_pointer());
}
Expand Down
75 changes: 74 additions & 1 deletion serde_json_path_core/src/node.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! Types representing nodes within a JSON object
use std::slice::Iter;
use std::{cmp::Ordering, slice::Iter};

Check failure on line 2 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Docs

unused import: `cmp::Ordering`

Check failure on line 2 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Test

unused import: `cmp::Ordering`

Check failure on line 2 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused import: `cmp::Ordering`

use serde::Serialize;
use serde_json::Value;

use crate::spec::path::NormalizedPath;

/// A list of nodes resulting from a JSONPath query
///
/// Each node within the list is a borrowed reference to the node in the original
Expand Down Expand Up @@ -261,6 +263,77 @@ impl<'a> IntoIterator for NodeList<'a> {
}
}

#[derive(Debug, Default, Eq, PartialEq, Serialize, Clone)]
pub struct LocatedNodeList<'a>(Vec<(NormalizedPath<'a>, &'a Value)>);

Check warning on line 267 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a struct

Check failure on line 267 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a struct

Check failure on line 267 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Test

missing documentation for a struct

Check failure on line 267 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Clippy

missing documentation for a struct

impl<'a> LocatedNodeList<'a> {
pub fn at_most_one(

Check warning on line 270 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method

Check failure on line 270 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method

Check failure on line 270 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Test

missing documentation for a method

Check failure on line 270 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Clippy

missing documentation for a method
mut self,
) -> Result<Option<(NormalizedPath<'a>, &'a Value)>, AtMostOneError> {
if self.0.is_empty() {
Ok(None)
} else if self.0.len() > 1 {
Err(AtMostOneError(self.0.len()))
} else {
Ok(Some(self.0.pop().unwrap()))
}
}

pub fn exactly_one(mut self) -> Result<(NormalizedPath<'a>, &'a Value), ExactlyOneError> {

Check warning on line 282 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method

Check failure on line 282 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method

Check failure on line 282 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Test

missing documentation for a method

Check failure on line 282 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Clippy

missing documentation for a method
if self.0.is_empty() {
Err(ExactlyOneError::Empty)
} else if self.0.len() > 1 {
Err(ExactlyOneError::MoreThanOne(self.0.len()))
} else {
Ok(self.0.pop().unwrap())
}
}

pub fn all(self) -> Vec<(NormalizedPath<'a>, &'a Value)> {

Check warning on line 292 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method

Check failure on line 292 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method

Check failure on line 292 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Test

missing documentation for a method

Check failure on line 292 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Clippy

missing documentation for a method
self.0
}

pub fn len(&self) -> usize {

Check warning on line 296 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method

Check failure on line 296 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method

Check failure on line 296 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Test

missing documentation for a method

Check failure on line 296 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Clippy

missing documentation for a method
self.0.len()
}

pub fn is_empty(&self) -> bool {

Check warning on line 300 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method

Check failure on line 300 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method

Check failure on line 300 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Test

missing documentation for a method

Check failure on line 300 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Clippy

missing documentation for a method
self.0.is_empty()
}

pub fn iter(&self) -> Iter<'_, (NormalizedPath<'a>, &'a Value)> {

Check warning on line 304 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method

Check failure on line 304 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method

Check failure on line 304 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Test

missing documentation for a method

Check failure on line 304 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Clippy

missing documentation for a method
self.0.iter()
}

pub fn dedup(mut self) -> Self {

Check warning on line 308 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method

Check failure on line 308 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method

Check failure on line 308 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Test

missing documentation for a method

Check failure on line 308 in serde_json_path_core/src/node.rs

View workflow job for this annotation

GitHub Actions / Clippy

missing documentation for a method
self.0
// This unwrap is safe, since the paths corresponding to
// a query against a Value will always be ordered.
//
// TODO - the below From impl may in theory allow someone
// to violate this, so may need to remedy that...
.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
self.0.dedup();
self
}
}

impl<'a> From<Vec<(NormalizedPath<'a>, &'a Value)>> for LocatedNodeList<'a> {
fn from(v: Vec<(NormalizedPath<'a>, &'a Value)>) -> Self {
Self(v)
}
}

impl<'a> IntoIterator for LocatedNodeList<'a> {
type Item = (NormalizedPath<'a>, &'a Value);

type IntoIter = std::vec::IntoIter<Self::Item>;

fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}

#[cfg(test)]
mod tests {
use crate::node::NodeList;
Expand Down
73 changes: 71 additions & 2 deletions serde_json_path_core/src/spec/path.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#[derive(Debug, Clone, Default)]
use std::{cmp::Ordering, fmt::Display, slice::Iter};

use serde::Serialize;

#[derive(Debug, Default, Eq, PartialEq, Clone, PartialOrd)]
pub struct NormalizedPath<'a>(Vec<PathElement<'a>>);

Check warning on line 6 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a struct

impl<'a> NormalizedPath<'a> {
Expand All @@ -22,9 +26,43 @@ impl<'a> NormalizedPath<'a> {
acc
})
}

pub fn is_empty(&self) -> bool {
self.0.is_empty()
}

pub fn len(&self) -> usize {
self.0.len()
}

pub fn iter(&self) -> Iter<'_, PathElement<'a>> {
self.0.iter()
}
}

impl<'a> Display for NormalizedPath<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "$")?;
for elem in &self.0 {
match elem {
PathElement::Name(name) => write!(f, "['{name}']")?,
PathElement::Index(index) => write!(f, "[{index}]")?,
}
}
Ok(())
}
}

impl<'a> Serialize for NormalizedPath<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.to_string().as_str())
}
}

#[derive(Debug, Clone, Copy)]
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum PathElement<'a> {
Name(&'a str),
Index(usize),
Expand All @@ -39,6 +77,25 @@ impl<'a> PathElement<'a> {
}
}

impl<'a> PartialOrd for PathElement<'a> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(PathElement::Name(a), PathElement::Name(b)) => a.partial_cmp(b),
(PathElement::Index(a), PathElement::Index(b)) => a.partial_cmp(b),
_ => None,
}
}
}

impl<'a> Display for PathElement<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PathElement::Name(n) => write!(f, "{n}"),
PathElement::Index(i) => write!(f, "{i}"),
}
}
}

impl<'a> From<&'a String> for PathElement<'a> {
fn from(s: &'a String) -> Self {
Self::Name(s.as_str())
Expand All @@ -51,6 +108,18 @@ impl<'a> From<usize> for PathElement<'a> {
}
}

impl<'a> Serialize for PathElement<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
PathElement::Name(s) => serializer.serialize_str(s),
PathElement::Index(i) => serializer.serialize_u64(*i as u64),
}
}
}

#[cfg(test)]
mod tests {
use super::{NormalizedPath, PathElement};
Expand Down
13 changes: 3 additions & 10 deletions serde_json_path_core/src/spec/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,12 @@ mod sealed {
pub trait Queryable: sealed::Sealed {
/// Query `self` using a current node, and the root node
fn query<'b>(&self, current: &'b Value, root: &'b Value) -> Vec<&'b Value>;
fn query_paths<'b>(
fn query_located<'b>(
&self,
current: &'b Value,
root: &'b Value,
parent: NormalizedPath<'b>,
) -> Vec<(NormalizedPath<'b>, &'b Value)>;
fn query_paths_init<'b>(
&self,
current: &'b Value,
root: &'b Value,
) -> Vec<(NormalizedPath<'b>, &'b Value)> {
self.query_paths(current, root, Default::default())
}
}

/// Represents a JSONPath expression
Expand Down Expand Up @@ -111,7 +104,7 @@ impl Queryable for Query {
query
}

fn query_paths<'b>(
fn query_located<'b>(
&self,
current: &'b Value,
root: &'b Value,
Expand All @@ -124,7 +117,7 @@ impl Queryable for Query {
for s in &self.segments {
let mut r = vec![];
for (ref np, v) in result {
r.append(&mut s.query_paths(v, root, np.clone()));
r.append(&mut s.query_located(v, root, np.clone()));
}
result = r;
}
Expand Down
14 changes: 7 additions & 7 deletions serde_json_path_core/src/spec/segment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,18 @@ impl Queryable for QuerySegment {
query
}

fn query_paths<'b>(
fn query_located<'b>(
&self,
current: &'b Value,
root: &'b Value,
parent: NormalizedPath<'b>,
) -> Vec<(NormalizedPath<'b>, &'b Value)> {
if matches!(self.kind, QuerySegmentKind::Descendant) {
let mut result = self.segment.query_paths(current, root, parent.clone());
let mut result = self.segment.query_located(current, root, parent.clone());
result.append(&mut descend_paths(self, current, root, parent));
result
} else {
self.segment.query_paths(current, root, parent)
self.segment.query_located(current, root, parent)
}
}
}
Expand Down Expand Up @@ -96,11 +96,11 @@ fn descend_paths<'b>(
let mut result = Vec::new();
if let Some(list) = current.as_array() {
for (i, v) in list.iter().enumerate() {
result.append(&mut segment.query_paths(v, root, parent.clone_and_push(i)));
result.append(&mut segment.query_located(v, root, parent.clone_and_push(i)));
}
} else if let Some(obj) = current.as_object() {
for (k, v) in obj {
result.append(&mut segment.query_paths(v, root, parent.clone_and_push(k)));
result.append(&mut segment.query_located(v, root, parent.clone_and_push(k)));
}
}
result
Expand Down Expand Up @@ -209,7 +209,7 @@ impl Queryable for Segment {
query
}

fn query_paths<'b>(
fn query_located<'b>(
&self,
current: &'b Value,
root: &'b Value,
Expand All @@ -219,7 +219,7 @@ impl Queryable for Segment {
match self {
Segment::LongHand(selectors) => {
for s in selectors {
result.append(&mut s.query_paths(current, root, parent.clone()));
result.append(&mut s.query_located(current, root, parent.clone()));
}
}
Segment::DotName(name) => {
Expand Down
2 changes: 1 addition & 1 deletion serde_json_path_core/src/spec/selector/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl Queryable for Filter {
}
}

fn query_paths<'b>(
fn query_located<'b>(
&self,
current: &'b Value,
root: &'b Value,
Expand Down
2 changes: 1 addition & 1 deletion serde_json_path_core/src/spec/selector/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl Queryable for Index {
}
}

fn query_paths<'b>(
fn query_located<'b>(
&self,
current: &'b Value,
_root: &'b Value,
Expand Down
10 changes: 5 additions & 5 deletions serde_json_path_core/src/spec/selector/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,14 @@ impl Queryable for Selector {
query
}

fn query_paths<'b>(
fn query_located<'b>(
&self,
current: &'b Value,
root: &'b Value,
parent: NormalizedPath<'b>,
) -> Vec<(NormalizedPath<'b>, &'b Value)> {
match self {
Selector::Name(name) => name.query_paths(current, root, parent),
Selector::Name(name) => name.query_located(current, root, parent),
Selector::Wildcard => {
if let Some(list) = current.as_array() {
list.iter()
Expand All @@ -104,9 +104,9 @@ impl Queryable for Selector {
vec![]
}
}
Selector::Index(index) => index.query_paths(current, root, parent),
Selector::ArraySlice(slice) => slice.query_paths(current, root, parent),
Selector::Filter(filter) => filter.query_paths(current, root, parent),
Selector::Index(index) => index.query_located(current, root, parent),
Selector::ArraySlice(slice) => slice.query_located(current, root, parent),
Selector::Filter(filter) => filter.query_located(current, root, parent),
}
}
}
2 changes: 1 addition & 1 deletion serde_json_path_core/src/spec/selector/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl Queryable for Name {
}
}

fn query_paths<'b>(
fn query_located<'b>(
&self,
current: &'b Value,
_root: &'b Value,
Expand Down
2 changes: 1 addition & 1 deletion serde_json_path_core/src/spec/selector/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ impl Queryable for Slice {
}
}

fn query_paths<'b>(
fn query_located<'b>(
&self,
current: &'b Value,
_root: &'b Value,
Expand Down

0 comments on commit f2c23c8

Please sign in to comment.