serde_json_path_core-0.2.2/.cargo_vcs_info.json0000644000000001620000000000100152130ustar { "git": { "sha1": "66283b5e2fa9d099439882c6afba395d5de7b9f3" }, "path_in_vcs": "serde_json_path_core" }serde_json_path_core-0.2.2/CHANGELOG.md000064400000000000000000000114001046102023000156110ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). # Unreleased # 0.2.1 (3 November 2024) - **internal**: update `serde_json` to the latest version ([#107]) - **breaking**: ensure integers used as indices are within the [valid range for I-JSON][i-json-range] ([#98]) - **internal**: remove use of `once_cell` and use specific versions for crate dependencies ([#105]) [#98]: https://github.com/hiltontj/serde_json_path/pull/98 [i-json-range]: https://www.rfc-editor.org/rfc/rfc9535.html#section-2.1-4.1 [#105]: https://github.com/hiltontj/serde_json_path/pull/105 [#107]: https://github.com/hiltontj/serde_json_path/pull/107 # 0.1.6 (3 March 2024) - **testing**: support tests for non-determinism in compliance test suite ([#85]) - **fixed**: bug preventing registered functions from being used as arguments to other functions ([#84]) [#85]: https://github.com/hiltontj/serde_json_path/pull/85 [#84]: https://github.com/hiltontj/serde_json_path/pull/84 # 0.1.5 (23 February 2024) - **docs**: update links to refer to RFC 9535 ([#81]) [#81]: https://github.com/hiltontj/serde_json_path/pull/81 # 0.1.4 (2 February 2024) ## Added: `NormalizedPath` and `PathElement` types ([#78]) The `NormalizedPath` struct represents the location of a node within a JSON object. Its representation is like so: ```rust pub struct NormalizedPath<'a>(Vec); pub enum PathElement<'a> { Name(&'a str), Index(usize), } ``` Several methods were included to interact with a `NormalizedPath`, e.g., `first`, `last`, `get`, `iter`, etc., but notably there is a `to_json_pointer` method, which allows direct conversion to a JSON Pointer to be used with the [`serde_json::Value::pointer`][pointer] or [`serde_json::Value::pointer_mut`][pointer-mut] methods. [pointer]: https://docs.rs/serde_json/latest/serde_json/enum.Value.html#method.pointer [pointer-mut]: https://docs.rs/serde_json/latest/serde_json/enum.Value.html#method.pointer_mut The new `PathElement` type also comes equipped with several methods, and both it and `NormalizedPath` have eagerly implemented traits from the standard library / `serde` to help improve interoperability. ## Added: `LocatedNodeList` and `LocatedNode` types ([#78]) The `LocatedNodeList` struct was built to have a similar API surface to the `NodeList` struct, but includes additional methods that give access to the location of each node produced by the original query. For example, it has the `locations` and `nodes` methods to provide dedicated iterators over locations or nodes, respectively, but also provides the `iter` method to iterate over the location/node pairs. Here is an example: ```rust use serde_json::{json, Value}; use serde_json_path::JsonPath; let value = json!({"foo": {"bar": 1, "baz": 2}}); let path = JsonPath::parse("$.foo.*")?; let query = path.query_located(&value); let nodes: Vec<&Value> = query.nodes().collect(); assert_eq!(nodes, vec![1, 2]); let locs: Vec = query .locations() .map(|loc| loc.to_string()) .collect(); assert_eq!(locs, ["$['foo']['bar']", "$['foo']['baz']"]); ``` The location/node pairs are represented by the `LocatedNode` type. The `LocatedNodeList` provides one unique bit of functionality over `NodeList`: deduplication of the query results, via the `LocatedNodeList::dedup` and `LocatedNodeList::dedup_in_place` methods. [#78]: https://github.com/hiltontj/serde_json_path/pull/78 ## Other Changes - **internal**: address new clippy lints in Rust 1.75 ([#75]) - **internal**: address new clippy lints in Rust 1.74 and update some tracing instrumentation ([#70]) - **internal**: code clean-up ([#72]) [#70]: https://github.com/hiltontj/serde_json_path/pull/70 [#72]: https://github.com/hiltontj/serde_json_path/pull/72 [#75]: https://github.com/hiltontj/serde_json_path/pull/75 # 0.1.3 (9 November 2023) - **added**: `is_empty`, `is_more_than_one`, and `as_more_than_one` methods to `ExactlyOneError` ([#65]) - **fixed**: ensure that the check `== -0` in filters works as expected ([#67]) [#65]: https://github.com/hiltontj/serde_json_path/pull/65 [#67]: https://github.com/hiltontj/serde_json_path/pull/67 # 0.1.2 (17 September 2023) - **documentation**: Improvements to documentation ([#56]) [#56]: https://github.com/hiltontj/serde_json_path/pull/56 # 0.1.1 (13 July 2023) * **fixed**: Fixed an issue in the evaluation of `SingularQuery`s that was producing false positive query results when relative singular queries, e.g., `@.bar`, were being used as comparables in a filter, e.g., `$.foo[?(@.bar == 'baz')]` ([#50]) [#50]: https://github.com/hiltontj/serde_json_path/pull/50 # 0.1.0 (2 April 2023) Initial Release serde_json_path_core-0.2.2/Cargo.lock0000644000000106430000000000100131730ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "inventory" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b12ebb6799019b044deaf431eadfe23245b259bba5a2c0796acec3943a3cdb" dependencies = [ "rustversion", ] [[package]] name = "itoa" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "proc-macro2" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "rustversion" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "serde" version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_json_path_core" version = "0.2.2" dependencies = [ "inventory", "serde", "serde_json", "thiserror", "tracing", ] [[package]] name = "syn" version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tracing-core" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", ] [[package]] name = "unicode-ident" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" serde_json_path_core-0.2.2/Cargo.toml0000644000000025160000000000100132160ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "serde_json_path_core" version = "0.2.2" authors = ["Trevor Hilton "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Core types for the serde_json_path crate" readme = "README.md" keywords = [ "json", "jsonpath", "json_path", "serde", "serde_json", ] license = "MIT" repository = "https://github.com/hiltontj/serde_json_path" [lib] name = "serde_json_path_core" path = "src/lib.rs" [dependencies.inventory] version = "0.3.19" [dependencies.serde] version = "1.0.217" features = ["derive"] [dependencies.serde_json] version = "1.0.138" [dependencies.thiserror] version = "2.0.11" [dependencies.tracing] version = "0.1.40" optional = true [dev-dependencies] [features] default = ["functions"] functions = [] trace = ["dep:tracing"] serde_json_path_core-0.2.2/Cargo.toml.orig000064400000000000000000000012761046102023000167010ustar 00000000000000[package] name = "serde_json_path_core" version = "0.2.2" edition = "2021" license = "MIT" authors = ["Trevor Hilton "] description = "Core types for the serde_json_path crate" repository = "https://github.com/hiltontj/serde_json_path" readme = "README.md" keywords = ["json", "jsonpath", "json_path", "serde", "serde_json"] [lib] [features] default = ["functions"] trace = ["dep:tracing"] functions = [] [dependencies] # crates.io crates: inventory.workspace = true serde.workspace = true serde_json.workspace = true thiserror.workspace = true [dependencies.tracing] workspace = true optional = true [dev-dependencies] serde_json_path = { path = "../serde_json_path" } serde_json_path_core-0.2.2/LICENSE-MIT000064400000000000000000000020561046102023000154430ustar 00000000000000MIT License Copyright (c) 2023 Trevor Hilton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. serde_json_path_core-0.2.2/README.md000064400000000000000000000001741046102023000152650ustar 00000000000000# serde_json_path_core Core types for the [`serde_json_path`][sjp] crate. [sjp]: https://crates.io/crates/serde_json_path serde_json_path_core-0.2.2/src/lib.rs000064400000000000000000000022701046102023000157100ustar 00000000000000//! Core types for [`serde_json_path`] //! //! [`serde_json_path`]: https://crates.io/crates/serde_json_path #![warn( clippy::all, clippy::dbg_macro, clippy::todo, clippy::empty_enum, clippy::enum_glob_use, clippy::mem_forget, clippy::unused_self, clippy::filter_map_next, clippy::needless_continue, clippy::needless_borrow, clippy::match_wildcard_for_single_variants, clippy::if_let_mutex, unexpected_cfgs, clippy::await_holding_lock, clippy::match_on_vec_items, clippy::imprecise_flops, clippy::suboptimal_flops, clippy::lossy_float_literal, clippy::rest_pat_in_fully_bound_structs, clippy::fn_params_excessive_bools, clippy::exit, clippy::inefficient_to_string, clippy::linkedlist, clippy::macro_use_imports, clippy::option_option, clippy::verbose_file_reads, clippy::unnested_or_patterns, clippy::str_to_string, rust_2018_idioms, future_incompatible, nonstandard_style, missing_debug_implementations, missing_docs )] #![deny(unreachable_pub)] #![allow(elided_lifetimes_in_paths, clippy::type_complexity)] #![forbid(unsafe_code)] pub mod node; pub mod path; pub mod spec; serde_json_path_core-0.2.2/src/node.rs000064400000000000000000000474311046102023000160770ustar 00000000000000//! Types representing nodes within a JSON object use std::{iter::FusedIterator, slice::Iter}; use serde::Serialize; use serde_json::Value; use crate::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 /// [`serde_json::Value`] that was queried. #[derive(Debug, Default, Eq, PartialEq, Serialize, Clone)] pub struct NodeList<'a>(pub(crate) Vec<&'a Value>); impl<'a> NodeList<'a> { /// Extract _at most_ one node from a [`NodeList`] /// /// 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(&value).at_most_one().unwrap(); /// assert_eq!(node, Some(&json!("bar"))); /// # } /// # { /// let path = JsonPath::parse("$.foo.*")?; /// let error = path.query(&value).at_most_one().unwrap_err(); /// assert!(matches!(error, AtMostOneError(2))); /// # } /// # Ok(()) /// # } /// ``` pub fn at_most_one(&self) -> Result, AtMostOneError> { if self.0.is_empty() { Ok(None) } else if self.0.len() > 1 { Err(AtMostOneError(self.0.len())) } else { Ok(self.0.first().copied()) } } /// Extract _exactly_ one node from a [`NodeList`] /// /// This is intended for queries that are expected to yield exactly one node. /// /// # Usage /// ```rust /// # use serde_json::json; /// # use serde_json_path::JsonPath; /// # use serde_json_path::ExactlyOneError; /// # fn main() -> Result<(), serde_json_path::ParseError> { /// let value = json!({"foo": ["bar", "baz"]}); /// # { /// let path = JsonPath::parse("$.foo[0]")?; /// let node = path.query(&value).exactly_one().unwrap(); /// assert_eq!(node, "bar"); /// # } /// # { /// let path = JsonPath::parse("$.foo.*")?; /// let error = path.query(&value).exactly_one().unwrap_err(); /// assert!(matches!(error, ExactlyOneError::MoreThanOne(2))); /// # } /// # Ok(()) /// # } /// ``` pub fn exactly_one(&self) -> Result<&'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.first().unwrap()) } } /// Extract all 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(&value).all(); /// assert_eq!(nodes, vec!["bar", "baz"]); /// # Ok(()) /// # } /// ``` pub fn all(self) -> Vec<&'a Value> { self.0 } /// Get the length of a [`NodeList`] pub fn len(&self) -> usize { self.0.len() } /// Check if a [`NodeList`] is empty pub fn is_empty(&self) -> bool { self.0.is_empty() } /// Get an iterator over a [`NodeList`] /// /// Note that [`NodeList`] also implements [`IntoIterator`]. pub fn iter(&self) -> Iter<'_, &Value> { self.0.iter() } /// Returns the first node in the [`NodeList`], or `None` if it is empty /// /// # 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 node = path.query(&value).first(); /// assert_eq!(node, Some(&json!("bar"))); /// # Ok(()) /// # } /// ``` pub fn first(&self) -> Option<&'a Value> { self.0.first().copied() } /// Returns the last node in the [`NodeList`], or `None` if it is empty /// /// # 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 node = path.query(&value).last(); /// assert_eq!(node, Some(&json!("baz"))); /// # Ok(()) /// # } /// ``` pub fn last(&self) -> Option<&'a Value> { self.0.last().copied() } /// Returns the node at the given index in the [`NodeList`], or `None` if the given index is /// out of bounds. /// /// # Usage /// ```rust /// # use serde_json::json; /// # use serde_json_path::JsonPath; /// # fn main() -> Result<(), serde_json_path::ParseError> { /// let value = json!({"foo": ["bar", "biz", "bop"]}); /// let path = JsonPath::parse("$.foo.*")?; /// let nodes = path.query(&value); /// assert_eq!(nodes.get(1), Some(&json!("biz"))); /// assert!(nodes.get(4).is_none()); /// # Ok(()) /// # } /// ``` pub fn get(&self, index: usize) -> Option<&'a Value> { self.0.get(index).copied() } /// Extract _at most_ one node from a [`NodeList`] /// /// 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; /// # fn main() -> Result<(), serde_json_path::ParseError> { /// let value = json!({"foo": ["bar", "baz"]}); /// # { /// let path = JsonPath::parse("$.foo[0]")?; /// let node = path.query(&value).one(); /// assert_eq!(node, Some(&json!("bar"))); /// # } /// # { /// let path = JsonPath::parse("$.foo.*")?; /// let node = path.query(&value).one(); /// assert!(node.is_none()); /// # } /// # Ok(()) /// # } /// ``` #[deprecated( since = "0.5.1", note = "it is recommended to use `at_most_one`, `exactly_one`, `first`, `last`, or `get` instead" )] pub fn one(self) -> Option<&'a Value> { if self.0.is_empty() || self.0.len() > 1 { None } else { self.0.first().copied() } } } impl<'a> From> for NodeList<'a> { fn from(nodes: Vec<&'a Value>) -> Self { Self(nodes) } } impl<'a> IntoIterator for NodeList<'a> { type Item = &'a Value; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } /// 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 } /// Take the location of the node as a [`NormalizedPath`] pub fn to_location(self) -> NormalizedPath<'a> { self.loc } /// Get the node itself pub fn node(&self) -> &'a Value { self.node } } impl<'a> From> for NormalizedPath<'a> { fn from(node: LocatedNode<'a>) -> Self { node.to_location() } } #[allow(missing_docs)] #[derive(Debug, Default, Eq, PartialEq, Serialize, Clone)] pub struct LocatedNodeList<'a>(Vec>); impl<'a> LocatedNodeList<'a> { /// 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; /// # fn main() -> Result<(), Box> { /// let value = json!({"foo": ["bar", "baz"]}); /// # { /// let path = JsonPath::parse("$.foo[0]")?; /// 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(()) /// # } /// ``` pub fn at_most_one(mut self) -> Result>, 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())) } } /// 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; /// # fn main() -> Result<(), Box> { /// 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, 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()) } } /// 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> { 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]. /// /// # Example /// ```rust /// # use serde_json::{json, Value}; /// # use serde_json_path::JsonPath; /// # fn main() -> Result<(), serde_json_path::ParseError> { /// let value = json!({"foo": ["bar", "baz"]}); /// let path = JsonPath::parse("$.foo.*")?; /// let pairs: Vec<(String, &Value)> = path /// .query_located(&value) /// .iter() /// .map(|q| (q.location().to_string(), q.node())) /// .collect(); /// assert_eq!(pairs[0], ("$['foo'][0]".to_owned(), &json!("bar"))); /// assert_eq!(pairs[1], ("$['foo'][1]".to_owned(), &json!("baz"))); /// # Ok(()) /// # } /// ``` 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 = 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. self.0 .sort_unstable_by(|a, b| a.loc.partial_cmp(&b.loc).unwrap()); self.0.dedup(); } /// Return the first entry in the [`LocatedNodeList`], or `None` if it is empty /// /// # Usage /// ```rust /// # use serde_json::json; /// # use serde_json_path::JsonPath; /// # use serde_json_path::LocatedNode; /// # fn main() -> Result<(), serde_json_path::ParseError> { /// let value = json!({"foo": ["bar", "baz"]}); /// let path = JsonPath::parse("$.foo.*")?; /// let nodes = path.query_located(&value); /// let first = nodes.first().map(LocatedNode::node); /// assert_eq!(first, Some(&json!("bar"))); /// # Ok(()) /// # } /// ``` pub fn first(&self) -> Option<&LocatedNode<'a>> { self.0.first() } /// Return the last entry in the [`LocatedNodeList`], or `None` if it is empty /// /// # Usage /// ```rust /// # use serde_json::json; /// # use serde_json_path::JsonPath; /// # use serde_json_path::LocatedNode; /// # fn main() -> Result<(), serde_json_path::ParseError> { /// let value = json!({"foo": ["bar", "baz"]}); /// let path = JsonPath::parse("$.foo.*")?; /// let nodes = path.query_located(&value); /// let last = nodes.last().map(LocatedNode::node); /// assert_eq!(last, Some(&json!("baz"))); /// # Ok(()) /// # } /// ``` pub fn last(&self) -> Option<&LocatedNode<'a>> { self.0.last() } /// Returns the node at the given index in the [`LocatedNodeList`], or `None` if the /// given index is out of bounds. /// /// # Usage /// ```rust /// # use serde_json::json; /// # use serde_json_path::JsonPath; /// # use serde_json_path::LocatedNode; /// # fn main() -> Result<(), serde_json_path::ParseError> { /// let value = json!({"foo": ["bar", "biz", "bop"]}); /// let path = JsonPath::parse("$.foo.*")?; /// let nodes = path.query_located(&value); /// assert_eq!(nodes.get(1).map(LocatedNode::node), Some(&json!("biz"))); /// assert!(nodes.get(4).is_none()); /// # Ok(()) /// # } /// ``` pub fn get(&self, index: usize) -> Option<&LocatedNode<'a>> { self.0.get(index) } } impl<'a> From>> for LocatedNodeList<'a> { fn from(v: Vec>) -> Self { Self(v) } } impl<'a> IntoIterator for LocatedNodeList<'a> { type Item = LocatedNode<'a>; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } /// 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>>, } impl<'a> Iterator for Locations<'a> { type Item = &'a NormalizedPath<'a>; fn next(&mut self) -> Option { self.inner.next().map(|l| l.location()) } } impl DoubleEndedIterator for Locations<'_> { fn next_back(&mut self) -> Option { self.inner.next_back().map(|l| l.location()) } } impl ExactSizeIterator for Locations<'_> { fn len(&self) -> usize { self.inner.len() } } impl FusedIterator for Locations<'_> {} /// 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>>, } impl<'a> Iterator for Nodes<'a> { type Item = &'a Value; fn next(&mut self) -> Option { self.inner.next().map(|l| l.node()) } } impl DoubleEndedIterator for Nodes<'_> { fn next_back(&mut self) -> Option { self.inner.next_back().map(|l| l.node()) } } impl ExactSizeIterator for Nodes<'_> { fn len(&self) -> usize { self.inner.len() } } impl FusedIterator for Nodes<'_> {} /// Error produced when expecting no more than one node from a query #[derive(Debug, thiserror::Error)] #[error("nodelist expected to contain at most one entry, but instead contains {0} entries")] pub struct AtMostOneError(pub usize); /// Error produced when expecting exactly one node from a query #[derive(Debug, thiserror::Error)] pub enum ExactlyOneError { /// The query resulted in an empty [`NodeList`] #[error("nodelist expected to contain one entry, but is empty")] Empty, /// The query resulted in a [`NodeList`] containing more than one node #[error("nodelist expected to contain one entry, but instead contains {0} entries")] MoreThanOne(usize), } impl ExactlyOneError { /// Check that it is the `Empty` variant pub fn is_empty(&self) -> bool { matches!(self, Self::Empty) } /// Check that it is the `MoreThanOne` variant pub fn is_more_than_one(&self) -> bool { self.as_more_than_one().is_some() } /// Extract the number of nodes, if it was more than one, or `None` otherwise pub fn as_more_than_one(&self) -> Option { match self { ExactlyOneError::Empty => None, ExactlyOneError::MoreThanOne(u) => Some(*u), } } } #[cfg(test)] mod tests { use crate::node::{LocatedNodeList, NodeList}; use serde_json::{json, to_value}; use serde_json_path::JsonPath; #[test] fn test_send() { fn assert_send() {} assert_send::(); assert_send::(); } #[test] fn test_sync() { fn assert_sync() {} assert_sync::(); assert_sync::(); } #[test] fn test_serialize() { let v = json!([1, 2, 3, 4]); let q = JsonPath::parse("$.*").expect("valid query").query(&v); assert_eq!(to_value(q).expect("serialize"), v); } } serde_json_path_core-0.2.2/src/path.rs000064400000000000000000000307551046102023000161070ustar 00000000000000//! Types for representing [Normalized Paths][norm-paths] from the JSONPath specification //! //! [norm-paths]: https://www.rfc-editor.org/rfc/rfc9535.html#name-normalized-paths use std::{ cmp::Ordering, fmt::Display, slice::{Iter, SliceIndex}, }; use serde::Serialize; // Documented in the serde_json_path crate, for linking purposes #[allow(missing_docs)] #[derive(Debug, Default, Eq, PartialEq, Clone, PartialOrd)] pub struct NormalizedPath<'a>(Vec>); impl<'a> NormalizedPath<'a> { pub(crate) fn push>>(&mut self, elem: T) { self.0.push(elem.into()) } pub(crate) fn clone_and_push>>(&self, elem: T) -> Self { let mut new_path = self.clone(); new_path.push(elem.into()); new_path } /// Get the [`NormalizedPath`] as a [JSON Pointer][json-pointer] string /// /// This can be used with the [`serde_json::Value::pointer`] or /// [`serde_json::Value::pointer_mut`] methods. /// /// # Example /// ```rust /// # use serde_json::json; /// # use serde_json_path::JsonPath; /// # fn main() -> Result<(), Box> { /// let mut value = json!({"foo": ["bar", "baz"]}); /// let path = JsonPath::parse("$.foo[? @ == 'bar']")?; /// let pointer= path /// .query_located(&value) /// .exactly_one()? /// .location() /// .to_json_pointer(); /// *value.pointer_mut(&pointer).unwrap() = "bop".into(); /// assert_eq!(value, json!({"foo": ["bop", "baz"]})); /// # Ok(()) /// # } /// ``` /// /// [json-pointer]: https://datatracker.ietf.org/doc/html/rfc6901 pub fn to_json_pointer(&self) -> String { self.0 .iter() .map(PathElement::to_json_pointer) .fold(String::from(""), |mut acc, s| { acc.push('/'); acc.push_str(&s); acc }) } /// Check if the [`NormalizedPath`] is empty /// /// An empty normalized path represents the location of the root node of the JSON object, /// i.e., `$`. pub fn is_empty(&self) -> bool { self.0.is_empty() } /// Get the length of the [`NormalizedPath`] pub fn len(&self) -> usize { self.0.len() } /// Get an iterator over the [`PathElement`]s of the [`NormalizedPath`] /// /// Note that [`NormalizedPath`] also implements [`IntoIterator`] /// /// # Example /// ```rust /// # use serde_json::json; /// # use serde_json_path::JsonPath; /// # fn main() -> Result<(), Box> { /// let mut value = json!({"foo": {"bar": 1, "baz": 2, "bop": 3}}); /// let path = JsonPath::parse("$.foo[? @ == 2]")?; /// let location = path.query_located(&value).exactly_one()?.to_location(); /// let elements: Vec = location /// .iter() /// .map(|ele| ele.to_string()) /// .collect(); /// assert_eq!(elements, ["foo", "baz"]); /// # Ok(()) /// # } /// ``` pub fn iter(&self) -> Iter<'_, PathElement<'a>> { self.0.iter() } /// Get the [`PathElement`] at `index`, or `None` if the index is out of bounds /// /// # Example /// ```rust /// # use serde_json::json; /// # use serde_json_path::JsonPath; /// # fn main() -> Result<(), Box> { /// let value = json!({"foo": {"bar": {"baz": "bop"}}}); /// let path = JsonPath::parse("$..baz")?; /// let location = path.query_located(&value).exactly_one()?.to_location(); /// assert_eq!(location.to_string(), "$['foo']['bar']['baz']"); /// assert!(location.get(0).is_some_and(|p| p == "foo")); /// assert!(location.get(1..).is_some_and(|p| p == ["bar", "baz"])); /// assert!(location.get(3).is_none()); /// # Ok(()) /// # } /// ``` pub fn get(&self, index: I) -> Option<&I::Output> where I: SliceIndex<[PathElement<'a>]>, { self.0.get(index) } /// Get the first [`PathElement`], or `None` if the path is empty /// /// # Example /// ```rust /// # use serde_json::json; /// # use serde_json_path::JsonPath; /// # fn main() -> Result<(), Box> { /// let value = json!(["foo", true, {"bar": false}, {"bar": true}]); /// let path = JsonPath::parse("$..[? @ == false]")?; /// let location = path.query_located(&value).exactly_one()?.to_location(); /// assert_eq!(location.to_string(), "$[2]['bar']"); /// assert!(location.first().is_some_and(|p| *p == 2)); /// # Ok(()) /// # } /// ``` pub fn first(&self) -> Option<&PathElement<'a>> { self.0.first() } /// Get the last [`PathElement`], or `None` if the path is empty /// /// # Example /// ```rust /// # use serde_json::json; /// # use serde_json_path::JsonPath; /// # fn main() -> Result<(), Box> { /// let value = json!({"foo": {"bar": [1, 2, 3]}}); /// let path = JsonPath::parse("$..[? @ == 2]")?; /// let location = path.query_located(&value).exactly_one()?.to_location(); /// assert_eq!(location.to_string(), "$['foo']['bar'][1]"); /// assert!(location.last().is_some_and(|p| *p == 1)); /// # Ok(()) /// # } /// ``` pub fn last(&self) -> Option<&PathElement<'a>> { self.0.last() } } impl<'a> IntoIterator for NormalizedPath<'a> { type Item = PathElement<'a>; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl Display for NormalizedPath<'_> { /// Format the [`NormalizedPath`] as a JSONPath string using the canonical bracket notation /// as per the [JSONPath Specification][norm-paths] /// /// # Example /// ```rust /// # use serde_json::json; /// # use serde_json_path::JsonPath; /// # fn main() -> Result<(), Box> { /// let value = json!({"foo": ["bar", "baz"]}); /// let path = JsonPath::parse("$.foo[0]")?; /// let location = path.query_located(&value).exactly_one()?.to_location(); /// assert_eq!(location.to_string(), "$['foo'][0]"); /// # Ok(()) /// # } /// ``` /// /// [norm-paths]: https://www.rfc-editor.org/rfc/rfc9535.html#name-normalized-paths 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 Serialize for NormalizedPath<'_> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_str(self.to_string().as_str()) } } /// An element within a [`NormalizedPath`] #[derive(Debug, Eq, PartialEq, Clone)] pub enum PathElement<'a> { /// A key within a JSON object Name(&'a str), /// An index of a JSON Array Index(usize), } impl PathElement<'_> { fn to_json_pointer(&self) -> String { match self { PathElement::Name(s) => s.replace('~', "~0").replace('/', "~1"), PathElement::Index(i) => i.to_string(), } } /// Get the underlying name if the [`PathElement`] is `Name`, or `None` otherwise pub fn as_name(&self) -> Option<&str> { match self { PathElement::Name(n) => Some(n), PathElement::Index(_) => None, } } /// Get the underlying index if the [`PathElement`] is `Index`, or `None` otherwise pub fn as_index(&self) -> Option { match self { PathElement::Name(_) => None, PathElement::Index(i) => Some(*i), } } /// Test if the [`PathElement`] is `Name` pub fn is_name(&self) -> bool { self.as_name().is_some() } /// Test if the [`PathElement`] is `Index` pub fn is_index(&self) -> bool { self.as_index().is_some() } } impl PartialOrd for PathElement<'_> { 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 PartialEq for PathElement<'_> { fn eq(&self, other: &str) -> bool { match self { PathElement::Name(s) => s.eq(&other), PathElement::Index(_) => false, } } } impl PartialEq<&str> for PathElement<'_> { fn eq(&self, other: &&str) -> bool { match self { PathElement::Name(s) => s.eq(other), PathElement::Index(_) => false, } } } impl PartialEq for PathElement<'_> { fn eq(&self, other: &usize) -> bool { match self { PathElement::Name(_) => false, PathElement::Index(i) => i.eq(other), } } } impl Display for PathElement<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { PathElement::Name(n) => { // https://www.rfc-editor.org/rfc/rfc9535#section-2.7 for c in n.chars() { match c { '\u{0008}' => write!(f, r#"\b"#)?, // b BS backspace '\u{000C}' => write!(f, r#"\f"#)?, // f FF form feed '\u{000A}' => write!(f, r#"\n"#)?, // n LF line feed '\u{000D}' => write!(f, r#"\r"#)?, // r CR carriage return '\u{0009}' => write!(f, r#"\t"#)?, // t HT horizontal tab '\u{0027}' => write!(f, r#"\'"#)?, // ' apostrophe '\u{005C}' => write!(f, r#"\"#)?, // \ backslash (reverse solidus) ('\x00'..='\x07') | '\x0b' | '\x0e' | '\x0f' => { // "00"-"07", "0b", "0e"-"0f" write!(f, "\\u000{:x}", c as i32)? } _ => write!(f, "{c}")?, } } Ok(()) } 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()) } } impl From for PathElement<'_> { fn from(index: usize) -> Self { Self::Index(index) } } impl Serialize for PathElement<'_> { 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}; #[test] fn normalized_path_to_json_pointer() { let np = NormalizedPath(vec![ PathElement::Name("foo"), PathElement::Index(42), PathElement::Name("bar"), ]); assert_eq!(np.to_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.to_json_pointer(), "/foo~0bar/42/baz~1bop"); } #[test] fn normalized_element_fmt() { for (name, elem, exp) in [ ("simple name", PathElement::Name("foo"), "foo"), ("index", PathElement::Index(1), "1"), ("escape_apostrophes", PathElement::Name("'hi'"), r#"\'hi\'"#), ( "escapes", PathElement::Name(r#"'\b\f\n\r\t\\'"#), r#"\'\b\f\n\r\t\\\'"#, ), ( "escape_vertical_unicode", PathElement::Name("\u{000B}"), r#"\u000b"#, ), ( "escape_unicode_null", PathElement::Name("\u{0000}"), r#"\u0000"#, ), ( "escape_unicode_runes", PathElement::Name( "\u{0001}\u{0002}\u{0003}\u{0004}\u{0005}\u{0006}\u{0007}\u{000e}\u{000F}", ), r#"\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u000e\u000f"#, ), ] { assert_eq!(exp, elem.to_string(), "{name}"); } } } serde_json_path_core-0.2.2/src/spec/functions.rs000064400000000000000000000541271046102023000201140ustar 00000000000000//! Function Extensions in JSONPath //! //! [Function Extensions][func-ext] in JSONPath serve as a way to extend the capability of queries in a way that //! the standard query syntax can not support. There are various functions included in JSONPath, all //! of which conform to a specified type system. //! //! [func-ext]: https://www.rfc-editor.org/rfc/rfc9535.html#name-function-extensions //! //! # The JSONPath Type System //! //! The type system used in JSONPath function extensions is comprised of three types: [`NodesType`], //! [`ValueType`], and [`LogicalType`]. All functions use some combination of these types as their //! arguments, and use one of these types as their return type. //! //! # Registered Functions //! //! The IETF JSONPath Specification defines several functions for use in JSONPath query filter //! expressions, all of which are provided for use in `serde_json_path`, and defined below. //! //! ## `length` //! //! The `length` function extension provides a way to compute the length of a value and make that //! available for further processing in the filter expression. //! //! ### Parameters //! //! | Type | Description | //! |------|-------------| //! | [`ValueType`] | string, object, or array, possibly taken from a singular query | //! //! ### Result //! //! | Type | Description | //! |------|-------------| //! | [`ValueType`] | unsigned integer, or nothing | //! //! ### Example //! //! ```text //! $[?length(@.authors) >= 5] //! ``` //! //! ## `count` //! //! The `count` function extension provides a way to obtain the number of nodes in a nodelist and //! make that available for further processing in the filter expression. //! //! ### Parameters //! //! | Type | Description | //! |------|-------------| //! | [`NodesType`] | the nodelist whose members are being counted | //! //! ### Result //! //! | Type | Description | //! |------|-------------| //! | [`ValueType`] | an unsigned integer | //! //! ### Example //! //! ```text //! $[?count(@.*.author) >= 5] //! ``` //! //! ## `match` //! //! The `match` function extension provides a way to check whether **the entirety** of a given //! string matches a given regular expression. //! //! ### Parameters //! //! | Type | Description | //! |------|-------------| //! | [`ValueType`] | a string | //! | [`ValueType`] | a string representing a valid regular expression | //! //! ### Result //! //! | Type | Description | //! |------|-------------| //! | [`LogicalType`] | true for a match, false otherwise | //! //! ### Example //! //! ```text //! $[?match(@.date, "1974-05-..")] //! ``` //! //! ## `search` //! //! The `search` function extension provides a way to check whether a given string contains a //! substring that matches a given regular expression. //! //! ### Parameters //! //! | Type | Description | //! |------|-------------| //! | [`ValueType`] | a string | //! | [`ValueType`] | a string representing a valid regular expression | //! //! ### Result //! //! | Type | Description | //! |------|-------------| //! | [`LogicalType`] | true for a match, false otherwise | //! //! ### Example //! //! ```text //! $[?search(@.author, "[BR]ob")] //! ``` //! //! ## `value` //! //! The `value` function extension provides a way to convert an instance of `NodesType` to a value //! and make that available for further processing in the filter expression. //! //! ### Parameters //! //! | Type | Description | //! |------|-------------| //! | [`NodesType`] | a nodelist to convert to a value | //! //! ### Result //! //! | Type | Description | //! |------|-------------| //! | [`ValueType`] | if the input nodelist contains a single node, the result is the value of that node, otherwise it is nothing | //! //! ### Example //! //! ```text //! $[?value(@..color) == "red"] //! ``` //! use std::{ collections::VecDeque, ops::{Deref, DerefMut}, sync::LazyLock, }; use serde_json::Value; use crate::{node::NodeList, spec::query::Queryable}; use super::{ query::Query, selector::filter::{Literal, LogicalOrExpr, SingularQuery, TestFilter}, }; #[doc(hidden)] pub type Validator = LazyLock Result<(), FunctionValidationError> + Send + Sync>>; #[doc(hidden)] pub type Evaluator = LazyLock Fn(VecDeque>) -> JsonPathValue<'a> + Sync + Send>>; #[doc(hidden)] #[allow(missing_debug_implementations)] pub struct Function { pub name: &'static str, pub result_type: FunctionArgType, pub validator: &'static Validator, pub evaluator: &'static Evaluator, } impl Function { pub const fn new( name: &'static str, result_type: FunctionArgType, evaluator: &'static Evaluator, validator: &'static Validator, ) -> Self { Self { name, result_type, evaluator, validator, } } } #[cfg(feature = "functions")] inventory::collect!(Function); /// JSONPath type representing a Nodelist /// /// This is a thin wrapper around a [`NodeList`], and generally represents the result of a JSONPath /// query. It may also be produced by a function. #[derive(Debug, Default, PartialEq, Clone)] pub struct NodesType<'a>(NodeList<'a>); impl<'a> NodesType<'a> { #[doc(hidden)] pub const fn json_path_type() -> JsonPathType { JsonPathType::Nodes } #[doc(hidden)] pub const fn function_type() -> FunctionArgType { FunctionArgType::Nodelist } /// Extract all inner nodes as a vector /// /// Uses the [`NodeList::all`][NodeList::all] method. pub fn all(self) -> Vec<&'a Value> { self.0.all() } } impl<'a> IntoIterator for NodesType<'a> { type Item = &'a Value; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl<'a> Deref for NodesType<'a> { type Target = NodeList<'a>; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for NodesType<'_> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl<'a> From> for NodesType<'a> { fn from(value: NodeList<'a>) -> Self { Self(value) } } impl<'a> From> for NodesType<'a> { fn from(values: Vec<&'a Value>) -> Self { Self(values.into()) } } impl<'a> TryFrom> for NodesType<'a> { type Error = ConversionError; fn try_from(value: JsonPathValue<'a>) -> Result { match value { JsonPathValue::Nodes(nl) => Ok(nl.into()), JsonPathValue::Value(_) => Err(ConversionError::LiteralToNodes), JsonPathValue::Logical(_) => Err(ConversionError::IncompatibleTypes { from: JsonPathType::Logical, to: JsonPathType::Nodes, }), JsonPathValue::Node(n) => Ok(Self(vec![n].into())), JsonPathValue::Nothing => Ok(Self(vec![].into())), } } } /// JSONPath type representing `LogicalTrue` or `LogicalFalse` #[derive(Debug, Default, Clone, PartialEq, Eq)] pub enum LogicalType { /// True True, /// False #[default] False, } impl LogicalType { #[doc(hidden)] pub const fn json_path_type() -> JsonPathType { JsonPathType::Logical } #[doc(hidden)] pub const fn function_type() -> FunctionArgType { FunctionArgType::Logical } } impl<'a> TryFrom> for LogicalType { type Error = ConversionError; fn try_from(value: JsonPathValue<'a>) -> Result { match value { JsonPathValue::Nodes(nl) => { if nl.is_empty() { Ok(Self::False) } else { Ok(Self::True) } } JsonPathValue::Value(_) => Err(ConversionError::IncompatibleTypes { from: JsonPathType::Value, to: JsonPathType::Logical, }), JsonPathValue::Logical(l) => Ok(l), JsonPathValue::Node(_) => Ok(Self::True), JsonPathValue::Nothing => Ok(Self::False), } } } impl From for bool { fn from(value: LogicalType) -> Self { match value { LogicalType::True => true, LogicalType::False => false, } } } impl From for LogicalType { fn from(value: bool) -> Self { match value { true => Self::True, false => Self::False, } } } /// JSONPath type representing a JSON value or Nothing #[derive(Debug, Default, PartialEq, Eq, Clone)] pub enum ValueType<'a> { /// This may come from a literal value declared in a JSONPath query, or be produced by a /// function. Value(Value), /// This would be a reference to a location in the JSON object being queried, i.e., the result /// of a singular query, or produced by a function. Node(&'a Value), /// This would be the result of a singular query that does not result in any nodes, or be /// produced by a function. #[default] Nothing, } impl ValueType<'_> { #[doc(hidden)] pub const fn json_path_type() -> JsonPathType { JsonPathType::Value } #[doc(hidden)] pub const fn function_type() -> FunctionArgType { FunctionArgType::Value } /// Convert to a reference of a [`serde_json::Value`] if possible pub fn as_value(&self) -> Option<&Value> { match self { ValueType::Value(v) => Some(v), ValueType::Node(v) => Some(v), ValueType::Nothing => None, } } /// Check if this `ValueType` is nothing pub fn is_nothing(&self) -> bool { matches!(self, ValueType::Nothing) } } impl<'a> TryFrom> for ValueType<'a> { type Error = ConversionError; fn try_from(value: JsonPathValue<'a>) -> Result { match value { JsonPathValue::Value(v) => Ok(Self::Value(v)), JsonPathValue::Node(n) => Ok(Self::Node(n)), JsonPathValue::Nothing => Ok(Self::Nothing), JsonPathValue::Nodes(_) => Err(ConversionError::IncompatibleTypes { from: JsonPathType::Nodes, to: JsonPathType::Value, }), JsonPathValue::Logical(_) => Err(ConversionError::IncompatibleTypes { from: JsonPathType::Nodes, to: JsonPathType::Value, }), } } } impl From for ValueType<'_> where T: Into, { fn from(value: T) -> Self { Self::Value(value.into()) } } #[doc(hidden)] #[derive(Debug)] pub enum JsonPathValue<'a> { Nodes(NodeList<'a>), Logical(LogicalType), Node(&'a Value), Value(Value), Nothing, } impl<'a> From> for JsonPathValue<'a> { fn from(value: NodesType<'a>) -> Self { Self::Nodes(value.0) } } impl<'a> From> for JsonPathValue<'a> { fn from(value: ValueType<'a>) -> Self { match value { ValueType::Value(v) => Self::Value(v), ValueType::Node(n) => Self::Node(n), ValueType::Nothing => Self::Nothing, } } } impl From for JsonPathValue<'_> { fn from(value: LogicalType) -> Self { Self::Logical(value) } } #[doc(hidden)] /// Error used to convey JSONPath queries that are not well-typed #[derive(Debug, thiserror::Error)] pub enum ConversionError { /// Cannot convert `from` into `to` #[error("attempted to convert {from} to {to}")] IncompatibleTypes { /// The type being converted from from: JsonPathType, /// The type being converted to to: JsonPathType, }, /// Literal values can not be considered nodes #[error("cannot convert a literal value to NodesType")] LiteralToNodes, } #[doc(hidden)] #[derive(Debug, Clone, Copy, PartialEq)] pub enum JsonPathType { Nodes, Value, Logical, } impl std::fmt::Display for JsonPathType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { JsonPathType::Nodes => write!(f, "NodesType"), JsonPathType::Logical => write!(f, "LogicalType"), JsonPathType::Value => write!(f, "ValueType"), } } } #[doc(hidden)] #[derive(Debug, PartialEq, Eq, Clone)] pub struct FunctionExpr { pub name: String, pub args: Vec, pub return_type: FunctionArgType, pub validated: V, } #[doc(hidden)] #[derive(Debug)] pub struct NotValidated; #[doc(hidden)] #[derive(Clone)] pub struct Validated { pub evaluator: &'static Evaluator, } impl PartialEq for Validated { fn eq(&self, _other: &Self) -> bool { true } } impl Eq for Validated {} impl std::fmt::Debug for Validated { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Validated") .field("evaluator", &"static function") .finish() } } impl FunctionExpr { #[cfg_attr( feature = "trace", tracing::instrument(name = "Evaluate Function Expr", level = "trace", parent = None, ret) )] pub fn evaluate<'a, 'b: 'a>( &'a self, current: &'b Value, root: &'b Value, ) -> JsonPathValue<'a> { let args: VecDeque = self .args .iter() .map(|a| a.evaluate(current, root)) .collect(); (self.validated.evaluator)(args) } } impl FunctionExpr { pub fn validate( name: String, args: Vec, ) -> Result, FunctionValidationError> { for f in inventory::iter:: { if f.name == name { (f.validator)(args.as_slice())?; return Ok(FunctionExpr { name, args, return_type: f.result_type, validated: Validated { evaluator: f.evaluator, }, }); } } Err(FunctionValidationError::Undefined { name }) } } impl std::fmt::Display for FunctionExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{name}(", name = self.name)?; for (i, arg) in self.args.iter().enumerate() { write!( f, "{arg}{comma}", comma = if i == self.args.len() - 1 { "" } else { "," } )?; } write!(f, ")") } } #[doc(hidden)] #[derive(Debug, PartialEq, Eq, Clone)] pub enum FunctionExprArg { Literal(Literal), SingularQuery(SingularQuery), FilterQuery(Query), LogicalExpr(LogicalOrExpr), FunctionExpr(FunctionExpr), } impl std::fmt::Display for FunctionExprArg { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { FunctionExprArg::Literal(lit) => write!(f, "{lit}"), FunctionExprArg::FilterQuery(query) => write!(f, "{query}"), FunctionExprArg::SingularQuery(sq) => write!(f, "{sq}"), FunctionExprArg::LogicalExpr(log) => write!(f, "{log}"), FunctionExprArg::FunctionExpr(func) => write!(f, "{func}"), } } } impl FunctionExprArg { #[cfg_attr( feature = "trace", tracing::instrument(name = "Evaluate Function Arg", level = "trace", parent = None, ret) )] fn evaluate<'a, 'b: 'a>(&'a self, current: &'b Value, root: &'b Value) -> JsonPathValue<'a> { match self { FunctionExprArg::Literal(lit) => lit.into(), FunctionExprArg::SingularQuery(q) => match q.eval_query(current, root) { Some(n) => JsonPathValue::Node(n), None => JsonPathValue::Nothing, }, FunctionExprArg::FilterQuery(q) => JsonPathValue::Nodes(q.query(current, root).into()), FunctionExprArg::LogicalExpr(l) => match l.test_filter(current, root) { true => JsonPathValue::Logical(LogicalType::True), false => JsonPathValue::Logical(LogicalType::False), }, FunctionExprArg::FunctionExpr(f) => f.evaluate(current, root), } } #[cfg_attr( feature = "trace", tracing::instrument(name = "Function Arg As Type Kind", level = "trace", parent = None, ret) )] pub fn as_type_kind(&self) -> Result { match self { FunctionExprArg::Literal(_) => Ok(FunctionArgType::Literal), FunctionExprArg::SingularQuery(_) => Ok(FunctionArgType::SingularQuery), FunctionExprArg::FilterQuery(query) => { if query.is_singular() { Ok(FunctionArgType::SingularQuery) } else { Ok(FunctionArgType::Nodelist) } } FunctionExprArg::LogicalExpr(_) => Ok(FunctionArgType::Logical), FunctionExprArg::FunctionExpr(func) => { for f in inventory::iter:: { if f.name == func.name.as_str() { return Ok(f.result_type); } } registered_function_result_type(&func.name).ok_or_else(|| { FunctionValidationError::Undefined { name: func.name.to_owned(), } }) } } } } /// Get the result type of a registered function /// /// This is a hack, but must be done, because the REGISTRY defined in `serde_json_path` /// is not accessible here. Therefore, this must also be maintained when new functions /// are added to the registry in the standard. /// /// An alternative would be to define the registry of functions in core, we would then /// just not have the convenience macro for defining them, along with their validators /// and evaluators. fn registered_function_result_type(name: &str) -> Option { match name { "length" => Some(FunctionArgType::Value), "count" => Some(FunctionArgType::Value), "match" => Some(FunctionArgType::Logical), "search" => Some(FunctionArgType::Logical), "value" => Some(FunctionArgType::Value), _ => None, } } /// Function argument types /// /// This is used to describe the type of a function argument to determine if it will be valid as a /// parameter to the function it is being passed to. /// /// The reason for having this type in addition to [`JsonPathType`] is that we need to have an /// intermediate representation of arguments that are singular queries. This is because singular /// queries can be used as an argument to both [`ValueType`] and [`NodesType`] parameters. /// Therefore, we require a `Node` variant here to indicate that an argument may be converted into /// either type of parameter. #[doc(hidden)] #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum FunctionArgType { /// Denotes a literal owned JSON value Literal, /// Denotes a borrowed JSON value from a singular query SingularQuery, /// Denotes a literal or borrowed JSON value, used to represent functions that return [`ValueType`] Value, /// Denotes a node list, either from a filter query argument, or a function that returns [`NodesType`] Nodelist, /// Denotes a logical, either from a logical expression, or from a function that returns [`LogicalType`] Logical, } impl std::fmt::Display for FunctionArgType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { FunctionArgType::Literal => write!(f, "literal"), FunctionArgType::SingularQuery => write!(f, "singular query"), FunctionArgType::Value => write!(f, "value type"), FunctionArgType::Nodelist => write!(f, "nodes type"), FunctionArgType::Logical => write!(f, "logical type"), } } } impl FunctionArgType { pub fn converts_to(&self, json_path_type: JsonPathType) -> bool { matches!( (self, json_path_type), ( FunctionArgType::Literal | FunctionArgType::Value, JsonPathType::Value ) | ( FunctionArgType::SingularQuery, JsonPathType::Value | JsonPathType::Nodes | JsonPathType::Logical ) | ( FunctionArgType::Nodelist, JsonPathType::Nodes | JsonPathType::Logical ) | (FunctionArgType::Logical, JsonPathType::Logical), ) } } #[doc(hidden)] /// An error occurred while validating a function #[derive(Debug, thiserror::Error, PartialEq)] pub enum FunctionValidationError { /// Function not defined in inventory #[error("function name '{name}' is not defined")] Undefined { /// The name of the function name: String, }, /// Mismatch in number of function arguments #[error("expected {expected} args, but received {received}")] NumberOfArgsMismatch { /// Expected number of arguments expected: usize, /// Received number of arguments received: usize, }, /// The type of received argument does not match the function definition #[error("in function {name}, in argument position {position}, expected a type that converts to {expected}, received {received}")] MismatchTypeKind { /// Function name name: String, /// Expected type expected: JsonPathType, /// Received type received: FunctionArgType, /// Argument position position: usize, }, #[error("function with incorrect return type used")] IncorrectFunctionReturnType, } impl TestFilter for FunctionExpr { #[cfg_attr( feature = "trace", tracing::instrument(name = "Test Function Expr", level = "trace", parent = None, ret) )] fn test_filter<'b>(&self, current: &'b Value, root: &'b Value) -> bool { match self.evaluate(current, root) { JsonPathValue::Nodes(nl) => !nl.is_empty(), JsonPathValue::Value(v) => v.test_filter(current, root), JsonPathValue::Logical(l) => l.into(), JsonPathValue::Node(n) => n.test_filter(current, root), JsonPathValue::Nothing => false, } } } serde_json_path_core-0.2.2/src/spec/integer.rs000064400000000000000000000120161046102023000175300ustar 00000000000000//! Representation of integers in the JSONPath specification //! //! The JSONPath specification defines some rules for integers used in query strings (see [here][spec]). //! //! [spec]: https://www.rfc-editor.org/rfc/rfc9535.html#name-overview use std::{ num::{ParseIntError, TryFromIntError}, str::FromStr, }; /// An integer for internet JSON ([RFC7493][ijson]) /// /// The value must be within the range [-(253)+1, (253)-1]. /// /// [ijson]: https://www.rfc-editor.org/rfc/rfc7493#section-2.2 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct Integer(i64); /// The maximum allowed value, 2^53 - 1 const MAX: i64 = 9_007_199_254_740_992 - 1; /// The minimum allowed value (-2^53) + 1 const MIN: i64 = -9_007_199_254_740_992 + 1; #[inline] fn check_i64_is_valid(v: i64) -> bool { (MIN..=MAX).contains(&v) } impl Integer { /// An [`Integer`] with the value 0 pub const ZERO: Self = Self(0); fn try_new(value: i64) -> Result { if check_i64_is_valid(value) { Ok(Self(value)) } else { Err(IntegerError::OutOfBounds) } } /// Get an [`Integer`] from an `i64` /// /// This is intended for initializing an integer with small, non-zero numbers. /// /// # Panics /// /// This will panic if the inputted value is out of the valid range /// [-(253)+1, (253)-1]. pub fn from_i64_unchecked(value: i64) -> Self { Self::try_new(value).expect("value is out of the valid range") } /// Take the absolute value, producing a new instance of [`Integer`] /// /// This is safe and will never panic since no instance of [`Integer`] can be constructed with /// a value that is outside the valid range and since the absolute of the minimum allowed value /// is the maximum value. pub fn abs(self) -> Self { Self(self.0.abs()) } /// Add the two values, producing a new instance of [`Integer`] or `None` if the /// resulting value is outside the valid range [-(253)+1, (253)-1] pub fn checked_add(self, rhs: Self) -> Option { let i = self.0.checked_add(rhs.0)?; check_i64_is_valid(i).then_some(Self(i)) } /// Subtract the `rhs` from `self`, producing a new instance of [`Integer`] or `None` /// if the resulting value is outside the valid range [-(253)+1, (253)-1]. pub fn checked_sub(self, rhs: Self) -> Option { let i = self.0.checked_sub(rhs.0)?; check_i64_is_valid(i).then_some(Self(i)) } /// Multiply the two values, producing a new instance of [`Integer`] or `None` if the resulting /// value is outside the valid range [-(253)+1, (253)-1]. pub fn checked_mul(self, rhs: Self) -> Option { let i = self.0.checked_mul(rhs.0)?; check_i64_is_valid(i).then_some(Self(i)) } } impl TryFrom for Integer { type Error = IntegerError; fn try_from(value: i64) -> Result { Self::try_new(value) } } macro_rules! impl_try_from { ($type:ty) => { impl TryFrom<$type> for Integer { type Error = IntegerError; fn try_from(value: $type) -> Result { i64::try_from(value) .map_err(|_| IntegerError::OutOfBounds) .and_then(Self::try_from) } } }; } impl_try_from!(i128); impl_try_from!(u64); impl_try_from!(u128); impl_try_from!(usize); impl_try_from!(isize); macro_rules! impl_from { ($type:ty) => { impl From<$type> for Integer { fn from(value: $type) -> Self { Self(value.into()) } } }; } impl_from!(i8); impl_from!(i16); impl_from!(i32); impl_from!(u8); impl_from!(u16); impl_from!(u32); impl FromStr for Integer { type Err = IntegerError; fn from_str(s: &str) -> Result { s.parse::().map_err(Into::into).and_then(Self::try_new) } } impl std::fmt::Display for Integer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } impl TryFrom for usize { type Error = TryFromIntError; fn try_from(value: Integer) -> Result { Self::try_from(value.0) } } impl PartialEq for Integer { fn eq(&self, other: &i64) -> bool { self.0.eq(other) } } impl PartialOrd for Integer { fn partial_cmp(&self, other: &i64) -> Option { self.0.partial_cmp(other) } } /// An error for the [`Integer`] type #[derive(Debug, thiserror::Error)] pub enum IntegerError { /// The provided value was outside the valid range [-(2**53)+1, (2**53)-1] #[error("the provided integer was outside the valid range, see https://www.rfc-editor.org/rfc/rfc9535.html#section-2.1-4.1")] OutOfBounds, /// Integer parsing error #[error(transparent)] Parse(#[from] ParseIntError), } serde_json_path_core-0.2.2/src/spec/mod.rs000064400000000000000000000002101046102023000166430ustar 00000000000000//! Types representing the IETF JSONPath Standard pub mod functions; pub mod integer; pub mod query; pub mod segment; pub mod selector; serde_json_path_core-0.2.2/src/spec/query.rs000064400000000000000000000073341046102023000172470ustar 00000000000000//! Types representing queries in JSONPath use serde_json::Value; use crate::{node::LocatedNode, path::NormalizedPath}; use super::segment::QuerySegment; mod sealed { use crate::spec::{ segment::{QuerySegment, Segment}, selector::{ filter::{Filter, SingularQuery}, index::Index, name::Name, slice::Slice, Selector, }, }; use super::Query; pub trait Sealed {} impl Sealed for Query {} impl Sealed for QuerySegment {} impl Sealed for Segment {} impl Sealed for Slice {} impl Sealed for Name {} impl Sealed for Selector {} impl Sealed for Index {} impl Sealed for Filter {} impl Sealed for SingularQuery {} } /// A type that is query-able 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>; /// Query `self` using a current node, the root node, and the normalized path of the current /// node's parent fn query_located<'b>( &self, current: &'b Value, root: &'b Value, parent: NormalizedPath<'b>, ) -> Vec>; } /// Represents a JSONPath expression #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct Query { /// The kind of query, root (`$`), or current (`@`) pub kind: QueryKind, /// The segments constituting the query pub segments: Vec, } impl Query { pub(crate) fn is_singular(&self) -> bool { for s in &self.segments { if s.is_descendent() { return false; } if !s.segment.is_singular() { return false; } } true } } impl std::fmt::Display for Query { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.kind { QueryKind::Root => write!(f, "$")?, QueryKind::Current => write!(f, "@")?, } for s in &self.segments { write!(f, "{s}")?; } Ok(()) } } /// The kind of query #[derive(Debug, PartialEq, Eq, Clone, Default)] pub enum QueryKind { /// A query against the root of a JSON object, i.e., with `$` #[default] Root, /// A query against the current node within a JSON object, i.e., with `@` Current, } impl Queryable for Query { #[cfg_attr(feature = "trace", tracing::instrument(name = "Main Query", level = "trace", parent = None, ret))] fn query<'b>(&self, current: &'b Value, root: &'b Value) -> Vec<&'b Value> { let mut query = match self.kind { QueryKind::Root => vec![root], QueryKind::Current => vec![current], }; for segment in &self.segments { let mut new_query = Vec::new(); for q in &query { new_query.append(&mut segment.query(q, root)); } query = new_query; } query } fn query_located<'b>( &self, current: &'b Value, root: &'b Value, 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 LocatedNode { loc, node } in result { r.append(&mut s.query_located(node, root, loc.clone())); } result = r; } result } } serde_json_path_core-0.2.2/src/spec/segment.rs000064400000000000000000000200771046102023000175430ustar 00000000000000//! Types representing segments in JSONPath use serde_json::Value; use crate::{node::LocatedNode, path::NormalizedPath}; use super::{query::Queryable, selector::Selector}; /// A segment of a JSONPath query #[derive(Debug, PartialEq, Eq, Clone)] pub struct QuerySegment { /// The kind of segment pub kind: QuerySegmentKind, /// The segment pub segment: Segment, } impl QuerySegment { /// Is this a normal child segment pub fn is_child(&self) -> bool { matches!(self.kind, QuerySegmentKind::Child) } /// Is this a recursive descent child pub fn is_descendent(&self) -> bool { !self.is_child() } } impl std::fmt::Display for QuerySegment { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if matches!(self.kind, QuerySegmentKind::Descendant) { write!(f, "..")?; } write!(f, "{segment}", segment = self.segment) } } /// The kind of query segment #[derive(Debug, PartialEq, Eq, Clone)] pub enum QuerySegmentKind { /// A normal child /// /// Addresses the direct descendant of the preceding segment Child, /// A descendant child /// /// Addresses all descendant children of the preceding segment, recursively Descendant, } impl Queryable for QuerySegment { #[cfg_attr(feature = "trace", tracing::instrument(name = "Query Path Segment", level = "trace", parent = None, ret))] fn query<'b>(&self, current: &'b Value, root: &'b Value) -> Vec<&'b Value> { let mut query = self.segment.query(current, root); if matches!(self.kind, QuerySegmentKind::Descendant) { query.append(&mut descend(self, current, root)); } query } fn query_located<'b>( &self, current: &'b Value, root: &'b Value, parent: NormalizedPath<'b>, ) -> 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)); result } else { self.segment.query_located(current, root, parent) } } } #[cfg_attr(feature = "trace", tracing::instrument(name = "Descend", level = "trace", parent = None, ret))] fn descend<'b>(segment: &QuerySegment, current: &'b Value, root: &'b Value) -> Vec<&'b Value> { let mut query = Vec::new(); if let Some(list) = current.as_array() { for v in list { query.append(&mut segment.query(v, root)); } } else if let Some(obj) = current.as_object() { for (_, v) in obj { query.append(&mut segment.query(v, root)); } } query } fn descend_paths<'b>( segment: &QuerySegment, current: &'b Value, root: &'b Value, parent: NormalizedPath<'b>, ) -> Vec> { let mut result = Vec::new(); if let Some(list) = current.as_array() { for (i, v) in list.iter().enumerate() { 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_located(v, root, parent.clone_and_push(k))); } } result } /// Represents the different forms of JSONPath segment #[derive(Debug, PartialEq, Eq, Clone)] pub enum Segment { /// Long hand segments contain multiple selectors inside square brackets LongHand(Vec), /// Dot-name selectors are a short form for representing keys in an object DotName(String), /// The wildcard shorthand `.*` Wildcard, } impl Segment { /// Does this segment extract a singular node pub fn is_singular(&self) -> bool { match self { Segment::LongHand(selectors) => { if selectors.len() > 1 { return false; } if let Some(s) = selectors.first() { s.is_singular() } else { // if the selector list is empty, this shouldn't be a valid // JSONPath, but at least, it would be selecting nothing, and // that could be considered singular, i.e., None. true } } Segment::DotName(_) => true, Segment::Wildcard => false, } } /// Optionally produce self as a slice of selectors, from a long hand segment pub fn as_long_hand(&self) -> Option<&[Selector]> { match self { Segment::LongHand(v) => Some(v.as_slice()), _ => None, } } /// Optionally produce self as a single name segment pub fn as_dot_name(&self) -> Option<&str> { match self { Segment::DotName(s) => Some(s.as_str()), _ => None, } } } impl std::fmt::Display for Segment { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Segment::LongHand(selectors) => { write!(f, "[")?; for (i, s) in selectors.iter().enumerate() { write!( f, "{s}{comma}", comma = if i == selectors.len() - 1 { "" } else { "," } )?; } write!(f, "]")?; } Segment::DotName(name) => write!(f, ".{name}")?, Segment::Wildcard => write!(f, ".*")?, } Ok(()) } } impl Queryable for Segment { #[cfg_attr(feature = "trace", tracing::instrument(name = "Query Segment", level = "trace", parent = None, ret))] fn query<'b>(&self, current: &'b Value, root: &'b Value) -> Vec<&'b Value> { let mut query = Vec::new(); match self { Segment::LongHand(selectors) => { for selector in selectors { query.append(&mut selector.query(current, root)); } } Segment::DotName(key) => { if let Some(obj) = current.as_object() { if let Some(v) = obj.get(key) { query.push(v); } } } Segment::Wildcard => { if let Some(list) = current.as_array() { for v in list { query.push(v); } } else if let Some(obj) = current.as_object() { for (_, v) in obj { query.push(v); } } } } query } fn query_located<'b>( &self, current: &'b Value, root: &'b Value, mut parent: NormalizedPath<'b>, ) -> Vec> { let mut result = vec![]; match self { Segment::LongHand(selectors) => { for s in selectors { result.append(&mut s.query_located(current, root, parent.clone())); } } Segment::DotName(name) => { if let Some((k, v)) = current.as_object().and_then(|o| o.get_key_value(name)) { parent.push(k); 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(LocatedNode { loc: parent.clone_and_push(i), node: v, }); } } else if let Some(obj) = current.as_object() { for (k, v) in obj { result.push(LocatedNode { loc: parent.clone_and_push(k), node: v, }); } } } } result } } serde_json_path_core-0.2.2/src/spec/selector/filter.rs000064400000000000000000000525771046102023000212200ustar 00000000000000//! Types representing filter selectors in JSONPath use serde_json::{Number, Value}; use crate::{ node::LocatedNode, path::NormalizedPath, spec::{ functions::{FunctionExpr, JsonPathValue, Validated}, query::{Query, QueryKind, Queryable}, segment::{QuerySegment, Segment}, }, }; use super::{index::Index, name::Name, Selector}; mod sealed { use serde_json::Value; use crate::spec::functions::FunctionExpr; use super::{BasicExpr, ComparisonExpr, ExistExpr, LogicalAndExpr, LogicalOrExpr}; pub trait Sealed {} impl Sealed for Value {} impl Sealed for LogicalOrExpr {} impl Sealed for LogicalAndExpr {} impl Sealed for BasicExpr {} impl Sealed for ExistExpr {} impl Sealed for ComparisonExpr {} impl Sealed for FunctionExpr {} } /// Trait for testing a filter type pub trait TestFilter: sealed::Sealed { /// Test self using the current and root nodes fn test_filter<'b>(&self, current: &'b Value, root: &'b Value) -> bool; } impl TestFilter for Value { fn test_filter<'b>(&self, _current: &'b Value, _root: &'b Value) -> bool { match self { Value::Null => false, Value::Bool(b) => *b, Value::Number(n) => n != &Number::from(0), _ => true, } } } /// The main filter type for JSONPath #[derive(Debug, PartialEq, Eq, Clone)] pub struct Filter(pub LogicalOrExpr); impl std::fmt::Display for Filter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{expr}", expr = self.0) } } impl Queryable for Filter { #[cfg_attr(feature = "trace", tracing::instrument(name = "Query Filter", level = "trace", parent = None, ret))] fn query<'b>(&self, current: &'b Value, root: &'b Value) -> Vec<&'b Value> { if let Some(list) = current.as_array() { list.iter() .filter(|v| self.0.test_filter(v, root)) .collect() } else if let Some(obj) = current.as_object() { obj.iter() .map(|(_, v)| v) .filter(|v| self.0.test_filter(v, root)) .collect() } else { vec![] } } fn query_located<'b>( &self, current: &'b Value, root: &'b Value, parent: NormalizedPath<'b>, ) -> Vec> { if let Some(list) = current.as_array() { list.iter() .enumerate() .filter(|(_, v)| self.0.test_filter(v, root)) .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)| LocatedNode { loc: parent.clone_and_push(k), node: v, }) .collect() } else { vec![] } } } /// The top level boolean expression type /// /// This is also `logical-expression` in the JSONPath specification, but the naming was chosen to /// make it more clear that it represents the logical OR, and to not have an extra wrapping type. #[derive(Debug, PartialEq, Eq, Clone)] pub struct LogicalOrExpr(pub Vec); impl std::fmt::Display for LogicalOrExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for (i, expr) in self.0.iter().enumerate() { write!( f, "{expr}{logic}", logic = if i == self.0.len() - 1 { "" } else { " || " } )?; } Ok(()) } } impl TestFilter for LogicalOrExpr { #[cfg_attr(feature = "trace", tracing::instrument(name = "Test Logical Or Expr", level = "trace", parent = None, ret))] fn test_filter<'b>(&self, current: &'b Value, root: &'b Value) -> bool { self.0.iter().any(|expr| expr.test_filter(current, root)) } } /// A logical AND expression #[derive(Debug, PartialEq, Eq, Clone)] pub struct LogicalAndExpr(pub Vec); impl std::fmt::Display for LogicalAndExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for (i, expr) in self.0.iter().enumerate() { write!( f, "{expr}{logic}", logic = if i == self.0.len() - 1 { "" } else { " && " } )?; } Ok(()) } } impl TestFilter for LogicalAndExpr { #[cfg_attr(feature = "trace", tracing::instrument(name = "Test Logical And Expr", level = "trace", parent = None, ret))] fn test_filter<'b>(&self, current: &'b Value, root: &'b Value) -> bool { self.0.iter().all(|expr| expr.test_filter(current, root)) } } /// The basic for m of expression in a filter #[derive(Debug, PartialEq, Eq, Clone)] pub enum BasicExpr { /// An expression wrapped in parenthesis Paren(LogicalOrExpr), /// A parenthesized expression preceded with a `!` NotParen(LogicalOrExpr), /// A relationship expression which compares two JSON values Relation(ComparisonExpr), /// An existence expression Exist(ExistExpr), /// The inverse of an existence expression, i.e., preceded by `!` NotExist(ExistExpr), /// A function expression FuncExpr(FunctionExpr), /// The inverse of a function expression, i.e., preceded by `!` NotFuncExpr(FunctionExpr), } impl std::fmt::Display for BasicExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { BasicExpr::Paren(expr) => write!(f, "({expr})"), BasicExpr::NotParen(expr) => write!(f, "!({expr})"), BasicExpr::Relation(rel) => write!(f, "{rel}"), BasicExpr::Exist(exist) => write!(f, "{exist}"), BasicExpr::NotExist(exist) => write!(f, "!{exist}"), BasicExpr::FuncExpr(expr) => write!(f, "{expr}"), BasicExpr::NotFuncExpr(expr) => write!(f, "{expr}"), } } } impl BasicExpr { /// Optionally express as a relation expression pub fn as_relation(&self) -> Option<&ComparisonExpr> { match self { BasicExpr::Relation(cx) => Some(cx), _ => None, } } } impl TestFilter for BasicExpr { #[cfg_attr(feature = "trace", tracing::instrument(name = "Test Basic Expr", level = "trace", parent = None, ret))] fn test_filter<'b>(&self, current: &'b Value, root: &'b Value) -> bool { match self { BasicExpr::Paren(expr) => expr.test_filter(current, root), BasicExpr::NotParen(expr) => !expr.test_filter(current, root), BasicExpr::Relation(expr) => expr.test_filter(current, root), BasicExpr::Exist(expr) => expr.test_filter(current, root), BasicExpr::NotExist(expr) => !expr.test_filter(current, root), BasicExpr::FuncExpr(expr) => expr.test_filter(current, root), BasicExpr::NotFuncExpr(expr) => !expr.test_filter(current, root), } } } /// Existence expression /// /// ### Implementation Note /// /// This does not support the function expression notation outlined in the JSONPath spec. #[derive(Debug, PartialEq, Eq, Clone)] pub struct ExistExpr(pub Query); impl std::fmt::Display for ExistExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{query}", query = self.0) } } impl TestFilter for ExistExpr { #[cfg_attr(feature = "trace", tracing::instrument(name = "Test Exists Expr", level = "trace", parent = None, ret))] fn test_filter<'b>(&self, current: &'b Value, root: &'b Value) -> bool { !self.0.query(current, root).is_empty() } } /// A comparison expression comparing two JSON values #[derive(Debug, PartialEq, Eq, Clone)] pub struct ComparisonExpr { /// The JSON value on the left of the comparison pub left: Comparable, /// The operator of comparison pub op: ComparisonOperator, /// The JSON value on the right of the comparison pub right: Comparable, } fn check_equal_to(left: &JsonPathValue, right: &JsonPathValue) -> bool { match (left, right) { (JsonPathValue::Node(v1), JsonPathValue::Node(v2)) => value_equal_to(v1, v2), (JsonPathValue::Node(v1), JsonPathValue::Value(v2)) => value_equal_to(v1, v2), (JsonPathValue::Value(v1), JsonPathValue::Node(v2)) => value_equal_to(v1, v2), (JsonPathValue::Value(v1), JsonPathValue::Value(v2)) => value_equal_to(v1, v2), (JsonPathValue::Nothing, JsonPathValue::Nothing) => true, _ => false, } } fn value_equal_to(left: &Value, right: &Value) -> bool { match (left, right) { (Value::Number(l), Value::Number(r)) => number_equal_to(l, r), _ => left == right, } } fn number_equal_to(left: &Number, right: &Number) -> bool { if let (Some(l), Some(r)) = (left.as_f64(), right.as_f64()) { l == r } else if let (Some(l), Some(r)) = (left.as_i64(), right.as_i64()) { l == r } else if let (Some(l), Some(r)) = (left.as_u64(), right.as_u64()) { l == r } else { false } } fn value_less_than(left: &Value, right: &Value) -> bool { match (left, right) { (Value::Number(n1), Value::Number(n2)) => number_less_than(n1, n2), (Value::String(s1), Value::String(s2)) => s1 < s2, _ => false, } } fn check_less_than(left: &JsonPathValue, right: &JsonPathValue) -> bool { match (left, right) { (JsonPathValue::Node(v1), JsonPathValue::Node(v2)) => value_less_than(v1, v2), (JsonPathValue::Node(v1), JsonPathValue::Value(v2)) => value_less_than(v1, v2), (JsonPathValue::Value(v1), JsonPathValue::Node(v2)) => value_less_than(v1, v2), (JsonPathValue::Value(v1), JsonPathValue::Value(v2)) => value_less_than(v1, v2), _ => false, } } fn value_same_type(left: &Value, right: &Value) -> bool { matches!((left, right), (Value::Null, Value::Null)) | matches!((left, right), (Value::Bool(_), Value::Bool(_))) | matches!((left, right), (Value::Number(_), Value::Number(_))) | matches!((left, right), (Value::String(_), Value::String(_))) | matches!((left, right), (Value::Array(_), Value::Array(_))) | matches!((left, right), (Value::Object(_), Value::Object(_))) } fn check_same_type(left: &JsonPathValue, right: &JsonPathValue) -> bool { match (left, right) { (JsonPathValue::Node(v1), JsonPathValue::Node(v2)) => value_same_type(v1, v2), (JsonPathValue::Node(v1), JsonPathValue::Value(v2)) => value_same_type(v1, v2), (JsonPathValue::Value(v1), JsonPathValue::Node(v2)) => value_same_type(v1, v2), (JsonPathValue::Value(v1), JsonPathValue::Value(v2)) => value_same_type(v1, v2), _ => false, } } impl std::fmt::Display for ComparisonExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{left}{op}{right}", left = self.left, op = self.op, right = self.right ) } } impl TestFilter for ComparisonExpr { #[cfg_attr(feature = "trace", tracing::instrument(name = "Test Comparison Expr", level = "trace", parent = None, ret))] fn test_filter<'b>(&self, current: &'b Value, root: &'b Value) -> bool { let left = self.left.as_value(current, root); let right = self.right.as_value(current, root); match self.op { ComparisonOperator::EqualTo => check_equal_to(&left, &right), ComparisonOperator::NotEqualTo => !check_equal_to(&left, &right), ComparisonOperator::LessThan => { check_same_type(&left, &right) && check_less_than(&left, &right) } ComparisonOperator::GreaterThan => { check_same_type(&left, &right) && !check_less_than(&left, &right) && !check_equal_to(&left, &right) } ComparisonOperator::LessThanEqualTo => { check_same_type(&left, &right) && (check_less_than(&left, &right) || check_equal_to(&left, &right)) } ComparisonOperator::GreaterThanEqualTo => { check_same_type(&left, &right) && !check_less_than(&left, &right) } } } } fn number_less_than(n1: &Number, n2: &Number) -> bool { if let (Some(a), Some(b)) = (n1.as_f64(), n2.as_f64()) { a < b } else if let (Some(a), Some(b)) = (n1.as_i64(), n2.as_i64()) { a < b } else if let (Some(a), Some(b)) = (n1.as_u64(), n2.as_u64()) { a < b } else { false } } /// The comparison operator #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ComparisonOperator { /// `==` EqualTo, /// `!=` NotEqualTo, /// `<` LessThan, /// `>` GreaterThan, /// `<=` LessThanEqualTo, /// `>=` GreaterThanEqualTo, } impl std::fmt::Display for ComparisonOperator { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ComparisonOperator::EqualTo => write!(f, "=="), ComparisonOperator::NotEqualTo => write!(f, "!="), ComparisonOperator::LessThan => write!(f, "<"), ComparisonOperator::GreaterThan => write!(f, ">"), ComparisonOperator::LessThanEqualTo => write!(f, "<="), ComparisonOperator::GreaterThanEqualTo => write!(f, ">="), } } } /// A type that is comparable #[derive(Debug, PartialEq, Eq, Clone)] pub enum Comparable { /// A literal JSON value, excluding objects and arrays Literal(Literal), /// A singular query /// /// This will only produce a single node, i.e., JSON value, or nothing SingularQuery(SingularQuery), /// A function expression that can only produce a `ValueType` FunctionExpr(FunctionExpr), } impl std::fmt::Display for Comparable { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Comparable::Literal(lit) => write!(f, "{lit}"), Comparable::SingularQuery(path) => write!(f, "{path}"), Comparable::FunctionExpr(expr) => write!(f, "{expr}"), } } } impl Comparable { #[doc(hidden)] #[cfg_attr(feature = "trace", tracing::instrument(name = "Comparable::as_value", level = "trace", parent = None, ret))] pub fn as_value<'a, 'b: 'a>( &'a self, current: &'b Value, root: &'b Value, ) -> JsonPathValue<'a> { match self { Comparable::Literal(lit) => lit.into(), Comparable::SingularQuery(sp) => match sp.eval_query(current, root) { Some(v) => JsonPathValue::Node(v), None => JsonPathValue::Nothing, }, Comparable::FunctionExpr(expr) => expr.evaluate(current, root), } } #[doc(hidden)] pub fn as_singular_path(&self) -> Option<&SingularQuery> { match self { Comparable::SingularQuery(sp) => Some(sp), _ => None, } } } /// A literal JSON value that can be represented in a JSONPath query #[derive(Debug, PartialEq, Eq, Clone)] pub enum Literal { /// A valid JSON number Number(Number), /// A string String(String), /// `true` or `false` Bool(bool), /// `null` Null, } impl<'a> From<&'a Literal> for JsonPathValue<'a> { fn from(value: &'a Literal) -> Self { match value { // Cloning here seems cheap, certainly for numbers, but it may not be desireable for // strings. Literal::Number(n) => JsonPathValue::Value(n.to_owned().into()), Literal::String(s) => JsonPathValue::Value(s.to_owned().into()), Literal::Bool(b) => JsonPathValue::Value(Value::from(*b)), Literal::Null => JsonPathValue::Value(Value::Null), } } } impl std::fmt::Display for Literal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Literal::Number(n) => write!(f, "{n}"), Literal::String(s) => write!(f, "'{s}'"), Literal::Bool(b) => write!(f, "{b}"), Literal::Null => write!(f, "null"), } } } /// A segment in a singular query #[derive(Debug, PartialEq, Eq, Clone)] pub enum SingularQuerySegment { /// A single name segment Name(Name), /// A single index segment Index(Index), } impl std::fmt::Display for SingularQuerySegment { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { SingularQuerySegment::Name(name) => write!(f, "{name}"), SingularQuerySegment::Index(index) => write!(f, "{index}"), } } } impl TryFrom for SingularQuerySegment { type Error = NonSingularQueryError; fn try_from(segment: QuerySegment) -> Result { if segment.is_descendent() { return Err(NonSingularQueryError::Descendant); } match segment.segment { Segment::LongHand(mut selectors) => { if selectors.len() > 1 { Err(NonSingularQueryError::TooManySelectors) } else if let Some(sel) = selectors.pop() { sel.try_into() } else { Err(NonSingularQueryError::NoSelectors) } } Segment::DotName(name) => Ok(Self::Name(Name(name))), Segment::Wildcard => Err(NonSingularQueryError::Wildcard), } } } impl TryFrom for SingularQuerySegment { type Error = NonSingularQueryError; fn try_from(selector: Selector) -> Result { match selector { Selector::Name(n) => Ok(Self::Name(n)), Selector::Wildcard => Err(NonSingularQueryError::Wildcard), Selector::Index(i) => Ok(Self::Index(i)), Selector::ArraySlice(_) => Err(NonSingularQueryError::Slice), Selector::Filter(_) => Err(NonSingularQueryError::Filter), } } } /// Represents a singular query in JSONPath #[derive(Debug, PartialEq, Eq, Clone)] pub struct SingularQuery { /// The kind of singular query, relative or absolute pub kind: SingularQueryKind, /// The segments making up the query pub segments: Vec, } impl SingularQuery { /// Evaluate the singular query #[cfg_attr(feature = "trace", tracing::instrument(name = "SingularQuery::eval_query", level = "trace", parent = None, ret))] pub fn eval_query<'b>(&self, current: &'b Value, root: &'b Value) -> Option<&'b Value> { let mut target = match self.kind { SingularQueryKind::Absolute => root, SingularQueryKind::Relative => current, }; for segment in &self.segments { match segment { SingularQuerySegment::Name(name) => { if let Some(t) = target.as_object().and_then(|o| o.get(name.as_str())) { target = t; } else { return None; } } SingularQuerySegment::Index(index) => { if let Some(t) = target .as_array() .and_then(|l| usize::try_from(index.0).ok().and_then(|i| l.get(i))) { target = t; } else { return None; } } } } Some(target) } } impl TryFrom for SingularQuery { type Error = NonSingularQueryError; fn try_from(query: Query) -> Result { let kind = SingularQueryKind::from(query.kind); let segments = query .segments .into_iter() .map(TryFrom::try_from) .collect::, Self::Error>>()?; Ok(Self { kind, segments }) } } impl std::fmt::Display for SingularQuery { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.kind { SingularQueryKind::Absolute => write!(f, "$")?, SingularQueryKind::Relative => write!(f, "@")?, } for s in &self.segments { write!(f, "[{s}]")?; } Ok(()) } } /// The kind of singular query #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum SingularQueryKind { /// Referencing the root node, i.e., `$` Absolute, /// Referencing the current node, i.e., `@` Relative, } impl From for SingularQueryKind { fn from(qk: QueryKind) -> Self { match qk { QueryKind::Root => Self::Absolute, QueryKind::Current => Self::Relative, } } } /// Error when parsing a singular query #[derive(Debug, thiserror::Error, PartialEq)] pub enum NonSingularQueryError { /// Descendant segment #[error("descendant segments are not singular")] Descendant, /// Long hand segment with too many internal selectors #[error("long hand segment contained more than one selector")] TooManySelectors, /// Long hand segment with no selectors #[error("long hand segment contained no selectors")] NoSelectors, /// A wildcard segment #[error("wildcard segments are not singular")] Wildcard, /// A slice segment #[error("slice segments are not singular")] Slice, /// A filter segment #[error("filter segments are not singular")] Filter, } serde_json_path_core-0.2.2/src/spec/selector/index.rs000064400000000000000000000042001046102023000210160ustar 00000000000000//! Index selectors in JSONPath use serde_json::Value; use crate::{ node::LocatedNode, path::NormalizedPath, spec::{integer::Integer, query::Queryable}, }; /// For selecting array elements by their index /// /// Can use negative indices to index from the end of an array #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct Index(pub Integer); impl std::fmt::Display for Index { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{index}", index = self.0) } } impl Queryable for Index { #[cfg_attr(feature = "trace", tracing::instrument(name = "Query Index", level = "trace", parent = None, ret))] fn query<'b>(&self, current: &'b Value, _root: &'b Value) -> Vec<&'b Value> { if let Some(list) = current.as_array() { if self.0 < 0 { let abs = self.0.abs(); usize::try_from(abs) .ok() .and_then(|i| list.len().checked_sub(i)) .and_then(|i| list.get(i)) .into_iter() .collect() } else { usize::try_from(self.0) .ok() .and_then(|i| list.get(i)) .into_iter() .collect() } } else { vec![] } } fn query_located<'b>( &self, current: &'b Value, _root: &'b Value, mut parent: NormalizedPath<'b>, ) -> Vec> { if let Some((index, node)) = current.as_array().and_then(|list| { if self.0 < 0 { let abs = self.0.abs(); usize::try_from(abs) .ok() .and_then(|i| list.len().checked_sub(i)) .and_then(|i| list.get(i).map(|v| (i, v))) } else { usize::try_from(self.0) .ok() .and_then(|i| list.get(i).map(|v| (i, v))) } }) { parent.push(index); vec![LocatedNode { loc: parent, node }] } else { vec![] } } } serde_json_path_core-0.2.2/src/spec/selector/mod.rs000064400000000000000000000072621046102023000205010ustar 00000000000000//! Types representing the different selectors in JSONPath pub mod filter; pub mod index; pub mod name; 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::query::Queryable; /// A JSONPath selector #[derive(Debug, PartialEq, Eq, Clone)] pub enum Selector { /// Select an object key Name(Name), /// Select all nodes /// /// For an object, this produces a nodelist of all member values; for an array, this produces a /// nodelist of all array elements. Wildcard, /// Select an array element Index(Index), /// Select a slice from an array ArraySlice(Slice), /// Use a filter to select nodes Filter(Filter), } impl Selector { /// Will the selector select at most only a single node pub fn is_singular(&self) -> bool { matches!(self, Selector::Name(_) | Selector::Index(_)) } } impl std::fmt::Display for Selector { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Selector::Name(name) => write!(f, "{name}"), Selector::Wildcard => write!(f, "*"), Selector::Index(index) => write!(f, "{index}"), Selector::ArraySlice(slice) => write!(f, "{slice}"), Selector::Filter(filter) => write!(f, "?{filter}"), } } } impl Queryable for Selector { #[cfg_attr(feature = "trace", tracing::instrument(name = "Query Selector", level = "trace", parent = None, ret))] fn query<'b>(&self, current: &'b Value, root: &'b Value) -> Vec<&'b Value> { let mut query = Vec::new(); match self { Selector::Name(name) => query.append(&mut name.query(current, root)), Selector::Wildcard => { if let Some(list) = current.as_array() { for v in list { query.push(v); } } else if let Some(obj) = current.as_object() { for (_, v) in obj { query.push(v); } } } Selector::Index(index) => query.append(&mut index.query(current, root)), Selector::ArraySlice(slice) => query.append(&mut slice.query(current, root)), Selector::Filter(filter) => query.append(&mut filter.query(current, root)), } query } fn query_located<'b>( &self, current: &'b Value, root: &'b Value, parent: NormalizedPath<'b>, ) -> 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, node)| LocatedNode { loc: parent.clone_and_push(i), node, }) .collect() } else if let Some(obj) = current.as_object() { obj.iter() .map(|(k, node)| LocatedNode { loc: parent.clone_and_push(k), node, }) .collect() } else { vec![] } } 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), } } } serde_json_path_core-0.2.2/src/spec/selector/name.rs000064400000000000000000000025651046102023000206430ustar 00000000000000//! Name selector for selecting object keys in JSONPath use serde_json::Value; use crate::{node::LocatedNode, path::NormalizedPath, spec::query::Queryable}; /// Select a single JSON object key #[derive(Debug, PartialEq, Eq, Clone)] pub struct Name(pub String); impl Name { /// Get as a string slice pub fn as_str(&self) -> &str { &self.0 } } impl std::fmt::Display for Name { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "'{name}'", name = self.0) } } impl Queryable for Name { #[cfg_attr(feature = "trace", tracing::instrument(name = "Query Name", level = "trace", parent = None, ret))] fn query<'b>(&self, current: &'b Value, _root: &'b Value) -> Vec<&'b Value> { if let Some(obj) = current.as_object() { obj.get(&self.0).into_iter().collect() } else { vec![] } } fn query_located<'b>( &self, current: &'b Value, _root: &'b Value, mut parent: NormalizedPath<'b>, ) -> 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![] } } } impl From<&str> for Name { fn from(s: &str) -> Self { Self(s.to_owned()) } } serde_json_path_core-0.2.2/src/spec/selector/slice.rs000064400000000000000000000172641046102023000210240ustar 00000000000000//! Slice selectors for selecting array slices in JSONPath use serde_json::Value; use crate::{ node::LocatedNode, path::NormalizedPath, spec::{integer::Integer, query::Queryable}, }; /// A slice selector #[derive(Debug, PartialEq, Eq, Default, Clone, Copy)] pub struct Slice { /// The start of the slice /// /// This can be negative to start the slice from a position relative to the end of the array /// being sliced. pub start: Option, /// The end of the slice /// /// This can be negative to end the slice at a position relative to the end of the array being /// sliced. pub end: Option, /// The step slice for the slice /// /// This can be negative to step in reverse order. pub step: Option, } impl std::fmt::Display for Slice { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(start) = self.start { write!(f, "{start}")?; } write!(f, ":")?; if let Some(end) = self.end { write!(f, "{end}")?; } write!(f, ":")?; if let Some(step) = self.step { write!(f, "{step}")?; } Ok(()) } } #[doc(hidden)] impl Slice { pub fn new() -> Self { Self::default() } /// Set the slice `start` /// /// # Panics /// /// This will panic if the provided value is outside the range [-(253) + 1, (253) - 1]. pub fn with_start(mut self, start: i64) -> Self { self.start = Some(Integer::from_i64_unchecked(start)); self } /// Set the slice `end` /// /// # Panics /// /// This will panic if the provided value is outside the range [-(253) + 1, (253) - 1]. pub fn with_end(mut self, end: i64) -> Self { self.end = Some(Integer::from_i64_unchecked(end)); self } /// Set the slice `step` /// /// # Panics /// /// This will panic if the provided value is outside the range [-(253) + 1, (253) - 1]. pub fn with_step(mut self, step: i64) -> Self { self.step = Some(Integer::from_i64_unchecked(step)); self } #[inline] fn bounds_on_forward_slice(&self, len: Integer) -> (Integer, Integer) { let start_default = self.start.unwrap_or(Integer::ZERO); let end_default = self.end.unwrap_or(len); let start = normalize_slice_index(start_default, len) .unwrap_or(Integer::ZERO) .max(Integer::ZERO); let end = normalize_slice_index(end_default, len) .unwrap_or(Integer::ZERO) .max(Integer::ZERO); let lower = start.min(len); let upper = end.min(len); (lower, upper) } #[inline] fn bounds_on_reverse_slice(&self, len: Integer) -> Option<(Integer, Integer)> { let start_default = self .start .or_else(|| len.checked_sub(Integer::from_i64_unchecked(1)))?; let end_default = self.end.or_else(|| { let l = len.checked_mul(Integer::from_i64_unchecked(-1))?; l.checked_sub(Integer::from_i64_unchecked(1)) })?; let start = normalize_slice_index(start_default, len) .unwrap_or(Integer::ZERO) .max(Integer::from_i64_unchecked(-1)); let end = normalize_slice_index(end_default, len) .unwrap_or(Integer::ZERO) .max(Integer::from_i64_unchecked(-1)); let lower = end.min( len.checked_sub(Integer::from_i64_unchecked(1)) .unwrap_or(len), ); let upper = start.min( len.checked_sub(Integer::from_i64_unchecked(1)) .unwrap_or(len), ); Some((lower, upper)) } } impl Queryable for Slice { #[cfg_attr(feature = "trace", tracing::instrument(name = "Query Slice", level = "trace", parent = None, ret))] fn query<'b>(&self, current: &'b Value, _root: &'b Value) -> Vec<&'b Value> { if let Some(list) = current.as_array() { let mut query = Vec::new(); let step = self.step.unwrap_or(Integer::from_i64_unchecked(1)); if step == 0 { return vec![]; } let Ok(len) = Integer::try_from(list.len()) else { return vec![]; }; if step > 0 { let (lower, upper) = self.bounds_on_forward_slice(len); let mut i = lower; while i < upper { if let Some(v) = usize::try_from(i).ok().and_then(|i| list.get(i)) { query.push(v); } i = if let Some(i) = i.checked_add(step) { i } else { break; }; } } else { let Some((lower, upper)) = self.bounds_on_reverse_slice(len) else { return vec![]; }; let mut i = upper; while lower < i { if let Some(v) = usize::try_from(i).ok().and_then(|i| list.get(i)) { query.push(v); } i = if let Some(i) = i.checked_add(step) { i } else { break; }; } } query } else { vec![] } } fn query_located<'b>( &self, current: &'b Value, _root: &'b Value, parent: NormalizedPath<'b>, ) -> Vec> { if let Some(list) = current.as_array() { let mut result = Vec::new(); let step = self.step.unwrap_or(Integer::from_i64_unchecked(1)); if step == 0 { return vec![]; } let Ok(len) = Integer::try_from(list.len()) else { return vec![]; }; if step > 0 { let (lower, upper) = self.bounds_on_forward_slice(len); let mut i = lower; while i < upper { if let Some((i, node)) = usize::try_from(i) .ok() .and_then(|i| list.get(i).map(|v| (i, v))) { result.push(LocatedNode { loc: parent.clone_and_push(i), node, }); } i = if let Some(i) = i.checked_add(step) { i } else { break; }; } } else { let Some((lower, upper)) = self.bounds_on_reverse_slice(len) else { return vec![]; }; let mut i = upper; while lower < i { if let Some((i, node)) = usize::try_from(i) .ok() .and_then(|i| list.get(i).map(|v| (i, v))) { result.push(LocatedNode { loc: parent.clone_and_push(i), node, }); } i = if let Some(i) = i.checked_add(step) { i } else { break; }; } } result } else { vec![] } } } fn normalize_slice_index(index: Integer, len: Integer) -> Option { if index >= 0 { Some(index) } else { len.checked_sub(index.abs()) } }