From c01a942f4a7388d61188a2fa141ad7d886a65193 Mon Sep 17 00:00:00 2001 From: Trevor Hilton Date: Thu, 25 Jan 2024 21:44:57 -0500 Subject: [PATCH] feat: a start on normalized path support This commit introduces the NormalizedPath type to represent normalized paths from the JSONPath spec. The Queryable trait includes a query_paths method that is used to produce a list of normalized paths vs. the standard query method, which produces the nodes. A few of the spec types had this implemented in their impl for Queryable, but this is incomplete. --- serde_json_path_core/src/spec/mod.rs | 1 + serde_json_path_core/src/spec/path.rs | 83 +++++++++++++++++++ serde_json_path_core/src/spec/query.rs | 17 +++- serde_json_path_core/src/spec/segment.rs | 18 ++++ .../src/spec/selector/filter.rs | 18 ++++ .../src/spec/selector/index.rs | 27 +++++- serde_json_path_core/src/spec/selector/mod.rs | 41 ++++++++- .../src/spec/selector/name.rs | 16 +++- .../src/spec/selector/slice.rs | 9 ++ 9 files changed, 226 insertions(+), 4 deletions(-) create mode 100644 serde_json_path_core/src/spec/path.rs diff --git a/serde_json_path_core/src/spec/mod.rs b/serde_json_path_core/src/spec/mod.rs index 585baf5..2dc84e1 100644 --- a/serde_json_path_core/src/spec/mod.rs +++ b/serde_json_path_core/src/spec/mod.rs @@ -1,5 +1,6 @@ //! 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/path.rs b/serde_json_path_core/src/spec/path.rs new file mode 100644 index 0000000..40792df --- /dev/null +++ b/serde_json_path_core/src/spec/path.rs @@ -0,0 +1,83 @@ +use std::ops::{Deref, DerefMut}; + +#[derive(Clone)] +pub struct NormalizedPath<'a>(Vec>); + +impl<'a> NormalizedPath<'a> { + pub fn as_json_pointer(&self) -> String { + self.0 + .iter() + .map(PathElement::as_json_pointer) + .fold(String::from(""), |mut acc, s| { + acc.push('/'); + acc.push_str(&s.replace('~', "~0").replace('/', "~1")); + acc + }) + } +} + +impl<'a> Deref for NormalizedPath<'a> { + type Target = Vec>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> DerefMut for NormalizedPath<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[derive(Clone, Copy)] +pub enum PathElement<'a> { + Name(&'a str), + Index(usize), +} + +impl<'a> PathElement<'a> { + fn as_json_pointer(&self) -> String { + match self { + PathElement::Name(ref s) => format!("{s}"), + PathElement::Index(i) => format!("{i}"), + } + } +} + +impl<'a> From<&'a String> for PathElement<'a> { + fn from(s: &'a String) -> Self { + Self::Name(s.as_str()) + } +} + +impl<'a> From for PathElement<'a> { + fn from(index: usize) -> Self { + Self::Index(index) + } +} + +#[cfg(test)] +mod tests { + use super::{NormalizedPath, PathElement}; + + #[test] + fn normalized_path_to_json_pointer() { + let np = NormalizedPath(vec![ + PathElement::Name("foo"), + PathElement::Index(42), + PathElement::Name("bar"), + ]); + assert_eq!(np.as_json_pointer(), "/foo/42/bar",); + } + + #[test] + fn normalized_path_to_json_pointer_with_escapes() { + let np = NormalizedPath(vec![ + PathElement::Name("foo~bar"), + PathElement::Index(42), + PathElement::Name("baz/bop"), + ]); + assert_eq!(np.as_json_pointer(), "/foo~0bar/42/baz~1bop",); + } +} diff --git a/serde_json_path_core/src/spec/query.rs b/serde_json_path_core/src/spec/query.rs index 5ee171a..c3e73c2 100644 --- a/serde_json_path_core/src/spec/query.rs +++ b/serde_json_path_core/src/spec/query.rs @@ -1,7 +1,7 @@ //! Types representing queries in JSONPath use serde_json::Value; -use super::segment::QuerySegment; +use super::{path::NormalizedPath, segment::QuerySegment}; mod sealed { use crate::spec::{ @@ -33,6 +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>( + &self, + current: &'b Value, + _root: &'b Value, + parent: NormalizedPath<'b>, + ) -> Vec>; } /// Represents a JSONPath expression @@ -97,4 +103,13 @@ impl Queryable for Query { } query } + + fn query_paths<'b>( + &self, + current: &'b Value, + _root: &'b Value, + parent: NormalizedPath<'b>, + ) -> Vec> { + todo!() + } } diff --git a/serde_json_path_core/src/spec/segment.rs b/serde_json_path_core/src/spec/segment.rs index 5ce1b0b..5671c7b 100644 --- a/serde_json_path_core/src/spec/segment.rs +++ b/serde_json_path_core/src/spec/segment.rs @@ -55,6 +55,15 @@ impl Queryable for QuerySegment { } query } + + fn query_paths<'b>( + &self, + current: &'b Value, + _root: &'b Value, + parent: super::path::NormalizedPath<'b>, + ) -> Vec> { + todo!() + } } #[cfg_attr(feature = "trace", tracing::instrument(name = "Descend", level = "trace", parent = None, ret))] @@ -174,4 +183,13 @@ impl Queryable for Segment { } query } + + fn query_paths<'b>( + &self, + current: &'b Value, + _root: &'b Value, + parent: super::path::NormalizedPath<'b>, + ) -> Vec> { + todo!() + } } diff --git a/serde_json_path_core/src/spec/selector/filter.rs b/serde_json_path_core/src/spec/selector/filter.rs index 563a535..bf1394f 100644 --- a/serde_json_path_core/src/spec/selector/filter.rs +++ b/serde_json_path_core/src/spec/selector/filter.rs @@ -69,6 +69,15 @@ impl Queryable for Filter { vec![] } } + + fn query_paths<'b>( + &self, + current: &'b Value, + _root: &'b Value, + parent: crate::spec::path::NormalizedPath<'b>, + ) -> Vec> { + todo!() + } } /// The top level boolean expression type @@ -565,6 +574,15 @@ impl Queryable for SingularQuery { None => vec![], } } + + fn query_paths<'b>( + &self, + current: &'b Value, + _root: &'b Value, + parent: crate::spec::path::NormalizedPath<'b>, + ) -> Vec> { + todo!() + } } impl std::fmt::Display for SingularQuery { diff --git a/serde_json_path_core/src/spec/selector/index.rs b/serde_json_path_core/src/spec/selector/index.rs index ab369df..3a2c6a6 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::query::Queryable; +use crate::spec::{path::NormalizedPath, query::Queryable}; /// For selecting array elements by their index /// @@ -38,6 +38,31 @@ impl Queryable for Index { vec![] } } + + fn query_paths<'b>( + &self, + current: &'b Value, + _root: &'b Value, + mut parent: NormalizedPath<'b>, + ) -> Vec> { + if let Some(index) = current.as_array().and_then(|list| { + if self.0 < 0 { + self.0 + .checked_abs() + .and_then(|i| usize::try_from(i).ok()) + .and_then(|i| list.len().checked_sub(i)) + } else { + usize::try_from(self.0) + .ok() + .and_then(|i| (list.len() >= i).then_some(i)) + } + }) { + parent.push(index.into()); + vec![parent] + } else { + vec![] + } + } } impl From for Index { diff --git a/serde_json_path_core/src/spec/selector/mod.rs b/serde_json_path_core/src/spec/selector/mod.rs index b6bf82e..a88f7b1 100644 --- a/serde_json_path_core/src/spec/selector/mod.rs +++ b/serde_json_path_core/src/spec/selector/mod.rs @@ -8,7 +8,10 @@ use serde_json::Value; use self::{filter::Filter, index::Index, name::Name, slice::Slice}; -use super::query::Queryable; +use super::{ + path::{NormalizedPath, PathElement}, + query::Queryable, +}; /// A JSONPath selector #[derive(Debug, PartialEq, Eq, Clone)] @@ -70,4 +73,40 @@ impl Queryable for Selector { } query } + + fn query_paths<'b>( + &self, + current: &'b Value, + root: &'b Value, + parent: NormalizedPath<'b>, + ) -> Vec> { + match self { + Selector::Name(name) => name.query_paths(current, root, parent), + Selector::Wildcard => { + if let Some(list) = current.as_array() { + list.iter() + .enumerate() + .map(|(i, _)| { + let mut new_path = parent.clone(); + new_path.push(PathElement::from(i)); + new_path + }) + .collect() + } else if let Some(obj) = current.as_object() { + obj.keys() + .map(|k| { + let mut new_path = parent.clone(); + new_path.push(PathElement::from(k)); + new_path + }) + .collect() + } else { + 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), + } + } } diff --git a/serde_json_path_core/src/spec/selector/name.rs b/serde_json_path_core/src/spec/selector/name.rs index de2c350..0f116b0 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::query::Queryable; +use crate::spec::{path::NormalizedPath, query::Queryable}; /// Select a single JSON object key #[derive(Debug, PartialEq, Eq, Clone)] @@ -29,6 +29,20 @@ impl Queryable for Name { vec![] } } + + fn query_paths<'b>( + &self, + current: &'b Value, + _root: &'b Value, + mut parent: NormalizedPath<'b>, + ) -> Vec> { + if let Some((s, _)) = current.as_object().and_then(|o| o.get_key_value(&self.0)) { + parent.push(s.into()); + vec![parent] + } else { + vec![] + } + } } impl From<&str> for Name { diff --git a/serde_json_path_core/src/spec/selector/slice.rs b/serde_json_path_core/src/spec/selector/slice.rs index ffe1140..551f14d 100644 --- a/serde_json_path_core/src/spec/selector/slice.rs +++ b/serde_json_path_core/src/spec/selector/slice.rs @@ -118,6 +118,15 @@ impl Queryable for Slice { vec![] } } + + fn query_paths<'b>( + &self, + current: &'b Value, + _root: &'b Value, + parent: crate::spec::path::NormalizedPath<'b>, + ) -> Vec> { + todo!() + } } fn normalize_slice_index(index: isize, len: isize) -> Option {