Skip to content

Commit

Permalink
docs: add docs to LocatedNodeList and relatives
Browse files Browse the repository at this point in the history
  • Loading branch information
hiltontj committed Jan 30, 2024
1 parent 13a80ec commit d6ca84d
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 11 deletions.
13 changes: 12 additions & 1 deletion serde_json_path/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,9 +328,20 @@ pub use error::ParseError;
pub use ext::JsonPathExt;
#[doc(inline)]
pub use path::JsonPath;
/// A list of nodes resulting from a JSONPath query, along with their locations
///
/// This is produced by the [`JsonPath::query_located`] method.
///
/// As with [`NodeList`], each node is a borrowed reference to the node in the original
/// [`serde_json::Value`] that was queried; however, each node in the list is paired with its
/// location represented by a [`NormalizedPath`].
///
/// In addition to the locations, [`LocatedNodeList`] provides useful functionality over [`NodeList`]
/// such as de-duplication of query results (see [`dedup`][LocatedNodeList::dedup]).
pub use serde_json_path_core::node::LocatedNodeList;
#[doc(inline)]
pub use serde_json_path_core::node::{
AtMostOneError, ExactlyOneError, LocatedNode, LocatedNodeList, NodeList,
AtMostOneError, ExactlyOneError, LocatedNode, Locations, NodeList, Nodes,
};
#[doc(inline)]
pub use serde_json_path_core::path::NormalizedPath;
Expand Down
130 changes: 120 additions & 10 deletions serde_json_path_core/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ impl<'a> NodeList<'a> {
}
}

/// Extract all nodes yielded by the query.
/// Extract all nodes yielded by the query
///
/// This is intended for queries that are expected to yield zero or more nodes.
///
Expand All @@ -107,7 +107,7 @@ impl<'a> NodeList<'a> {
self.0.len()
}

/// Check if a [NodeList] is empty
/// Check if a [`NodeList`] is empty
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
Expand Down Expand Up @@ -227,27 +227,27 @@ impl<'a> IntoIterator for NodeList<'a> {
}
}

/// A node within a JSON value, along with its location
#[derive(Debug, Eq, PartialEq, Serialize, Clone)]
pub struct LocatedNode<'a> {
pub(crate) loc: NormalizedPath<'a>,
pub(crate) node: &'a Value,
}

impl<'a> LocatedNode<'a> {
/// Get the location of the node as a [`NormalizedPath`]
pub fn location(&self) -> &NormalizedPath<'a> {
&self.loc
}

/// Get the node itself
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`].
// This is documented in the serde_json_path crate, for linking purposes
#[allow(missing_docs)]
#[derive(Debug, Default, Eq, PartialEq, Serialize, Clone)]
pub struct LocatedNodeList<'a>(Vec<LocatedNode<'a>>);

Expand All @@ -261,12 +261,15 @@ impl<'a> LocatedNodeList<'a> {
/// # use serde_json::json;
/// # use serde_json_path::JsonPath;
/// # use serde_json_path::AtMostOneError;
/// # fn main() -> Result<(), serde_json_path::ParseError> {
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// 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());
/// let Some(node) = path.query_located(&value).at_most_one()? else {
/// /* ... */
/// # unreachable!("query should not be empty");
/// };
/// assert_eq!("$['foo'][0]", node.location().to_string());
/// # }
/// # Ok(())
/// # }
Expand All @@ -281,6 +284,25 @@ impl<'a> LocatedNodeList<'a> {
}
}

/// Extract _exactly_ one entry from a [`LocatedNodeList`]
///
/// This is intended for queries that are expected to yield a single node.
///
/// # Usage
/// ```rust
/// # use serde_json::json;
/// # use serde_json_path::JsonPath;
/// # use serde_json_path::ExactlyOneError;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let value = json!({"foo": ["bar", "baz"]});
/// # {
/// let path = JsonPath::parse("$.foo[? @ == 'bar']")?;
/// let node = path.query_located(&value).exactly_one()?;
/// assert_eq!("$['foo'][0]", node.location().to_string());
/// # }
/// # Ok(())
/// # }
/// ```
pub fn exactly_one(mut self) -> Result<LocatedNode<'a>, ExactlyOneError> {
if self.0.is_empty() {
Err(ExactlyOneError::Empty)
Expand All @@ -291,35 +313,117 @@ impl<'a> LocatedNodeList<'a> {
}
}

/// Extract all located nodes yielded by the query
///
/// This is intended for queries that are expected to yield zero or more nodes.
///
/// # Usage
/// ```rust
/// # use serde_json::json;
/// # use serde_json_path::JsonPath;
/// # fn main() -> Result<(), serde_json_path::ParseError> {
/// let value = json!({"foo": ["bar", "baz"]});
/// let path = JsonPath::parse("$.foo.*")?;
/// let nodes = path.query_located(&value).all();
/// assert_eq!(nodes[0].location().to_string(), "$['foo'][0]");
/// assert_eq!(nodes[0].node(), "bar");
/// assert_eq!(nodes[1].location().to_string(), "$['foo'][1]");
/// assert_eq!(nodes[1].node(), "baz");
/// # Ok(())
/// # }
/// ```
pub fn all(self) -> Vec<LocatedNode<'a>> {
self.0
}

/// Get the length of a [`LocatedNodeList`]
pub fn len(&self) -> usize {
self.0.len()
}

/// Check if a [`LocatedNodeList`] is empty
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}

/// Get an iterator over a [`LocatedNodeList`]
///
/// Note that [`LocatedNodeList`] also implements [`IntoIterator`].
///
/// To iterate over just locations, see [`locations`][LocatedNodeList::locations]. To iterate
/// over just nodes, see [`nodes`][LocatedNodeList::nodes].
pub fn iter(&self) -> Iter<'_, LocatedNode<'a>> {
self.0.iter()
}

/// Get an iterator over the locations of nodes within a [`LocatedNodeList`]
///
/// # Usage
/// ```rust
/// # use serde_json::json;
/// # use serde_json_path::JsonPath;
/// # fn main() -> Result<(), serde_json_path::ParseError> {
/// let value = json!({"foo": ["bar", "baz"]});
/// let path = JsonPath::parse("$.foo.*")?;
/// let locations: Vec<String> = path
/// .query_located(&value)
/// .locations()
/// .map(|loc| loc.to_string())
/// .collect();
/// assert_eq!(locations, ["$['foo'][0]", "$['foo'][1]"]);
/// # Ok(())
/// # }
/// ```
pub fn locations(&self) -> Locations<'_> {
Locations { inner: self.iter() }
}

/// Get an iterator over the nodes within a [`LocatedNodeList`]
pub fn nodes(&self) -> Nodes<'_> {
Nodes { inner: self.iter() }
}

/// Deduplicate a [`LocatedNodeList`] and return the result
///
/// See also, [`dedup_in_place`][LocatedNodeList::dedup_in_place].
///
/// # Usage
/// ```rust
/// # use serde_json::json;
/// # use serde_json_path::JsonPath;
/// # fn main() -> Result<(), serde_json_path::ParseError> {
/// let value = json!({"foo": ["bar", "baz"]});
/// let path = JsonPath::parse("$.foo[0, 0, 1, 1]")?;
/// let nodes = path.query_located(&value);
/// assert_eq!(4, nodes.len());
/// let nodes = path.query_located(&value).dedup();
/// assert_eq!(2, nodes.len());
/// # Ok(())
/// # }
/// ```
pub fn dedup(mut self) -> Self {
self.dedup_in_place();
self
}

/// Deduplicate a [`LocatedNodeList`] _in-place_
///
/// See also, [`dedup`][LocatedNodeList::dedup].
///
/// # Usage
/// ```rust
/// # use serde_json::json;
/// # use serde_json_path::JsonPath;
/// # fn main() -> Result<(), serde_json_path::ParseError> {
/// let value = json!({"foo": ["bar", "baz"]});
/// let path = JsonPath::parse("$.foo[0, 0, 1, 1]")?;
/// let mut nodes = path.query_located(&value);
/// assert_eq!(4, nodes.len());
/// nodes.dedup_in_place();
/// assert_eq!(2, nodes.len());
/// # Ok(())
/// # }
/// ```
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.
Expand All @@ -345,6 +449,9 @@ impl<'a> IntoIterator for LocatedNodeList<'a> {
}
}

/// An iterator over the locations in a [`LocatedNodeList`]
///
/// Produced by the [`LocatedNodeList::locations`] method.
#[derive(Debug)]
pub struct Locations<'a> {
inner: Iter<'a, LocatedNode<'a>>,
Expand Down Expand Up @@ -372,6 +479,9 @@ impl<'a> ExactSizeIterator for Locations<'a> {

impl<'a> FusedIterator for Locations<'a> {}

/// An iterator over the nodes in a [`LocatedNodeList`]
///
/// Produced by the [`LocatedNodeList::nodes`] method.
#[derive(Debug)]
pub struct Nodes<'a> {
inner: Iter<'a, LocatedNode<'a>>,
Expand Down
3 changes: 3 additions & 0 deletions serde_json_path_core/src/path.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//! Types for representing [Normalized Paths][norm-paths] from the JSONPath specification
//!
//! [norm-paths]: https://www.ietf.org/archive/id/draft-ietf-jsonpath-base-21.html#name-normalized-paths
use std::{cmp::Ordering, fmt::Display, slice::Iter};

use serde::Serialize;
Expand Down

0 comments on commit d6ca84d

Please sign in to comment.