From 53135af185aeb936c1e7193b6bec4da3ebc58c0f Mon Sep 17 00:00:00 2001 From: Trevor Hilton Date: Sun, 28 Jan 2024 22:52:39 -0500 Subject: [PATCH] feat: add LocatedNode type The LocatedNode type represents a node, i.e., &Value, as well as its location within the original Value that was queried. Defining a dedicated type for this was done to replace the use of the (NormalizedPath, &Value) every- where, and allows for addition of convenience methods. This also ensures controlled construction of LocatedNodeList, which ensures that their deduplication can be done safely, i.e., the unwrap is safe. This commit also started adding in the missing docs for newly created items in this branch. --- serde_json_path/src/lib.rs | 6 +- serde_json_path/src/path.rs | 31 ++++++-- serde_json_path_core/src/lib.rs | 1 + serde_json_path_core/src/node.rs | 78 ++++++++++++++----- serde_json_path_core/src/{spec => }/path.rs | 0 serde_json_path_core/src/spec/mod.rs | 1 - serde_json_path_core/src/spec/query.rs | 26 ++++--- serde_json_path_core/src/spec/segment.rs | 25 ++++-- .../src/spec/selector/filter.rs | 23 ++++-- .../src/spec/selector/index.rs | 8 +- serde_json_path_core/src/spec/selector/mod.rs | 23 +++--- .../src/spec/selector/name.rs | 10 +-- .../src/spec/selector/slice.rs | 18 +++-- 13 files changed, 170 insertions(+), 80 deletions(-) rename serde_json_path_core/src/{spec => }/path.rs (100%) diff --git a/serde_json_path/src/lib.rs b/serde_json_path/src/lib.rs index e44eb40..c820cf2 100644 --- a/serde_json_path/src/lib.rs +++ b/serde_json_path/src/lib.rs @@ -329,7 +329,11 @@ pub use ext::JsonPathExt; #[doc(inline)] pub use path::JsonPath; #[doc(inline)] -pub use serde_json_path_core::node::{AtMostOneError, ExactlyOneError, NodeList}; +pub use serde_json_path_core::node::{ + AtMostOneError, ExactlyOneError, LocatedNode, LocatedNodeList, NodeList, +}; +#[doc(inline)] +pub use serde_json_path_core::path::NormalizedPath; pub use serde_json_path_core::spec::functions; diff --git a/serde_json_path/src/path.rs b/serde_json_path/src/path.rs index bb3832d..041b944 100644 --- a/serde_json_path/src/path.rs +++ b/serde_json_path/src/path.rs @@ -4,10 +4,7 @@ use serde::{de::Visitor, Deserialize, Serialize}; use serde_json::Value; use serde_json_path_core::{ node::{LocatedNodeList, NodeList}, - spec::{ - path::NormalizedPath, - query::{Query, Queryable}, - }, + spec::query::{Query, Queryable}, }; use crate::{parser::parse_query_main, ParseError}; @@ -68,8 +65,8 @@ impl JsonPath { /// # use serde_json::json; /// # use serde_json_path::JsonPath; /// # fn main() -> Result<(), serde_json_path::ParseError> { - /// let path = JsonPath::parse("$.foo[::2]")?; /// let value = json!({"foo": [1, 2, 3, 4]}); + /// let path = JsonPath::parse("$.foo[::2]")?; /// let nodes = path.query(&value); /// assert_eq!(nodes.all(), vec![1, 3]); /// # Ok(()) @@ -79,6 +76,26 @@ impl JsonPath { self.0.query(value, value).into() } + /// Query a [`serde_json::Value`] using this [`JsonPath`] to produce a [`LocatedNodeList`] + /// + /// # Example + /// ```rust + /// # use serde_json::{json, Value}; + /// # use serde_json_path::{JsonPath,NormalizedPath}; + /// # fn main() -> Result<(), serde_json_path::ParseError> { + /// let value = json!({"foo": [1, 2, 3, 4]}); + /// let path = JsonPath::parse("$.foo[2:]")?; + /// let query = path.query_located(&value); + /// let nodes: Vec<&Value> = query.nodes().collect(); + /// assert_eq!(nodes, vec![3, 4]); + /// let locs: Vec = query + /// .locations() + /// .map(|loc| loc.to_string()) + /// .collect(); + /// assert_eq!(locs, ["$['foo'][2]", "$['foo'][3]"]); + /// # Ok(()) + /// # } + /// ``` pub fn query_located<'b>(&self, value: &'b Value) -> LocatedNodeList<'b> { self.0 .query_located(value, value, Default::default()) @@ -170,8 +187,8 @@ mod tests { }}); let p = JsonPath::parse("$.foo.bar.*").unwrap(); let r = p.query_located(&j); - for (np, _) in r { - println!("{pointer}", pointer = np.as_json_pointer()); + for ln in r { + println!("{pointer}", pointer = ln.location().as_json_pointer()); } } } diff --git a/serde_json_path_core/src/lib.rs b/serde_json_path_core/src/lib.rs index 0ecdd65..52cc683 100644 --- a/serde_json_path_core/src/lib.rs +++ b/serde_json_path_core/src/lib.rs @@ -41,4 +41,5 @@ #![forbid(unsafe_code)] pub mod node; +pub mod path; pub mod spec; diff --git a/serde_json_path_core/src/node.rs b/serde_json_path_core/src/node.rs index 5ce4fb3..e5af808 100644 --- a/serde_json_path_core/src/node.rs +++ b/serde_json_path_core/src/node.rs @@ -4,7 +4,7 @@ use std::{iter::FusedIterator, slice::Iter}; use serde::Serialize; use serde_json::Value; -use crate::spec::path::NormalizedPath; +use crate::path::NormalizedPath; /// A list of nodes resulting from a JSONPath query /// @@ -227,13 +227,51 @@ impl<'a> IntoIterator for NodeList<'a> { } } +#[derive(Debug, Eq, PartialEq, Serialize, Clone)] +pub struct LocatedNode<'a> { + pub(crate) loc: NormalizedPath<'a>, + pub(crate) node: &'a Value, +} + +impl<'a> LocatedNode<'a> { + pub fn location(&self) -> &NormalizedPath<'a> { + &self.loc + } + + pub fn node(&self) -> &'a Value { + self.node + } +} + +/// A list of nodes resulting from a JSONPath query, along with their locations +/// +/// As with [`NodeList`], each node is a borrowed reference to the node in the original +/// [`serde_json::Value`] that was queried. Each node in the list is paired with its location +/// represented by a [`NormalizedPath`]. #[derive(Debug, Default, Eq, PartialEq, Serialize, Clone)] -pub struct LocatedNodeList<'a>(Vec<(NormalizedPath<'a>, &'a Value)>); +pub struct LocatedNodeList<'a>(Vec>); impl<'a> LocatedNodeList<'a> { - pub fn at_most_one( - mut self, - ) -> Result, &'a Value)>, AtMostOneError> { + /// Extract _at most_ one entry from a [`LocatedNodeList`] + /// + /// This is intended for queries that are expected to optionally yield a single node. + /// + /// # Usage + /// ```rust + /// # use serde_json::json; + /// # use serde_json_path::JsonPath; + /// # use serde_json_path::AtMostOneError; + /// # fn main() -> Result<(), serde_json_path::ParseError> { + /// let value = json!({"foo": ["bar", "baz"]}); + /// # { + /// let path = JsonPath::parse("$.foo[0]")?; + /// let node = path.query_located(&value).at_most_one().unwrap(); + /// assert_eq!("$['foo'][0]", node.unwrap().location().to_string()); + /// # } + /// # Ok(()) + /// # } + /// ``` + pub fn at_most_one(mut self) -> Result>, AtMostOneError> { if self.0.is_empty() { Ok(None) } else if self.0.len() > 1 { @@ -243,7 +281,7 @@ impl<'a> LocatedNodeList<'a> { } } - pub fn exactly_one(mut self) -> Result<(NormalizedPath<'a>, &'a Value), ExactlyOneError> { + pub fn exactly_one(mut self) -> Result, ExactlyOneError> { if self.0.is_empty() { Err(ExactlyOneError::Empty) } else if self.0.len() > 1 { @@ -253,7 +291,7 @@ impl<'a> LocatedNodeList<'a> { } } - pub fn all(self) -> Vec<(NormalizedPath<'a>, &'a Value)> { + pub fn all(self) -> Vec> { self.0 } @@ -265,7 +303,7 @@ impl<'a> LocatedNodeList<'a> { self.0.is_empty() } - pub fn iter(&self) -> Iter<'_, (NormalizedPath<'a>, &'a Value)> { + pub fn iter(&self) -> Iter<'_, LocatedNode<'a>> { self.0.iter() } @@ -285,22 +323,20 @@ impl<'a> LocatedNodeList<'a> { pub fn dedup_in_place(&mut self) { // This unwrap should be safe, since the paths corresponding to // a query against a Value will always be ordered. - // - // TODO - the below From impl may allow someone to violate this self.0 - .sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + .sort_unstable_by(|a, b| a.loc.partial_cmp(&b.loc).unwrap()); self.0.dedup(); } } -impl<'a> From, &'a Value)>> for LocatedNodeList<'a> { - fn from(v: Vec<(NormalizedPath<'a>, &'a Value)>) -> Self { +impl<'a> From>> for LocatedNodeList<'a> { + fn from(v: Vec>) -> Self { Self(v) } } impl<'a> IntoIterator for LocatedNodeList<'a> { - type Item = (NormalizedPath<'a>, &'a Value); + type Item = LocatedNode<'a>; type IntoIter = std::vec::IntoIter; @@ -309,21 +345,22 @@ impl<'a> IntoIterator for LocatedNodeList<'a> { } } +#[derive(Debug)] pub struct Locations<'a> { - inner: Iter<'a, (NormalizedPath<'a>, &'a Value)>, + inner: Iter<'a, LocatedNode<'a>>, } impl<'a> Iterator for Locations<'a> { type Item = &'a NormalizedPath<'a>; fn next(&mut self) -> Option { - self.inner.next().map(|(np, _)| np) + self.inner.next().map(|l| l.location()) } } impl<'a> DoubleEndedIterator for Locations<'a> { fn next_back(&mut self) -> Option { - self.inner.next_back().map(|(np, _)| np) + self.inner.next_back().map(|l| l.location()) } } @@ -335,21 +372,22 @@ impl<'a> ExactSizeIterator for Locations<'a> { impl<'a> FusedIterator for Locations<'a> {} +#[derive(Debug)] pub struct Nodes<'a> { - inner: Iter<'a, (NormalizedPath<'a>, &'a Value)>, + inner: Iter<'a, LocatedNode<'a>>, } impl<'a> Iterator for Nodes<'a> { type Item = &'a Value; fn next(&mut self) -> Option { - self.inner.next().map(|(_, n)| *n) + self.inner.next().map(|l| l.node()) } } impl<'a> DoubleEndedIterator for Nodes<'a> { fn next_back(&mut self) -> Option { - self.inner.next_back().map(|(_, n)| *n) + self.inner.next_back().map(|l| l.node()) } } diff --git a/serde_json_path_core/src/spec/path.rs b/serde_json_path_core/src/path.rs similarity index 100% rename from serde_json_path_core/src/spec/path.rs rename to serde_json_path_core/src/path.rs diff --git a/serde_json_path_core/src/spec/mod.rs b/serde_json_path_core/src/spec/mod.rs index 2dc84e1..585baf5 100644 --- a/serde_json_path_core/src/spec/mod.rs +++ b/serde_json_path_core/src/spec/mod.rs @@ -1,6 +1,5 @@ //! Types representing the IETF JSONPath Standard pub mod functions; -pub mod path; pub mod query; pub mod segment; pub mod selector; diff --git a/serde_json_path_core/src/spec/query.rs b/serde_json_path_core/src/spec/query.rs index cd4a8e3..6aa9ff1 100644 --- a/serde_json_path_core/src/spec/query.rs +++ b/serde_json_path_core/src/spec/query.rs @@ -1,7 +1,9 @@ //! Types representing queries in JSONPath use serde_json::Value; -use super::{path::NormalizedPath, segment::QuerySegment}; +use crate::{node::LocatedNode, path::NormalizedPath}; + +use super::segment::QuerySegment; mod sealed { use crate::spec::{ @@ -38,7 +40,7 @@ pub trait Queryable: sealed::Sealed { current: &'b Value, root: &'b Value, parent: NormalizedPath<'b>, - ) -> Vec<(NormalizedPath<'b>, &'b Value)>; + ) -> Vec>; } /// Represents a JSONPath expression @@ -108,16 +110,22 @@ impl Queryable for Query { &self, current: &'b Value, root: &'b Value, - _parent: NormalizedPath<'b>, - ) -> Vec<(NormalizedPath<'b>, &'b Value)> { - let mut result: Vec<(NormalizedPath<'b>, &Value)> = match self.kind { - QueryKind::Root => vec![(Default::default(), root)], - QueryKind::Current => vec![(Default::default(), current)], + parent: NormalizedPath<'b>, + ) -> Vec> { + let mut result: Vec> = match self.kind { + QueryKind::Root => vec![LocatedNode { + loc: Default::default(), + node: root, + }], + QueryKind::Current => vec![LocatedNode { + loc: parent, + node: current, + }], }; for s in &self.segments { let mut r = vec![]; - for (ref np, v) in result { - r.append(&mut s.query_located(v, root, np.clone())); + for LocatedNode { loc, node } in result { + r.append(&mut s.query_located(node, root, loc.clone())); } result = r; } diff --git a/serde_json_path_core/src/spec/segment.rs b/serde_json_path_core/src/spec/segment.rs index 8c49a9f..beb220b 100644 --- a/serde_json_path_core/src/spec/segment.rs +++ b/serde_json_path_core/src/spec/segment.rs @@ -1,7 +1,9 @@ //! Types representing segments in JSONPath use serde_json::Value; -use super::{path::NormalizedPath, query::Queryable, selector::Selector}; +use crate::{node::LocatedNode, path::NormalizedPath}; + +use super::{query::Queryable, selector::Selector}; /// A segment of a JSONPath query #[derive(Debug, PartialEq, Eq, Clone)] @@ -61,7 +63,7 @@ impl Queryable for QuerySegment { current: &'b Value, root: &'b Value, parent: NormalizedPath<'b>, - ) -> Vec<(NormalizedPath<'b>, &'b Value)> { + ) -> Vec> { if matches!(self.kind, QuerySegmentKind::Descendant) { let mut result = self.segment.query_located(current, root, parent.clone()); result.append(&mut descend_paths(self, current, root, parent)); @@ -92,7 +94,7 @@ fn descend_paths<'b>( current: &'b Value, root: &'b Value, parent: NormalizedPath<'b>, -) -> Vec<(NormalizedPath<'b>, &'b Value)> { +) -> Vec> { let mut result = Vec::new(); if let Some(list) = current.as_array() { for (i, v) in list.iter().enumerate() { @@ -214,7 +216,7 @@ impl Queryable for Segment { current: &'b Value, root: &'b Value, mut parent: NormalizedPath<'b>, - ) -> Vec<(NormalizedPath<'b>, &'b Value)> { + ) -> Vec> { let mut result = vec![]; match self { Segment::LongHand(selectors) => { @@ -225,17 +227,26 @@ impl Queryable for Segment { Segment::DotName(name) => { if let Some((k, v)) = current.as_object().and_then(|o| o.get_key_value(name)) { parent.push(k); - result.push((parent, v)); + result.push(LocatedNode { + loc: parent, + node: v, + }); } } Segment::Wildcard => { if let Some(list) = current.as_array() { for (i, v) in list.iter().enumerate() { - result.push((parent.clone_and_push(i), v)); + result.push(LocatedNode { + loc: parent.clone_and_push(i), + node: v, + }); } } else if let Some(obj) = current.as_object() { for (k, v) in obj { - result.push((parent.clone_and_push(k), v)); + result.push(LocatedNode { + loc: parent.clone_and_push(k), + node: v, + }); } } } diff --git a/serde_json_path_core/src/spec/selector/filter.rs b/serde_json_path_core/src/spec/selector/filter.rs index 4c8555e..d63e8a2 100644 --- a/serde_json_path_core/src/spec/selector/filter.rs +++ b/serde_json_path_core/src/spec/selector/filter.rs @@ -1,11 +1,14 @@ //! Types representing filter selectors in JSONPath use serde_json::{Number, Value}; -use crate::spec::{ - functions::{FunctionExpr, JsonPathValue, Validated}, +use crate::{ + node::LocatedNode, path::NormalizedPath, - query::{Query, QueryKind, Queryable}, - segment::{QuerySegment, Segment}, + spec::{ + functions::{FunctionExpr, JsonPathValue, Validated}, + query::{Query, QueryKind, Queryable}, + segment::{QuerySegment, Segment}, + }, }; use super::{index::Index, name::Name, Selector}; @@ -76,17 +79,23 @@ impl Queryable for Filter { current: &'b Value, root: &'b Value, parent: NormalizedPath<'b>, - ) -> Vec<(NormalizedPath<'b>, &'b Value)> { + ) -> Vec> { if let Some(list) = current.as_array() { list.iter() .enumerate() .filter(|(_, v)| self.0.test_filter(v, root)) - .map(|(i, v)| (parent.clone_and_push(i), v)) + .map(|(i, v)| LocatedNode { + loc: parent.clone_and_push(i), + node: v, + }) .collect() } else if let Some(obj) = current.as_object() { obj.iter() .filter(|(_, v)| self.0.test_filter(v, root)) - .map(|(k, v)| (parent.clone_and_push(k), v)) + .map(|(k, v)| LocatedNode { + loc: parent.clone_and_push(k), + node: v, + }) .collect() } else { vec![] diff --git a/serde_json_path_core/src/spec/selector/index.rs b/serde_json_path_core/src/spec/selector/index.rs index 98b0036..66e3fc7 100644 --- a/serde_json_path_core/src/spec/selector/index.rs +++ b/serde_json_path_core/src/spec/selector/index.rs @@ -1,7 +1,7 @@ //! Index selectors in JSONPath use serde_json::Value; -use crate::spec::{path::NormalizedPath, query::Queryable}; +use crate::{node::LocatedNode, path::NormalizedPath, spec::query::Queryable}; /// For selecting array elements by their index /// @@ -44,8 +44,8 @@ impl Queryable for Index { current: &'b Value, _root: &'b Value, mut parent: NormalizedPath<'b>, - ) -> Vec<(NormalizedPath<'b>, &'b Value)> { - if let Some((index, value)) = current.as_array().and_then(|list| { + ) -> Vec> { + if let Some((index, node)) = current.as_array().and_then(|list| { if self.0 < 0 { self.0 .checked_abs() @@ -59,7 +59,7 @@ impl Queryable for Index { } }) { parent.push(index); - vec![(parent, value)] + vec![LocatedNode { loc: parent, node }] } else { vec![] } diff --git a/serde_json_path_core/src/spec/selector/mod.rs b/serde_json_path_core/src/spec/selector/mod.rs index 596a47b..2ec51de 100644 --- a/serde_json_path_core/src/spec/selector/mod.rs +++ b/serde_json_path_core/src/spec/selector/mod.rs @@ -6,12 +6,11 @@ pub mod slice; use serde_json::Value; +use crate::{node::LocatedNode, path::NormalizedPath}; + use self::{filter::Filter, index::Index, name::Name, slice::Slice}; -use super::{ - path::{NormalizedPath, PathElement}, - query::Queryable, -}; +use super::query::Queryable; /// A JSONPath selector #[derive(Debug, PartialEq, Eq, Clone)] @@ -79,25 +78,23 @@ impl Queryable for Selector { current: &'b Value, root: &'b Value, parent: NormalizedPath<'b>, - ) -> Vec<(NormalizedPath<'b>, &'b Value)> { + ) -> Vec> { match self { Selector::Name(name) => name.query_located(current, root, parent), Selector::Wildcard => { if let Some(list) = current.as_array() { list.iter() .enumerate() - .map(|(i, v)| { - let mut new_path = parent.clone(); - new_path.push(PathElement::from(i)); - (new_path, v) + .map(|(i, node)| LocatedNode { + loc: parent.clone_and_push(i), + node, }) .collect() } else if let Some(obj) = current.as_object() { obj.iter() - .map(|(k, v)| { - let mut new_path = parent.clone(); - new_path.push(PathElement::from(k)); - (new_path, v) + .map(|(k, node)| LocatedNode { + loc: parent.clone_and_push(k), + node, }) .collect() } else { diff --git a/serde_json_path_core/src/spec/selector/name.rs b/serde_json_path_core/src/spec/selector/name.rs index 1837d72..758ef66 100644 --- a/serde_json_path_core/src/spec/selector/name.rs +++ b/serde_json_path_core/src/spec/selector/name.rs @@ -1,7 +1,7 @@ //! Name selector for selecting object keys in JSONPath use serde_json::Value; -use crate::spec::{path::NormalizedPath, query::Queryable}; +use crate::{node::LocatedNode, path::NormalizedPath, spec::query::Queryable}; /// Select a single JSON object key #[derive(Debug, PartialEq, Eq, Clone)] @@ -35,10 +35,10 @@ impl Queryable for Name { current: &'b Value, _root: &'b Value, mut parent: NormalizedPath<'b>, - ) -> Vec<(NormalizedPath<'b>, &'b Value)> { - if let Some((s, v)) = current.as_object().and_then(|o| o.get_key_value(&self.0)) { - parent.push(s); - vec![(parent, v)] + ) -> Vec> { + if let Some((name, node)) = current.as_object().and_then(|o| o.get_key_value(&self.0)) { + parent.push(name); + vec![LocatedNode { loc: parent, node }] } else { vec![] } diff --git a/serde_json_path_core/src/spec/selector/slice.rs b/serde_json_path_core/src/spec/selector/slice.rs index f7c5fc8..ddb779f 100644 --- a/serde_json_path_core/src/spec/selector/slice.rs +++ b/serde_json_path_core/src/spec/selector/slice.rs @@ -1,7 +1,7 @@ //! Slice selectors for selecting array slices in JSONPath use serde_json::Value; -use crate::spec::{path::NormalizedPath, query::Queryable}; +use crate::{node::LocatedNode, path::NormalizedPath, spec::query::Queryable}; /// A slice selector #[derive(Debug, PartialEq, Eq, Default, Clone, Copy)] @@ -133,7 +133,7 @@ impl Queryable for Slice { current: &'b Value, _root: &'b Value, parent: NormalizedPath<'b>, - ) -> Vec<(NormalizedPath<'b>, &'b Value)> { + ) -> Vec> { if let Some(list) = current.as_array() { let mut result = Vec::new(); let step = self.step.unwrap_or(1); @@ -147,11 +147,14 @@ impl Queryable for Slice { let (lower, upper) = self.bounds_on_forward_slice(len); let mut i = lower; while i < upper { - if let Some((i, v)) = usize::try_from(i) + if let Some((i, node)) = usize::try_from(i) .ok() .and_then(|i| list.get(i).map(|v| (i, v))) { - result.push((parent.clone_and_push(i), v)); + result.push(LocatedNode { + loc: parent.clone_and_push(i), + node, + }); } i += step; } @@ -161,11 +164,14 @@ impl Queryable for Slice { }; let mut i = upper; while lower < i { - if let Some((i, v)) = usize::try_from(i) + if let Some((i, node)) = usize::try_from(i) .ok() .and_then(|i| list.get(i).map(|v| (i, v))) { - result.push((parent.clone_and_push(i), v)); + result.push(LocatedNode { + loc: parent.clone_and_push(i), + node, + }); } i += step; }