From f2c23c82e754417d0654969c1fa17419ce4679c4 Mon Sep 17 00:00:00 2001 From: Trevor Hilton Date: Sun, 28 Jan 2024 09:02:07 -0500 Subject: [PATCH] feat: add LocatedNodeList type 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. --- serde_json_path/src/path.rs | 10 ++- serde_json_path_core/src/node.rs | 75 ++++++++++++++++++- serde_json_path_core/src/spec/path.rs | 73 +++++++++++++++++- serde_json_path_core/src/spec/query.rs | 13 +--- serde_json_path_core/src/spec/segment.rs | 14 ++-- .../src/spec/selector/filter.rs | 2 +- .../src/spec/selector/index.rs | 2 +- serde_json_path_core/src/spec/selector/mod.rs | 10 +-- .../src/spec/selector/name.rs | 2 +- .../src/spec/selector/slice.rs | 2 +- 10 files changed, 170 insertions(+), 33 deletions(-) diff --git a/serde_json_path/src/path.rs b/serde_json_path/src/path.rs index 756dd49..bb3832d 100644 --- a/serde_json_path/src/path.rs +++ b/serde_json_path/src/path.rs @@ -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}, @@ -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() } } @@ -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()); } diff --git a/serde_json_path_core/src/node.rs b/serde_json_path_core/src/node.rs index c3534d5..24a3aa2 100644 --- a/serde_json_path_core/src/node.rs +++ b/serde_json_path_core/src/node.rs @@ -1,9 +1,11 @@ //! Types representing nodes within a JSON object -use std::slice::Iter; +use std::{cmp::Ordering, slice::Iter}; 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 @@ -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)>); + +impl<'a> LocatedNodeList<'a> { + pub fn at_most_one( + mut self, + ) -> Result, &'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> { + 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)> { + self.0 + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn iter(&self) -> Iter<'_, (NormalizedPath<'a>, &'a Value)> { + self.0.iter() + } + + pub fn dedup(mut self) -> Self { + 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, &'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; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + #[cfg(test)] mod tests { use crate::node::NodeList; diff --git a/serde_json_path_core/src/spec/path.rs b/serde_json_path_core/src/spec/path.rs index 6aad4cd..7ef6f4d 100644 --- a/serde_json_path_core/src/spec/path.rs +++ b/serde_json_path_core/src/spec/path.rs @@ -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>); impl<'a> NormalizedPath<'a> { @@ -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(&self, serializer: S) -> Result + 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), @@ -39,6 +77,25 @@ impl<'a> PathElement<'a> { } } +impl<'a> PartialOrd for PathElement<'a> { + fn partial_cmp(&self, other: &Self) -> Option { + 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()) @@ -51,6 +108,18 @@ impl<'a> From for PathElement<'a> { } } +impl<'a> Serialize for PathElement<'a> { + fn serialize(&self, serializer: S) -> Result + 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}; diff --git a/serde_json_path_core/src/spec/query.rs b/serde_json_path_core/src/spec/query.rs index 9a71fae..cd4a8e3 100644 --- a/serde_json_path_core/src/spec/query.rs +++ b/serde_json_path_core/src/spec/query.rs @@ -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 @@ -111,7 +104,7 @@ impl Queryable for Query { query } - fn query_paths<'b>( + fn query_located<'b>( &self, current: &'b Value, root: &'b Value, @@ -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; } diff --git a/serde_json_path_core/src/spec/segment.rs b/serde_json_path_core/src/spec/segment.rs index f35d81f..8c49a9f 100644 --- a/serde_json_path_core/src/spec/segment.rs +++ b/serde_json_path_core/src/spec/segment.rs @@ -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) } } } @@ -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 @@ -209,7 +209,7 @@ impl Queryable for Segment { query } - fn query_paths<'b>( + fn query_located<'b>( &self, current: &'b Value, root: &'b Value, @@ -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) => { diff --git a/serde_json_path_core/src/spec/selector/filter.rs b/serde_json_path_core/src/spec/selector/filter.rs index cb5e30e..4c8555e 100644 --- a/serde_json_path_core/src/spec/selector/filter.rs +++ b/serde_json_path_core/src/spec/selector/filter.rs @@ -71,7 +71,7 @@ impl Queryable for Filter { } } - fn query_paths<'b>( + fn query_located<'b>( &self, current: &'b Value, root: &'b Value, diff --git a/serde_json_path_core/src/spec/selector/index.rs b/serde_json_path_core/src/spec/selector/index.rs index 55d5a4a..98b0036 100644 --- a/serde_json_path_core/src/spec/selector/index.rs +++ b/serde_json_path_core/src/spec/selector/index.rs @@ -39,7 +39,7 @@ impl Queryable for Index { } } - fn query_paths<'b>( + fn query_located<'b>( &self, current: &'b Value, _root: &'b Value, diff --git a/serde_json_path_core/src/spec/selector/mod.rs b/serde_json_path_core/src/spec/selector/mod.rs index 1c0dac3..596a47b 100644 --- a/serde_json_path_core/src/spec/selector/mod.rs +++ b/serde_json_path_core/src/spec/selector/mod.rs @@ -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() @@ -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), } } } diff --git a/serde_json_path_core/src/spec/selector/name.rs b/serde_json_path_core/src/spec/selector/name.rs index b47dc3c..1837d72 100644 --- a/serde_json_path_core/src/spec/selector/name.rs +++ b/serde_json_path_core/src/spec/selector/name.rs @@ -30,7 +30,7 @@ impl Queryable for Name { } } - fn query_paths<'b>( + fn query_located<'b>( &self, current: &'b Value, _root: &'b Value, diff --git a/serde_json_path_core/src/spec/selector/slice.rs b/serde_json_path_core/src/spec/selector/slice.rs index 1e44bfb..f7c5fc8 100644 --- a/serde_json_path_core/src/spec/selector/slice.rs +++ b/serde_json_path_core/src/spec/selector/slice.rs @@ -128,7 +128,7 @@ impl Queryable for Slice { } } - fn query_paths<'b>( + fn query_located<'b>( &self, current: &'b Value, _root: &'b Value,