serde_json_path-0.7.2/.cargo_vcs_info.json0000644000000001550000000000100142120ustar { "git": { "sha1": "66283b5e2fa9d099439882c6afba395d5de7b9f3" }, "path_in_vcs": "serde_json_path" }serde_json_path-0.7.2/CHANGELOG.md000064400000000000000000000332601046102023000146160ustar 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.7.2 (2 February 2025) - **added**: better error message for invalid use of function in selector ([#118]) - **fixed**: properly format normalized path names ([#113], thanks [@theory]) - **internal**: fix clippy lints from 1.83.0 ([#110]) [#118]: https://github.com/hiltontj/serde_json_path/pull/118 [#113]: https://github.com/hiltontj/serde_json_path/pull/113 [@theory]: https://github.com/theory [#110]: https://github.com/hiltontj/serde_json_path/pull/110 # 0.7.1 (3 November 2024) - **internal**: update `serde_json` to the latest version ([#107]) - **fixed**: edge case where `.` in regexes for `match` and `search` functions was matching `\r\n` properly ([#92]) - **breaking**: added `regex` feature flag that gates regex functions `match` and `search` ([#93], thanks [@LucasPickering]) - Feature is enabled by default, but if you have `default-features = false` you'll need to explicitly add it to retain access to these functions - **breaking**(`serde_json_path_core`): 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]) [#92]: https://github.com/hiltontj/serde_json_path/pull/92 [#93]: https://github.com/hiltontj/serde_json_path/pull/93 [@LucasPickering]: https://github.com/LucasPickering [#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.6.7 (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.6.6 (23 February 2024) - **docs**: update links to refer to RFC 9535 ([#81]) [#81]: https://github.com/hiltontj/serde_json_path/pull/81 # 0.6.5 (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 ([#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.6.4 (9 November 2023) - **added**: `is_empty`, `is_more_than_one`, and `as_more_than_one` methods to `ExactlyOneError` ([#65]) - **fixed**: allow whitespace before dot-name selectors ([#67]) - **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.6.3 (17 September 2023) - **documentation**: Add line describing Descendant Operator ([#53]) - **documentation**: Improve example in Filter Selector section of main docs ([#54]) - **documentation**: Improve examples in Slice Selector section of main docs ([#55]) - **documentation**: Other improvements to documentation ([#56]) - **fixed**: Formulate the regex used by the `match` function to correctly handle regular expressions with leading or trailing `|` characters ([#61]) [#53]: https://github.com/hiltontj/serde_json_path/pull/53 [#54]: https://github.com/hiltontj/serde_json_path/pull/54 [#55]: https://github.com/hiltontj/serde_json_path/pull/55 [#56]: https://github.com/hiltontj/serde_json_path/pull/56 [#61]: https://github.com/hiltontj/serde_json_path/pull/61 # 0.6.2 (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.6.1 (5 July 2023) * **documentation**: Updated links to JSONPath specification to latest version (base 14) ([#43]) * **fixed**: Support newline characters in query strings where previously they were not being supported ([#44]) [#43]: https://github.com/hiltontj/serde_json_path/pull/43 [#44]: https://github.com/hiltontj/serde_json_path/pull/44 # 0.6.0 (2 April 2023) ## Function Extensions ([#32]) This release introduces the implementation of [Function Extensions][jpspec_func_ext] in `serde_json_path`. This release ships with support for the standard built-in functions that are part of the base JSONPath specification: - `length` - `count` - `match` - `search` - `value` These can now be used in your JSONPath query filter selectors, and are defined in the crate documentation in the `functions` module. In addition, the `#[function]` attribute macro was introduced to enable users of `serde_json_path` to define their own custom functions for use in their JSONPath queries. ### The `functions` module (**added**) In addition to the documentation/definitions for built-in functions, the `functions` module includes three new types: - `ValueType` - `NodesType` - `LogicalType` These reflect the type system defined in the JSONPath spec. Each is available through the public API, to be used in custom function definitions, along with the `#[function]` attribute macro. ### The `#[function]` attribute macro (**added**) A new attribute macro: `#[function]` was introduced to allow users of `serde_json_path` to define their own custom functions for use in their JSONPath queries. Along with the new types introduced by the `functions` module, it can be used like so: ```rust use serde_json_path::functions::{NodesType, ValueType}; /// A function that takes a node list, and optionally produces the first element as /// a value, if there are any elements in the list. #[serde_json_path::function] fn first(nodes: NodesType) -> ValueType { match nodes.first() { Some(v) => ValueType::Node(v), None => ValueType::Nothing, } } ``` Which will then allow you to use a `first` function in your JSONPath queries: ``` $[? first(@.*) > 5 ] ``` Usage of `first` in you JSONPath queries, like any of the built-in functions, will be validated at parse-time. The `#[function]` macro is gated behind the `functions` feature, which is enabled by default. Functions defined using the `#[function]` macro will override any of the built-in functions that are part of the standard, e.g., `length`, `count`, etc. ### Changed the `Error` type (**breaking**) The `Error` type was renamed to `ParseError` and was updated to have more concise error messages. It was refactored internally to better support future improvements to the parser. It is now a struct, vs. an enum, with a private implementation, and two core APIs: - `message()`: the parser error message - `position()`: indicate where the parser error was encountered in the JSONPath query string This gives far more concise errors than the pre-existing usage of `nom`'s built-in `VerboseError` type. However, for now, this leads to somewhat of a trade-off, in that errors that are not specially handled by the parser will present as just `"parser error"` with a position. Over time, the objective is to isolate cases where specific errors can be propagated up, and give better error messages. ### Repository switched to a workspace With this release, `serde_json_path` is split into four separate crates: - `serde_json_path` - `serde_json_path_macros` - `serde_json_path_macros_internal` - `serde_json_path_core` `serde_json_path` is still the entry point for general consumption. It still contains some of the key components of the API, e.g., `JsonPath`, `JsonPathExt`, and `Error`, as well as the entire `parser` module. However, many of the core types used to represent the JSONPath model, as defined in the specification, were moved into `serde_json_path_core`. This split was done to accommodate the new `#[function]` attribute macro, which is defined within the `serde_json_path_macros`/`macros_internal` crates, and discussed below. [#32]: https://github.com/hiltontj/serde_json_path/pull/32 [jpspec_func_ext]: https://www.ietf.org/archive/id/draft-ietf-jsonpath-base-14.html#name-function-extensions ## Other Changes - **added:** updated to latest version of CTS to ensure compliance [#33] - **added:** implement `Eq` for `JsonPath` [#34] - **breaking:**: Changed the name of `Error` type to `ParseError` [#36] [#33]: https://github.com/hiltontj/serde_json_path/pull/33 [#34]: https://github.com/hiltontj/serde_json_path/pull/34 [#36]: https://github.com/hiltontj/serde_json_path/pull/36 # 0.5.3 (14 March 2023) - **fixed:** Fix serialization behavior of `NodeList` ([#30]) [#30]: https://github.com/hiltontj/serde_json_path/pull/30 # 0.5.2 (13 March 2023) - **added:** Add `first`, `last`, and `get` methods to `NodeList` type ([#16]) - **changed:** Make `NodeList::at_most_one` and `NodeList::exactly_one` take `&self` instead of `self` ([#16]) - **docs:** Update crate-level docs to better reflect recent changes ([#21]) - **docs:** Corrected a broken link in crate-level docs ([#21]) - **added:** derive `Clone` for `JsonPath` and its descendants ([#24]) - **added:** derive `Default` for `JsonPath` ([#25]) - **added:** implement `Display` and `Serialize` for `JsonPath` ([#26]) [#16]: https://github.com/hiltontj/serde_json_path/pull/16 [#21]: https://github.com/hiltontj/serde_json_path/pull/21 [#24]: https://github.com/hiltontj/serde_json_path/pull/24 [#25]: https://github.com/hiltontj/serde_json_path/pull/25 [#26]: https://github.com/hiltontj/serde_json_path/pull/26 # 0.5.1 (11 March 2023) - **added:** Derive `PartialEq` on `JsonPath` ([#13]) - **added:** Add the `NodeList::at_most_one` method ([#13]) - **added:** Add the `NodeList::exactly_one` method ([#13]) - **deprecated:** Deprecate the `NodeList::one` method in favor of the new `NodeList::at_most_one` method ([#13]) [#13]: https://github.com/hiltontj/serde_json_path/pull/13 # 0.5.0 (10 March 2023) ## The `JsonPath` type - **added:** Add the `JsonPath` type ([#10]) `JsonPath` is a new struct that contains a parsed and valid JSON Path query, and can be re-used to query `serde_json::Value`s. ```rust let value = json!({"foo": [1, 2, 3]}); let path = JsonPath::parse("$.foo.*")?; let nodes = path.query(&value).all(); assert_eq!(nodes, vec![1, 2, 3]); ``` `JsonPath` implements `serde`'s `Deserialize`, which allows it to be used directly in serialized formats ```rust #[derive(Deserialize)] struct Config { pub path: JsonPath, } let config_json = json!({ "path": "$.foo.*" }); let config = from_value::(config_json).expect("deserializes"); let value = json!({"foo": [1, 2, 3]}); let nodes = config.path.query(&value).all(); assert_eq!(nodes, vec![1, 2, 3]); ``` `JsonPath` also implements `FromStr`, for convenience, e.g., ```rust let path = "$.foo.*".parse::()?; ``` ## Other changes - **breaking:** Alter the `JsonPathExt::json_path` API to accept a `&JsonPath` and to be infallible ([#10]) ```rust let value = json!({"foo": [1, 2, 3]}); let path = JsonPath::parse("$.foo.*")?; // <- note, this is fallible let nodes = value.json_path(&path).all(); // <- while this is not assert_eq!(nodes, vec![1, 2, 3]); ``` [#10]: https://github.com/hiltontj/serde_json_path/pull/10 # Previous Versions Previous versions are not documented here. serde_json_path-0.7.2/Cargo.lock0000644000000220350000000000100121660ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[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 = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "matchers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ "regex-automata 0.1.10", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[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 = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.9", "regex-syntax 0.8.5", ] [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ "regex-syntax 0.6.29", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax 0.8.5", ] [[package]] name = "regex-syntax" version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[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" version = "0.7.2" dependencies = [ "inventory", "nom", "regex", "serde", "serde_json", "serde_json_path_core", "serde_json_path_macros", "test-log", "thiserror", "tracing", "tracing-subscriber", ] [[package]] name = "serde_json_path_core" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde67d8dfe7d4967b5a95e247d4148368ddd1e753e500adb34b3ffe40c6bc1bc" dependencies = [ "inventory", "serde", "serde_json", "thiserror", "tracing", ] [[package]] name = "serde_json_path_macros" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "517acfa7f77ddaf5c43d5f119c44a683774e130b4247b7d3210f8924506cfac8" dependencies = [ "inventory", "serde_json_path_core", "serde_json_path_macros_internal", ] [[package]] name = "serde_json_path_macros_internal" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aafbefbe175fa9bf03ca83ef89beecff7d2a95aaacd5732325b90ac8c3bd7b90" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[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 = "test-log" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7f46083d221181166e5b6f6b1e5f1d499f3a76888826e6cb1d057554157cd0f" dependencies = [ "test-log-macros", "tracing-subscriber", ] [[package]] name = "test-log-macros" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "888d0c3c6db53c0fdab160d2ed5e12ba745383d3e85813f2ea0f2b1475ab553f" dependencies = [ "proc-macro2", "quote", "syn", ] [[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 = "thread_local" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", ] [[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 = "tracing-subscriber" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "once_cell", "regex", "sharded-slab", "thread_local", "tracing", "tracing-core", ] [[package]] name = "unicode-ident" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" serde_json_path-0.7.2/Cargo.toml0000644000000042420000000000100122110ustar # 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" version = "0.7.2" authors = ["Trevor Hilton "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Query serde_json Values using JSONPath" documentation = "https://docs.rs/serde_json_path" 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" path = "src/lib.rs" [[test]] name = "compliance" path = "tests/compliance.rs" [[test]] name = "functions" path = "tests/functions.rs" [[test]] name = "regressions" path = "tests/regressions.rs" [[test]] name = "serde" path = "tests/serde.rs" [[test]] name = "spec_examples" path = "tests/spec_examples.rs" [dependencies.inventory] version = "0.3.19" [dependencies.nom] version = "7.1.3" [dependencies.regex] version = "1.11.1" optional = true [dependencies.serde] version = "1.0.217" features = ["derive"] [dependencies.serde_json] version = "1.0.138" [dependencies.serde_json_path_core] version = "0.2.2" [dependencies.serde_json_path_macros] version = "0.1.6" [dependencies.thiserror] version = "2.0.11" [dependencies.tracing] version = "0.1.40" optional = true [dev-dependencies.test-log] version = "0.2.17" features = ["trace"] default-features = false [dev-dependencies.tracing-subscriber] version = "0.3.18" features = [ "env-filter", "fmt", ] default-features = false [features] default = [ "functions", "regex", ] functions = ["serde_json_path_core/functions"] regex = ["dep:regex"] trace = [ "dep:tracing", "serde_json_path_core/trace", ] serde_json_path-0.7.2/Cargo.toml.orig000064400000000000000000000021101046102023000156620ustar 00000000000000[package] name = "serde_json_path" version = "0.7.2" edition = "2021" license = "MIT" authors = ["Trevor Hilton "] description = "Query serde_json Values using JSONPath" documentation = "https://docs.rs/serde_json_path" repository = "https://github.com/hiltontj/serde_json_path" readme = "README.md" keywords = ["json", "jsonpath", "json_path", "serde", "serde_json"] [features] default = ["functions", "regex"] regex = ["dep:regex"] trace = ["dep:tracing", "serde_json_path_core/trace"] functions = ["serde_json_path_core/functions"] [dependencies] # local crates: serde_json_path_core = { path = "../serde_json_path_core", version = "0.2.2" } serde_json_path_macros = { path = "../serde_json_path_macros", version = "0.1.6" } # crates.io crates: inventory.workspace = true nom.workspace = true serde.workspace = true serde_json.workspace = true thiserror.workspace = true [dependencies.regex] workspace = true optional = true [dependencies.tracing] workspace = true optional = true [dev-dependencies] test-log.workspace = true tracing-subscriber.workspace = true serde_json_path-0.7.2/LICENSE-MIT000064400000000000000000000020561046102023000144400ustar 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-0.7.2/README.md000064400000000000000000000027001046102023000142570ustar 00000000000000# serde_json_path `serde_json_path` allows you to use JSONPath ([RFC 9535][rfc]) to query the [`serde_json::Value`][serde_json_value] type. [![Build status](https://github.com/hiltontj/serde_json_path/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/hiltontj/serde_json_path/actions/workflows/ci.yml) [![Crates.io](https://img.shields.io/crates/v/serde_json_path)](https://crates.io/crates/serde_json_path) [![Documentation](https://docs.rs/serde_json_path/badge.svg)][docs] [![CTS Submodule Update and Test](https://github.com/hiltontj/serde_json_path/actions/workflows/cts.yml/badge.svg)](https://github.com/hiltontj/serde_json_path/actions/workflows/cts.yml) ## Learn More * See the [Crate Documentation][docs] for usage and examples. * See the JSONPath standard ([RFC 9535][rfc]) for more details about JSONPath query syntax and examples of its usage. * Try it out in the [Sandbox](https://serdejsonpath.live) ## License This project is licensed under the [MIT license][license]. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in `serde_json_path` by you, shall be licensed as MIT, without any additional terms or conditions. [rfc]: https://www.rfc-editor.org/rfc/rfc9535.html [docs]: https://docs.rs/serde_json_path [serde_json_value]: https://docs.rs/serde_json/latest/serde_json/enum.Value.html [license]: https://github.com/hiltontj/serde_json_path/blob/main/LICENSE-MIT serde_json_path-0.7.2/src/error.rs000064400000000000000000000025321046102023000152710ustar 00000000000000use std::ops::Deref; use crate::parser::Error; /// Error type for JSONPath query string parsing errors #[derive(Debug, thiserror::Error)] #[error("{err}")] pub struct ParseError { err: Box, } impl ParseError { /// Get the 1-indexed error position pub fn position(&self) -> usize { self.err.position } /// Get the error message pub fn message(&self) -> &str { &self.err.message } } #[derive(Debug, thiserror::Error)] #[error("at position {position}, {message}")] struct ErrorImpl { position: usize, message: Box, } impl From<(I, Error)> for ParseError where I: Deref + std::fmt::Debug, { fn from((input, pe): (I, Error)) -> Self { #[cfg(feature = "trace")] tracing::trace!(input = %input.to_string(), parser_error = ?pe); let position = pe.calculate_position(input); let message = pe.to_string().into(); Self { err: Box::new(ErrorImpl { position, message }), } } } #[cfg(test)] mod tests { use crate::ParseError; #[cfg(feature = "trace")] use test_log::test; #[test] fn test_send() { fn assert_send() {} assert_send::(); } #[test] fn test_sync() { fn assert_sync() {} assert_sync::(); } } serde_json_path-0.7.2/src/ext.rs000064400000000000000000000014311046102023000147350ustar 00000000000000use serde_json::Value; use crate::{JsonPath, NodeList}; /// Extension trait that allows for JSONPath queries directly on [`serde_json::Value`] /// /// ## Usage /// ```rust /// use serde_json::json; /// use serde_json_path::{JsonPath, JsonPathExt}; /// /// # fn main() -> Result<(), serde_json_path::ParseError> { /// let value = json!({"foo": ["bar", "baz"]}); /// let query = JsonPath::parse("$.foo[*]")?; /// let nodes = value.json_path(&query).all(); /// assert_eq!(nodes, vec!["bar", "baz"]); /// # Ok(()) /// # } /// ``` pub trait JsonPathExt { /// Query a [`serde_json::Value`] with a JSONPath query string fn json_path(&self, path: &JsonPath) -> NodeList; } impl JsonPathExt for Value { fn json_path(&self, path: &JsonPath) -> NodeList { path.query(self) } } serde_json_path-0.7.2/src/lib.rs000064400000000000000000000413421046102023000147100ustar 00000000000000//! This crate allows you to use JSONPath queries to extract nodelists from a [`serde_json::Value`]. //! //! The crate intends to adhere to the IETF JSONPath standard ([RFC 9535][rfc]). Check out the //! specification to read more about JSONPath query syntax and to find many examples of its usage. //! //! [rfc]: https://www.rfc-editor.org/rfc/rfc9535.html //! //! # Features //! //! This crate provides three key abstractions: //! //! * The [`JsonPath`] struct, which represents a parsed JSONPath query. //! * The [`NodeList`] struct, which represents the result of a JSONPath query performed on a //! [`serde_json::Value`] using the [`JsonPath::query`] method. //! * The [`LocatedNodeList`] struct, which is similar to [`NodeList`], but includes the location //! of each node in the query string as a [`NormalizedPath`], and is produced by the //! [`JsonPath::query_located`] method. //! //! In addition, the [`JsonPathExt`] trait is provided, which extends the [`serde_json::Value`] //! type with the [`json_path`][JsonPathExt::json_path] method for performing JSONPath queries. //! //! Finally, the [`#[function]`][function] attribute macro can be used to extend JSONPath //! queries to use custom functions. //! //! # Usage //! //! ## Parsing //! //! JSONPath query strings can be parsed using the [`JsonPath`] type: //! //! ```rust //! use serde_json_path::JsonPath; //! //! # fn main() -> Result<(), serde_json_path::ParseError> { //! let path = JsonPath::parse("$.foo.bar")?; //! # Ok(()) //! # } //! ``` //! //! You then have two options to query a [`serde_json::Value`] using the parsed JSONPath: //! [`JsonPath::query`] or [`JsonPath::query_located`]. The former will produce a [`NodeList`], //! while the latter will produce a [`LocatedNodeList`]. The two options provide similar //! functionality, but it is recommended to use the former unless you have need of node locations //! in the query results. //! //! ## Querying for single nodes //! //! For queries that are expected to return a single node, use either the //! [`exactly_one`][NodeList::exactly_one] or the [`at_most_one`][NodeList::at_most_one] method. //! //! ```rust //! use serde_json::json; //! # use serde_json_path::JsonPath; //! //! # fn main() -> Result<(), Box> { //! let value = json!({ "foo": { "bar": ["baz", 42] } }); //! let path = JsonPath::parse("$.foo.bar[0]")?; //! let node = path.query(&value).exactly_one()?; //! assert_eq!(node, "baz"); //! # Ok(()) //! # } //! ``` //! //! JSONPath allows access via reverse indices: //! //! ```rust //! # use serde_json::json; //! # use serde_json_path::JsonPath; //! # fn main() -> Result<(), Box> { //! let value = json!([1, 2, 3, 4, 5]); //! let path = JsonPath::parse("$[-1]")?; //! let node = path.query(&value).at_most_one()?; //! assert_eq!(node, Some(&json!(5))); //! # Ok(()) //! # } //! ``` //! //! Keep in mind, that for simple queries, the [`serde_json::Value::pointer`] method may suffice. //! //! ## Querying for multiple nodes //! //! For queries that are expected to return zero or many nodes, use the [`all`][NodeList::all] //! method. There are several [selectors][rfc-selectors] in JSONPath whose combination can produce //! useful and powerful queries. //! //! [rfc-selectors]: https://www.rfc-editor.org/rfc/rfc9535.html#name-selectors-2 //! //! #### Wildcards (`*`) //! //! Wildcards select everything under a current node. They work on both arrays, by selecting all //! array elements, and on objects, by selecting all object key values: //! //! ```rust //! # use serde_json::json; //! # use serde_json_path::JsonPath; //! # fn main() -> Result<(), serde_json_path::ParseError> { //! let value = json!({ "foo": { "bar": ["baz", "bop"] } }); //! let path = JsonPath::parse("$.foo.bar[*]")?; //! let nodes = path.query(&value).all(); //! assert_eq!(nodes, vec!["baz", "bop"]); //! # Ok(()) //! # } //! ``` //! //! #### Slice selectors (`start:end:step`) //! //! Extract slices from JSON arrays using optional `start`, `end`, and `step` values. Reverse //! indices can be used for `start` and `end`, and a negative `step` can be used to traverse //! the array in reverse order. Consider the following JSON object, and subsequent examples: //! ```rust //! # use serde_json::json; //! # use serde_json_path::JsonPath; //! # fn main() -> Result<(), serde_json_path::ParseError> { //! let value = json!({ "foo": [1, 2, 3, 4, 5] }); //! # Ok(()) //! # } //! ``` //! `start`, `end`, and `step` are all optional: //! ```rust //! # use serde_json::json; //! # use serde_json_path::JsonPath; //! # fn main() -> Result<(), serde_json_path::ParseError> { //! # let value = json!({ "foo": [1, 2, 3, 4, 5] }); //! let path = JsonPath::parse("$.foo[:]")?; //! let nodes = path.query(&value).all(); //! assert_eq!(nodes, vec![1, 2, 3, 4, 5]); //! # Ok(()) //! # } //! ``` //! //! Omitting `end` will go to end of slice: //! ```rust //! # use serde_json::json; //! # use serde_json_path::JsonPath; //! # fn main() -> Result<(), serde_json_path::ParseError> { //! # let value = json!({ "foo": [1, 2, 3, 4, 5] }); //! let path = JsonPath::parse("$.foo[2:]")?; //! let nodes = path.query(&value).all(); //! assert_eq!(nodes, vec![3, 4, 5]); //! # Ok(()) //! # } //! ``` //! //! Omitting `start` will start from beginning of slice: //! ```rust //! # use serde_json::json; //! # use serde_json_path::JsonPath; //! # fn main() -> Result<(), serde_json_path::ParseError> { //! # let value = json!({ "foo": [1, 2, 3, 4, 5] }); //! let path = JsonPath::parse("$.foo[:2]")?; //! let nodes = path.query(&value).all(); //! assert_eq!(nodes, vec![1, 2]); //! # Ok(()) //! # } //! ``` //! //! You can specify the `step` size: //! ```rust //! # use serde_json::json; //! # use serde_json_path::JsonPath; //! # fn main() -> Result<(), serde_json_path::ParseError> { //! # let value = json!({ "foo": [1, 2, 3, 4, 5] }); //! let path = JsonPath::parse("$.foo[::2]")?; //! let nodes = path.query(&value).all(); //! assert_eq!(nodes, vec![1, 3, 5]); //! # Ok(()) //! # } //! ``` //! //! Or use a negative `step` to go in reverse: //! ```rust //! # use serde_json::json; //! # use serde_json_path::JsonPath; //! # fn main() -> Result<(), serde_json_path::ParseError> { //! # let value = json!({ "foo": [1, 2, 3, 4, 5] }); //! let path = JsonPath::parse("$.foo[::-1]")?; //! let nodes = path.query(&value).all(); //! assert_eq!(nodes, vec![5, 4, 3, 2, 1]); //! # Ok(()) //! # } //! ``` //! //! Finally, reverse indices can be used for `start` or `end`: //! ```rust //! # use serde_json::json; //! # use serde_json_path::JsonPath; //! # fn main() -> Result<(), serde_json_path::ParseError> { //! # let value = json!({ "foo": [1, 2, 3, 4, 5] }); //! let path = JsonPath::parse("$.foo[-2:]")?; //! let nodes = path.query(&value).all(); //! assert_eq!(nodes, vec![4, 5]); //! # Ok(()) //! # } //! ``` //! //! #### Filter expressions (`?`) //! //! [Filter selectors][rfc-filter-selectors] allow you to use logical expressions to evaluate which //! members in a JSON object or array will be selected. You can use the boolean `&&` and `||` //! operators as well as parentheses to group logical expressions in your filters. The current node //! (`@`) operator allows you to utilize the node being filtered in your filter logic: //! //! [rfc-filter-selectors]: https://www.rfc-editor.org/rfc/rfc9535.html#name-filter-selector //! //! ```rust //! # use serde_json::json; //! # use serde_json_path::JsonPath; //! # fn main() -> Result<(), serde_json_path::ParseError> { //! let value = json!({ "foo": [1, 2, 3, 4, 5] }); //! let path = JsonPath::parse("$.foo[?@ > 2 && @ < 5]")?; //! let nodes = path.query(&value).all(); //! assert_eq!(nodes, vec![3, 4]); //! # Ok(()) //! # } //! ``` //! //! You can form relative paths on the current node, as well as absolute paths on the root (`$`) //! node when writing filters: //! //! ```rust //! # use serde_json::json; //! # use serde_json_path::JsonPath; //! # fn main() -> Result<(), serde_json_path::ParseError> { //! let value = json!({ //! "threshold": 40, //! "readings": [ //! { "val": 35, "msg": "foo" }, //! { "val": 40, "msg": "bar" }, //! { "val": 42, "msg": "biz" }, //! { "val": 48, "msg": "bop" }, //! ] //! }); //! let path = JsonPath::parse("$.readings[? @.val > $.threshold ].msg")?; //! let nodes = path.query(&value).all(); //! assert_eq!(nodes, vec!["biz", "bop"]); //! # Ok(()) //! # } //! ``` //! //! Filters also allow you to make use of [functions] in your queries: //! //! ```rust //! # use serde_json::json; //! # use serde_json_path::JsonPath; //! # fn main() -> Result<(), serde_json_path::ParseError> { //! let value = json!([ //! "a short string", //! "a longer string", //! "an unnecessarily long string", //! ]); //! let path = JsonPath::parse("$[? length(@) < 20 ]")?; //! let nodes = path.query(&value).all(); //! assert_eq!(nodes, vec!["a short string", "a longer string"]); //! # Ok(()) //! # } //! ``` //! //! #### Descendant Operator (`..`) //! //! JSONPath query segments following a descendant operator (`..`) will visit the input node and each of its [descendants][rfc-descendants-def]. //! //! [rfc-descendants-def]: https://www.rfc-editor.org/rfc/rfc9535.html#section-1.1-7.28.1 //! //! ```rust //! # use serde_json::json; //! # use serde_json_path::JsonPath; //! # fn main() -> Result<(), serde_json_path::ParseError> { //! let value = json!({ //! "foo": { //! "bar": { //! "baz": 1 //! }, //! "baz": 2 //! }, //! "baz": 3, //! }); //! let path = JsonPath::parse("$.foo..baz")?; //! let nodes = path.query(&value).all(); //! assert_eq!(nodes, vec![2, 1]); //! # Ok(()) //! # } //! ``` //! //! ## Node locations and `NormalizedPath` //! //! Should you need to know the locations of the nodes produced by your queries, you can make use //! of the [`JsonPath::query_located`] method to perform the query. The resulting //! [`LocatedNodeList`] contains both the nodes produced by the query, as well as their locations //! represented by their [`NormalizedPath`]. //! //! ```rust //! # use serde_json::json; //! # use serde_json_path::JsonPath; //! # fn main() -> Result<(), Box> { //! let value = json!({ //! "foo": { //! "bar": { //! "baz": 1 //! }, //! "baz": 2 //! }, //! "baz": 3, //! }); //! let path = JsonPath::parse("$..[? @.baz == 1]")?; //! let location = path //! .query_located(&value) //! .exactly_one()? //! .location() //! .to_string(); //! assert_eq!(location, "$['foo']['bar']"); //! # Ok(()) //! # } //! ``` //! //! ## Feature Flags //! //! The following feature flags are supported: //! //! - `tracing` - Enable internal tracing via [tracing](https://docs.rs/tracing/latest/tracing/) //! - `functions` - Enable user-defined functions //! - `regex` - Enable the `match` and `search` functions #![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)] mod error; mod ext; mod parser; mod path; #[doc(inline)] pub use error::ParseError; #[doc(inline)] pub use ext::JsonPathExt; #[doc(inline)] pub use path::JsonPath; /// A list of nodes resulting from a JSONPath query, along with their locations /// /// This is produced by the [`JsonPath::query_located`] method. /// /// As with [`NodeList`], each node is a borrowed reference to the node in the original /// [`serde_json::Value`] that was queried; however, each node in the list is paired with its /// location, which is represented by a [`NormalizedPath`]. /// /// In addition to the locations, [`LocatedNodeList`] provides useful functionality over [`NodeList`] /// such as de-duplication of query results (see [`dedup`][LocatedNodeList::dedup]). pub use serde_json_path_core::node::LocatedNodeList; #[doc(inline)] pub use serde_json_path_core::node::{ AtMostOneError, ExactlyOneError, LocatedNode, Locations, NodeList, Nodes, }; /// Represents a [Normalized Path][norm-path] from the JSONPath specification /// /// A [`NormalizedPath`] is used to represent the location of a node within a query result /// produced by the [`JsonPath::query_located`] method. /// /// [norm-path]: https://www.rfc-editor.org/rfc/rfc9535.html#name-normalized-paths pub use serde_json_path_core::path::NormalizedPath; #[doc(inline)] pub use serde_json_path_core::path::PathElement; pub use serde_json_path_core::spec::functions; /// Register a function for use in JSONPath queries /// /// The `#[function]` attribute macro allows you to define your own functions for use in your /// JSONPath queries. /// /// Note that `serde_json_path` already provides the functions defined in the IETF JSONPath /// specification. You can find documentation for those in the [functions] module. /// /// # Usage /// /// ``` /// use serde_json::json; /// use serde_json_path::JsonPath; /// use serde_json_path::functions::{NodesType, ValueType}; /// /// // This will register a function called "first" that takes the first node from a nodelist and /// // returns a reference to that node, if it contains any nodes, or nothing otherwise: /// #[serde_json_path::function] /// fn first(nodes: NodesType) -> ValueType { /// match nodes.first() { /// Some(n) => ValueType::Node(n), /// None => ValueType::Nothing, /// } /// } /// /// // You can now use this function in your JSONPath queries: /// # fn main() -> Result<(), Box> { /// let value = json!([ /// [1, 2, 3], /// [4, 5, 6], /// [7, 8, 9], /// ]); /// let path = JsonPath::parse("$[? first(@.*) == 4 ]")?; /// let node = path.query(&value).exactly_one()?; /// assert_eq!(node, &json!([4, 5, 6])); /// # Ok(()) /// # } /// ``` /// /// # Compile-time Checking /// /// `#[function]` will ensure validity of your custom functions at compile time. The JSONPath type /// system is outlined in more detail in the [functions] module, but consists of the three types: /// [`NodesType`][functions::NodesType], [`ValueType`][functions::ValueType], and /// [`LogicalType`][functions::LogicalType]. /// /// When defining your own JSONPath functions, you must use only these types as arguments to your /// functions. In addition, your function must return one of these types. If you do not, the macro /// will produce a helpful compiler error. /// /// # Override function name using the `name` argument /// /// By default, the function name available in your JSONPath queries will be that of the function /// defined in your Rust code. However, you can specify the name that will be used from within your /// JSONPath queries using the `name` argument: /// /// ``` /// # use serde_json_path::JsonPath; /// # use serde_json_path::functions::{ValueType, LogicalType}; /// #[serde_json_path::function(name = "match")] /// fn match_func(node: ValueType, regexp: ValueType) -> LogicalType { /// /* ... */ /// # LogicalType::False /// } /// // You can then call this in your queries using "match" (instead of "match_func"): /// # fn main() -> Result<(), Box> { /// let path = JsonPath::parse("$[? match(@.foo, 'ba[rz]')]")?; /// # Ok(()) /// # } /// ``` /// /// # How it works /// /// In addition to type checking the function signature, the `#[function]` macro generates two /// pieces of code: a validator and an evaluator. /// /// The validator is used to ensure validity of function usage in your JSONPath query strings during /// parsing, and will produce errors when calling, e.g., [`JsonPath::parse`], if you have misused a /// function in your query. /// /// The evaluator is used to evaluate the function when using [`JsonPath::query`] to evaluate a /// JSONPath query against a [`serde_json::Value`]. /// /// The validator and evaluator are generated as lazily evaluated static closures and registered /// into a single function registry using the [`inventory`] crate. This means you can define your /// functions in any source file linked into your application. /// /// [`inventory`]: https://docs.rs/inventory/latest/inventory/ #[doc(inline)] #[cfg(feature = "functions")] pub use serde_json_path_macros::function; serde_json_path-0.7.2/src/parser/mod.rs000064400000000000000000000131331046102023000162120ustar 00000000000000use std::fmt::Display; use std::ops::Deref; use nom::character::complete::char; use nom::combinator::all_consuming; use nom::error::{ContextError, ErrorKind, FromExternalError, ParseError}; use nom::Offset; use nom::{branch::alt, combinator::map, multi::many0, sequence::preceded, IResult}; use serde_json_path_core::spec::query::{Query, QueryKind}; use serde_json_path_core::spec::segment::QuerySegment; use self::segment::parse_segment; pub(crate) mod primitive; pub(crate) mod segment; pub(crate) mod selector; pub(crate) mod utils; type PResult<'a, O> = IResult<&'a str, O, Error<&'a str>>; #[derive(Debug, PartialEq)] pub(crate) struct Error { pub(crate) errors: Vec>, } impl Error where I: Deref, { pub(crate) fn calculate_position(&self, input: I) -> usize { self.errors .first() .map(|e| input.offset(&e.input)) .unwrap_or(0) } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(e) = self.errors.first() { if let Some(ctx) = e.context { write!(f, "in {ctx}, ")?; } write!(f, "{err}", err = e.kind) } else { write!(f, "empty error") } } } impl std::error::Error for Error {} #[derive(Debug, PartialEq)] pub(crate) struct ParserErrorInner { input: I, kind: ParserErrorKind, context: Option<&'static str>, } #[derive(Debug, PartialEq, thiserror::Error)] pub(crate) enum ParserErrorKind { #[error("{0}")] Message(Box), #[error("parser error")] Nom(ErrorKind), } impl ParseError for Error { fn from_error_kind(input: I, kind: ErrorKind) -> Self { Self { errors: vec![ParserErrorInner { input, kind: ParserErrorKind::Nom(kind), context: None, }], } } fn append(input: I, kind: ErrorKind, mut other: Self) -> Self { other.errors.push(ParserErrorInner { input, kind: ParserErrorKind::Nom(kind), context: None, }); other } } impl ContextError for Error { fn add_context(_input: I, ctx: &'static str, mut other: Self) -> Self { if let Some(e) = other.errors.first_mut() { e.context = Some(ctx); }; other } } impl FromExternalError for Error where E: std::error::Error + Display, { fn from_external_error(input: I, _kind: ErrorKind, e: E) -> Self { Self { errors: vec![ParserErrorInner { input, kind: ParserErrorKind::Message(e.to_string().into()), context: None, }], } } } pub(crate) trait FromInternalError { fn from_internal_error(input: I, e: E) -> Self; } impl FromInternalError for Error where E: std::error::Error + Display, { fn from_internal_error(input: I, e: E) -> Self { Self { errors: vec![ParserErrorInner { input, kind: ParserErrorKind::Message(e.to_string().into()), context: None, }], } } } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_path_segments(input: &str) -> PResult> { many0(parse_segment)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_root_query(input: &str) -> PResult { map(preceded(char('$'), parse_path_segments), |segments| Query { kind: QueryKind::Root, segments, })(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_current_query(input: &str) -> PResult { map(preceded(char('@'), parse_path_segments), |segments| Query { kind: QueryKind::Current, segments, })(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_query(input: &str) -> PResult { alt((parse_root_query, parse_current_query))(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_query_main(input: &str) -> PResult { all_consuming(parse_root_query)(input) } #[cfg(test)] mod tests { use serde_json_path_core::spec::{ query::QueryKind, segment::Segment, selector::{name::Name, Selector}, }; use super::{parse_query, parse_query_main}; #[test] fn root_path() { { let (_, p) = parse_query("$").unwrap(); assert!(matches!(p.kind, QueryKind::Root)); } { let (_, p) = parse_query("$.name").unwrap(); assert_eq!(p.segments[0].segment.as_dot_name().unwrap(), "name"); } { let (_, p) = parse_query("$.names['first_name']..*").unwrap(); assert_eq!(p.segments[0].segment.as_dot_name().unwrap(), "names"); let clh = p.segments[1].segment.as_long_hand().unwrap(); assert!(matches!(&clh[0], Selector::Name(Name(s)) if s == "first_name")); assert!(matches!(p.segments[2].segment, Segment::Wildcard)); } } #[test] fn current_path() { { let (_, p) = parse_query("@").unwrap(); assert!(matches!(p.kind, QueryKind::Current)); } } #[test] fn no_tail() { assert!(parse_query_main("$.a['b']tail").is_err()); } } serde_json_path-0.7.2/src/parser/primitive/int.rs000064400000000000000000000037171046102023000202440ustar 00000000000000use nom::character::complete::char; use nom::{ branch::alt, bytes::complete::{tag, take_while_m_n}, character::complete::digit0, combinator::{map_res, opt, recognize}, sequence::tuple, }; use serde_json_path_core::spec::integer::Integer; use crate::parser::PResult; #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_zero(input: &str) -> PResult<&str> { tag("0")(input) } fn is_non_zero_digit(chr: char) -> bool { ('1'..='9').contains(&chr) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_non_zero_first_digit(input: &str) -> PResult<&str> { take_while_m_n(1, 1, is_non_zero_digit)(input) } /// Parse a non-zero integer as `i64` /// /// This does not allow leading `0`'s, e.g., `0123` #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_non_zero_int(input: &str) -> PResult<&str> { recognize(tuple((opt(char('-')), parse_non_zero_first_digit, digit0)))(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_int_string(input: &str) -> PResult<&str> { alt((parse_zero, parse_non_zero_int))(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_int(input: &str) -> PResult { map_res(parse_int_string, |i_str| i_str.parse())(input) } #[cfg(test)] mod tests { use serde_json_path_core::spec::integer::Integer; use crate::parser::primitive::int::parse_int; #[test] fn parse_integers() { assert_eq!(parse_int("0"), Ok(("", Integer::from_i64_unchecked(0)))); assert_eq!(parse_int("10"), Ok(("", Integer::from_i64_unchecked(10)))); assert_eq!(parse_int("-10"), Ok(("", Integer::from_i64_unchecked(-10)))); assert_eq!(parse_int("010"), Ok(("10", Integer::from_i64_unchecked(0)))); } } serde_json_path-0.7.2/src/parser/primitive/mod.rs000064400000000000000000000011701046102023000202200ustar 00000000000000use nom::{branch::alt, bytes::complete::tag, combinator::value}; use super::PResult; pub(crate) mod int; pub(crate) mod number; pub(crate) mod string; #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_null(input: &str) -> PResult<()> { value((), tag("null"))(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_bool(input: &str) -> PResult { let parse_true = value(true, tag("true")); let parse_false = value(false, tag("false")); alt((parse_true, parse_false))(input) } serde_json_path-0.7.2/src/parser/primitive/number.rs000064400000000000000000000034631046102023000207400ustar 00000000000000use std::str::FromStr; use nom::{ branch::alt, bytes::complete::tag, character::complete::{char, digit0, digit1, one_of}, combinator::{map_res, opt, recognize}, sequence::{preceded, tuple}, }; use serde_json::Number; use crate::parser::PResult; use super::int::parse_int_string; #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_fractional(input: &str) -> PResult<&str> { preceded(char('.'), digit1)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_exponent(input: &str) -> PResult<&str> { recognize(tuple((one_of("eE"), opt(one_of("-+")), digit0)))(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_number_string(input: &str) -> PResult<&str> { recognize(tuple(( alt((parse_int_string, tag("-0"))), opt(parse_fractional), opt(parse_exponent), )))(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_number(input: &str) -> PResult { map_res(parse_number_string, Number::from_str)(input) } #[cfg(test)] mod tests { use serde_json::Number; use super::parse_number; #[test] fn test_numbers() { assert_eq!(parse_number("123"), Ok(("", Number::from(123)))); assert_eq!(parse_number("-1"), Ok(("", Number::from(-1)))); assert_eq!( parse_number("1e10"), Ok(("", Number::from_f64(1e10).unwrap())) ); assert_eq!( parse_number("1.0001"), Ok(("", Number::from_f64(1.0001).unwrap())) ); assert_eq!( parse_number("-0"), Ok(("", Number::from_f64(-0.0).unwrap())) ); } } serde_json_path-0.7.2/src/parser/primitive/string.rs000064400000000000000000000204761046102023000207610ustar 00000000000000use nom::bytes::complete::{tag, take_while}; use nom::character::complete::{anychar, char}; use nom::character::streaming::one_of; use nom::combinator::{cut, map, recognize, verify}; use nom::error::context; use nom::sequence::{pair, separated_pair, tuple}; use nom::{ branch::alt, bytes::complete::take_while_m_n, combinator::{map_opt, map_res, value}, multi::fold_many0, sequence::{delimited, preceded}, }; use crate::parser::utils::cut_with; use crate::parser::PResult; #[derive(Debug, Copy, Clone)] enum Quotes { Single, Double, } fn is_digit(chr: &char) -> bool { chr.is_ascii_digit() } fn is_hex_digit(chr: char) -> bool { is_digit(&chr) || ('A'..='F').contains(&chr) || ('a'..='f').contains(&chr) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_digit(input: &str) -> PResult { verify(anychar, is_digit)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None))] fn parse_n_hex_digits(n: usize) -> impl Fn(&str) -> PResult<&str> { move |input: &str| take_while_m_n(n, n, is_hex_digit)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_non_surrogate(input: &str) -> PResult { let non_d_base = alt((parse_digit, one_of("ABCEFabcdef"))); let non_d_based = pair(non_d_base, parse_n_hex_digits(3)); let zero_to_7 = verify(anychar, |c: &char| ('0'..='7').contains(c)); let d_based = tuple((one_of("Dd"), zero_to_7, parse_n_hex_digits(2))); let parse_u32 = map_res(alt((recognize(non_d_based), recognize(d_based))), |hex| { u32::from_str_radix(hex, 16) }); context("non surrogate", map_opt(parse_u32, char::from_u32))(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_low_surrogate(input: &str) -> PResult { context( "low surrogate", map_res( recognize(tuple(( one_of("Dd"), one_of("CDEFcdef"), parse_n_hex_digits(2), ))), |hex| u16::from_str_radix(hex, 16), ), )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_high_surrogate(input: &str) -> PResult { context( "high surrogate", map_res( recognize(tuple((char('D'), one_of("89AB"), parse_n_hex_digits(2)))), |hex| u16::from_str_radix(hex, 16), ), )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_surrogate(input: &str) -> PResult { context( "surrogate pair", map_res( separated_pair(parse_high_surrogate, tag("\\u"), parse_low_surrogate), |(h, l)| String::from_utf16(&[h, l]), ), )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_hex_char(input: &str) -> PResult { alt((map(parse_non_surrogate, String::from), parse_surrogate))(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_unicode_sequence(input: &str) -> PResult { context("unicode sequence", preceded(char('u'), parse_hex_char))(input) } fn parse_escaped_quote(quoted_with: Quotes) -> impl Fn(&str) -> PResult { move |input: &str| match quoted_with { Quotes::Single => value('\u{0027}', char('\''))(input), Quotes::Double => value('\u{0022}', char('"'))(input), } } fn parse_escaped_char(quoted_with: Quotes) -> impl Fn(&str) -> PResult { move |input: &str| { context( "escaped character", preceded( char('\\'), alt(( map( alt(( value('\u{0008}', char('b')), value('\u{0009}', char('t')), value('\u{000A}', char('n')), value('\u{000C}', char('f')), value('\u{000D}', char('r')), value('\u{002F}', char('/')), value('\u{005C}', char('\\')), parse_escaped_quote(quoted_with), )), String::from, ), parse_unicode_sequence, )), ), )(input) } } fn is_valid_unescaped_char(chr: char, quoted_with: Quotes) -> bool { let invalid_quote_char = match quoted_with { Quotes::Single => '\'', Quotes::Double => '"', }; if chr == invalid_quote_char { return false; } match chr { '\u{20}'..='\u{5B}' // Omit control characters | '\u{5D}'..='\u{10FFFF}' => true, // Omit \ _ => false, } } fn parse_unescaped(quoted_with: Quotes) -> impl Fn(&str) -> PResult<&str> { move |input: &str| { context( "unescaped character", verify( take_while(|chr| is_valid_unescaped_char(chr, quoted_with)), |s: &str| !s.is_empty(), ), )(input) } } fn parse_fragment(quoted_with: Quotes) -> impl Fn(&str) -> PResult { move |input: &str| { alt(( map(parse_unescaped(quoted_with), String::from), parse_escaped_char(quoted_with), ))(input) } } fn parse_internal(quoted_with: Quotes) -> impl Fn(&str) -> PResult { move |input: &str| { fold_many0( parse_fragment(quoted_with), String::new, |mut string, fragment| { string.push_str(fragment.as_str()); string }, )(input) } } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_single_quoted(input: &str) -> PResult { context( "single quoted", delimited( char('\''), parse_internal(Quotes::Single), cut_with(char('\''), |_| StringError::ExpectedEndQuote), ), )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_double_quoted(input: &str) -> PResult { context( "double quoted", delimited(char('"'), parse_internal(Quotes::Double), cut(char('"'))), )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_string_literal(input: &str) -> PResult { context( "string literal", alt((parse_single_quoted, parse_double_quoted)), )(input) } #[derive(Debug, thiserror::Error)] pub(crate) enum StringError { #[error("expected an ending quote")] ExpectedEndQuote, } #[cfg(test)] mod tests { use crate::parser::primitive::string::{parse_escaped_char, Quotes}; use super::parse_string_literal; #[test] fn valid_double_quoted_selectors() { assert_eq!( parse_string_literal("\"test\""), Ok(("", String::from("test"))) ); assert_eq!( parse_string_literal("\"test\\n\""), Ok(("", String::from("test\n"))) ); assert_eq!( parse_string_literal("\"test\\ntest\""), Ok(("", String::from("test\ntest"))) ); assert_eq!( parse_string_literal("\"test\\\"\""), Ok(("", String::from("test\""))) ); assert_eq!( parse_string_literal("\"tes't\""), Ok(("", String::from("tes't"))) ); } #[test] fn valid_single_quoted_selectors() { assert_eq!( parse_string_literal("'test'"), Ok(("", String::from("test"))) ); assert_eq!( parse_string_literal(r#"'te"st'"#), Ok(("", String::from("te\"st"))) ); assert_eq!( parse_string_literal(r"'te\'st'"), Ok(("", String::from("te'st"))) ); } #[test] fn invalid_unicode() { { for c in '\u{00}'..'\u{20}' { let input = format!("{c}"); let result = parse_escaped_char(Quotes::Double)(&input); assert!(result.is_err()); } } } } serde_json_path-0.7.2/src/parser/segment.rs000064400000000000000000000210401046102023000170710ustar 00000000000000use nom::bytes::complete::tag; use nom::character::complete::char; use nom::error::context; use nom::sequence::terminated; use nom::{ branch::alt, bytes::complete::take_while1, character::complete::{alpha1, digit1, multispace0}, combinator::{map, recognize}, multi::{fold_many0, separated_list1}, sequence::{delimited, pair, preceded}, }; use serde_json_path_core::spec::segment::{QuerySegment, QuerySegmentKind, Segment}; use serde_json_path_core::spec::selector::Selector; use super::selector::{parse_selector, parse_wildcard_selector}; use super::utils::cut_with; use super::PResult; // The specification requires that a non-ASCII character is in the range // %x80-10FFFF. In Rust, the `char` type can not hold characters higher // than %x10FFFF, so we only need to check the lower bound in this function. // // See: https://doc.rust-lang.org/std/primitive.char.html#validity fn is_non_ascii_unicode(chr: char) -> bool { chr >= '\u{0080}' } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_non_ascii_unicode(input: &str) -> PResult<&str> { take_while1(is_non_ascii_unicode)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_name_first(input: &str) -> PResult<&str> { alt((alpha1, recognize(char('_')), parse_non_ascii_unicode))(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_name_char(input: &str) -> PResult<&str> { alt((digit1, parse_name_first))(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_dot_member_name(input: &str) -> PResult { map( recognize(pair( cut_with(parse_name_first, |_| { SegmentError::InvalidFirstNameCharacter }), fold_many0(parse_name_char, String::new, |mut s, item| { s.push_str(item); s }), )), |s| s.to_owned(), )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_dot_member_name_shorthand(input: &str) -> PResult { map( preceded(char('.'), context("dot member name", parse_dot_member_name)), Segment::DotName, )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_multi_selector(input: &str) -> PResult> { separated_list1( delimited(multispace0, char(','), multispace0), parse_selector, )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_child_long_hand(input: &str) -> PResult { context( "long-hand segment", preceded( pair(char('['), multispace0), terminated( map(parse_multi_selector, Segment::LongHand), pair( multispace0, cut_with(char(']'), |_| SegmentError::ExpectedClosingBrace), ), ), ), )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_dot_wildcard_shorthand(input: &str) -> PResult { map(preceded(char('.'), parse_wildcard_selector), |_| { Segment::Wildcard })(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_child_segment(input: &str) -> PResult { preceded( multispace0, alt(( parse_dot_wildcard_shorthand, parse_dot_member_name_shorthand, parse_child_long_hand, )), )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_descendant_segment(input: &str) -> PResult { preceded( tag(".."), alt(( map(parse_wildcard_selector, |_| Segment::Wildcard), parse_child_long_hand, map(parse_dot_member_name, Segment::DotName), )), )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_segment(input: &str) -> PResult { alt(( map(parse_descendant_segment, |inner| QuerySegment { kind: QuerySegmentKind::Descendant, segment: inner, }), map(parse_child_segment, |inner| QuerySegment { kind: QuerySegmentKind::Child, segment: inner, }), ))(input) } #[doc(hidden)] #[derive(Debug, thiserror::Error)] pub(crate) enum SegmentError { #[error("must start with lowercase alpha or '_'")] InvalidFirstNameCharacter, #[error("expected closing ']'")] ExpectedClosingBrace, } #[cfg(test)] mod tests { #[cfg(feature = "trace")] use test_log::test; use nom::combinator::all_consuming; use serde_json_path_core::spec::{ integer::Integer, selector::{index::Index, name::Name, slice::Slice, Selector}, }; use super::{ parse_child_long_hand, parse_child_segment, parse_descendant_segment, parse_dot_member_name_shorthand, Segment, }; #[test] fn dot_member_names() { assert!(matches!( parse_dot_member_name_shorthand(".name"), Ok(("", Segment::DotName(s))) if s == "name", )); assert!(matches!( parse_dot_member_name_shorthand(".foo_bar"), Ok(("", Segment::DotName(s))) if s == "foo_bar", )); assert!(parse_dot_member_name_shorthand(". space").is_err()); assert!(all_consuming(parse_dot_member_name_shorthand)(".no-dash").is_err()); assert!(parse_dot_member_name_shorthand(".1no_num_1st").is_err()); } #[test] fn child_long_hand() { { let (_, sk) = parse_child_long_hand(r#"["name"]"#).unwrap(); let s = sk.as_long_hand().unwrap(); assert_eq!(s[0], Selector::Name(Name::from("name"))); } { let (_, sk) = parse_child_long_hand(r#"['name']"#).unwrap(); let s = sk.as_long_hand().unwrap(); assert_eq!(s[0], Selector::Name(Name::from("name"))); } { let (_, sk) = parse_child_long_hand(r#"["name","test"]"#).unwrap(); let s = sk.as_long_hand().unwrap(); assert_eq!(s[0], Selector::Name(Name::from("name"))); assert_eq!(s[1], Selector::Name(Name::from("test"))); } { let (_, sk) = parse_child_long_hand(r#"['name',10,0:3]"#).unwrap(); let s = sk.as_long_hand().unwrap(); assert_eq!(s[0], Selector::Name(Name::from("name"))); assert_eq!( s[1], Selector::Index(Index(Integer::from_i64_unchecked(10))) ); assert_eq!( s[2], Selector::ArraySlice(Slice::new().with_start(0).with_end(3)) ); } { let (_, sk) = parse_child_long_hand(r#"[::,*]"#).unwrap(); let s = sk.as_long_hand().unwrap(); assert_eq!(s[0], Selector::ArraySlice(Slice::new())); assert_eq!(s[1], Selector::Wildcard); } { let err = parse_child_long_hand("[010]").unwrap_err(); match err { nom::Err::Error(e) | nom::Err::Failure(e) => println!("{e:#?}"), nom::Err::Incomplete(_) => panic!("wrong error kind: {err:?}"), } } } #[test] fn child_segment() { { let (_, sk) = parse_child_segment(".name").unwrap(); assert_eq!(sk.as_dot_name(), Some("name")); } { let (_, sk) = parse_child_segment(".*").unwrap(); assert!(matches!(sk, Segment::Wildcard)); } { let (_, sk) = parse_child_segment("[*]").unwrap(); let s = sk.as_long_hand().unwrap(); assert_eq!(s[0], Selector::Wildcard); } } #[test] fn descendant_segment() { { let (_, sk) = parse_descendant_segment("..['name']").unwrap(); let s = sk.as_long_hand().unwrap(); assert_eq!(s[0], Selector::Name(Name::from("name"))); } { let (_, sk) = parse_descendant_segment("..name").unwrap(); assert_eq!(sk.as_dot_name().unwrap(), "name"); } { let (_, sk) = parse_descendant_segment("..*").unwrap(); assert!(matches!(sk, Segment::Wildcard)); } } } serde_json_path-0.7.2/src/parser/selector/filter.rs000064400000000000000000000235401046102023000205430ustar 00000000000000use nom::character::complete::{char, multispace0}; use nom::combinator::{cut, map, map_res}; use nom::multi::separated_list1; use nom::sequence::{delimited, pair, preceded, separated_pair, tuple}; use nom::{branch::alt, bytes::complete::tag, combinator::value}; use serde_json_path_core::spec::functions::{ FunctionArgType, FunctionExpr, FunctionValidationError, Validated, }; use serde_json_path_core::spec::selector::filter::{ BasicExpr, Comparable, ComparisonExpr, ComparisonOperator, ExistExpr, Filter, Literal, LogicalAndExpr, LogicalOrExpr, SingularQuery, }; use super::function::parse_function_expr; use crate::parser::primitive::number::parse_number; use crate::parser::primitive::string::parse_string_literal; use crate::parser::primitive::{parse_bool, parse_null}; use crate::parser::utils::uncut; use crate::parser::{parse_query, PResult}; #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_filter(input: &str) -> PResult { map( preceded(pair(char('?'), multispace0), parse_logical_or_expr), Filter, )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_logical_and(input: &str) -> PResult { map( separated_list1( tuple((multispace0, tag("&&"), multispace0)), parse_basic_expr, ), LogicalAndExpr, )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_logical_or_expr(input: &str) -> PResult { map( separated_list1( tuple((multispace0, tag("||"), multispace0)), parse_logical_and, ), LogicalOrExpr, )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_exist_expr_inner(input: &str) -> PResult { map(parse_query, ExistExpr)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_exist_expr(input: &str) -> PResult { map(parse_exist_expr_inner, BasicExpr::Exist)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_not_exist_expr(input: &str) -> PResult { map( preceded(pair(char('!'), multispace0), parse_exist_expr_inner), BasicExpr::NotExist, )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_func_expr_inner(input: &str) -> PResult> { cut(map_res(parse_function_expr, |fe| match fe.return_type { FunctionArgType::Logical | FunctionArgType::Nodelist => Ok(fe), _ => Err(FunctionValidationError::IncorrectFunctionReturnType), }))(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_func_expr(input: &str) -> PResult { map(parse_func_expr_inner, BasicExpr::FuncExpr)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_not_func_expr(input: &str) -> PResult { map( preceded(pair(char('!'), multispace0), parse_func_expr_inner), BasicExpr::NotFuncExpr, )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_paren_expr_inner(input: &str) -> PResult { delimited( pair(char('('), multispace0), parse_logical_or_expr, pair(multispace0, char(')')), )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_paren_expr(input: &str) -> PResult { map(parse_paren_expr_inner, BasicExpr::Paren)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_not_parent_expr(input: &str) -> PResult { map( preceded(pair(char('!'), multispace0), parse_paren_expr_inner), BasicExpr::NotParen, )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_basic_expr(input: &str) -> PResult { alt(( parse_not_parent_expr, parse_paren_expr, map(parse_comp_expr, BasicExpr::Relation), parse_not_exist_expr, parse_exist_expr, parse_not_func_expr, parse_func_expr, ))(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_comp_expr(input: &str) -> PResult { map( separated_pair( parse_comparable, multispace0, separated_pair(parse_comparison_operator, multispace0, parse_comparable), ), |(left, (op, right))| ComparisonExpr { left, op, right }, )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_comparison_operator(input: &str) -> PResult { alt(( value(ComparisonOperator::EqualTo, tag("==")), value(ComparisonOperator::NotEqualTo, tag("!=")), value(ComparisonOperator::LessThanEqualTo, tag("<=")), value(ComparisonOperator::GreaterThanEqualTo, tag(">=")), value(ComparisonOperator::LessThan, char('<')), value(ComparisonOperator::GreaterThan, char('>')), ))(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_literal(input: &str) -> PResult { alt(( map(parse_string_literal, Literal::String), map(parse_number, Literal::Number), map(parse_bool, Literal::Bool), value(Literal::Null, parse_null), ))(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_literal_comparable(input: &str) -> PResult { map(parse_literal, Comparable::Literal)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_singular_path(input: &str) -> PResult { map_res(parse_query, |q| q.try_into())(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_singular_path_comparable(input: &str) -> PResult { map(parse_singular_path, Comparable::SingularQuery)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_function_expr_comparable(input: &str) -> PResult { map_res(parse_function_expr, |fe| { match fe.return_type { FunctionArgType::Value => Ok(fe), _ => Err(FunctionValidationError::IncorrectFunctionReturnType), } .map(Comparable::FunctionExpr) })(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_comparable(input: &str) -> PResult { uncut(alt(( parse_literal_comparable, parse_singular_path_comparable, parse_function_expr_comparable, )))(input) } #[cfg(test)] mod tests { #[cfg(feature = "trace")] use test_log::test; use serde_json::Number; use serde_json_path_core::spec::selector::filter::{Comparable, Literal, SingularQuerySegment}; use crate::parser::selector::{ filter::{parse_literal, ComparisonOperator}, Index, Name, }; use super::{parse_basic_expr, parse_comp_expr, parse_comparable}; #[test] fn literals() { { let (_, lit) = parse_literal("null").unwrap(); assert!(matches!(lit, Literal::Null)); } { let (_, lit) = parse_literal("true").unwrap(); assert!(matches!(lit, Literal::Bool(true))); } { let (_, lit) = parse_literal("false").unwrap(); assert!(matches!(lit, Literal::Bool(false))); } { let (_, lit) = parse_literal("\"test\"").unwrap(); assert!(matches!(lit, Literal::String(s) if s == "test")); } { let (_, lit) = parse_literal("'test'").unwrap(); assert!(matches!(lit, Literal::String(s) if s == "test")); } { let (_, lit) = parse_literal("123").unwrap(); assert!(matches!(lit, Literal::Number(n) if n == Number::from(123))); } } #[test] fn comp_expr() { let (_, cxp) = parse_comp_expr("true != false").unwrap(); assert!(matches!(cxp.left, Comparable::Literal(Literal::Bool(true)))); assert!(matches!(cxp.op, ComparisonOperator::NotEqualTo)); assert!(matches!( cxp.right, Comparable::Literal(Literal::Bool(false)) )); } #[test] fn basic_expr() { let (_, bxp) = parse_basic_expr("true == true").unwrap(); let cx = bxp.as_relation().unwrap(); assert!(matches!(cx.left, Comparable::Literal(Literal::Bool(true)))); assert!(matches!(cx.right, Comparable::Literal(Literal::Bool(true)))); assert!(matches!(cx.op, ComparisonOperator::EqualTo)); } #[test] fn singular_path_comparables() { { let (_, cmp) = parse_comparable("@.name").unwrap(); let sp = &cmp.as_singular_path().unwrap().segments; assert!(matches!(&sp[0], SingularQuerySegment::Name(Name(s)) if s == "name")); } { let (_, cmp) = parse_comparable("$.data[0].id").unwrap(); let sp = &cmp.as_singular_path().unwrap().segments; assert!(matches!(&sp[0], SingularQuerySegment::Name(Name(s)) if s == "data")); assert!(matches!(&sp[1], SingularQuerySegment::Index(Index(i)) if i == &0)); assert!(matches!(&sp[2], SingularQuerySegment::Name(Name(s)) if s == "id")); } } } serde_json_path-0.7.2/src/parser/selector/function/mod.rs000064400000000000000000000072051046102023000216620ustar 00000000000000use nom::character::complete::char; use nom::combinator::{cut, map_res}; use nom::multi::separated_list0; use nom::sequence::{preceded, terminated}; use nom::{ branch::alt, character::complete::{multispace0, satisfy}, combinator::map, multi::fold_many1, sequence::{delimited, pair}, }; use serde_json_path_core::spec::functions::{ Function, FunctionExpr, FunctionExprArg, FunctionValidationError, Validated, }; pub(crate) mod registry; use crate::parser::{parse_query, PResult}; use self::registry::REGISTRY; use super::filter::{parse_literal, parse_logical_or_expr, parse_singular_path}; #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_function_name_first(input: &str) -> PResult { satisfy(|c| c.is_ascii_lowercase())(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_function_name_char(input: &str) -> PResult { alt(( parse_function_name_first, char('_'), satisfy(|c| c.is_ascii_digit()), ))(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_function_name(input: &str) -> PResult { map( pair( parse_function_name_first, fold_many1( parse_function_name_char, String::new, |mut string, fragment| { string.push(fragment); string }, ), ), |(first, rest)| format!("{first}{rest}"), )(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_function_argument(input: &str) -> PResult { alt(( map(parse_literal, FunctionExprArg::Literal), map(parse_singular_path, FunctionExprArg::SingularQuery), map(parse_query, FunctionExprArg::FilterQuery), map(parse_function_expr, FunctionExprArg::FunctionExpr), map(parse_logical_or_expr, FunctionExprArg::LogicalExpr), ))(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_function_expr(input: &str) -> PResult> { cut(map_res( pair( parse_function_name, delimited( terminated(char('('), multispace0), separated_list0( delimited(multispace0, char(','), multispace0), parse_function_argument, ), preceded(multispace0, char(')')), ), ), |(name, args)| { #[cfg(feature = "functions")] 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, }, }); } } if let Some(f) = REGISTRY.get(name.as_str()) { (f.validator)(args.as_slice())?; return Ok(FunctionExpr { name, args, return_type: f.result_type, validated: Validated { evaluator: f.evaluator, }, }); } Err(FunctionValidationError::Undefined { name }) }, ))(input) } serde_json_path-0.7.2/src/parser/selector/function/registry.rs000064400000000000000000000057121046102023000227540ustar 00000000000000use std::{collections::HashMap, sync::LazyLock}; use serde_json::Value; use serde_json_path_core::spec::functions::{Function, LogicalType, NodesType, ValueType}; /// The main registry of functions for use in JSONPath queries /// /// These come directly from the JSONPath specification, which includes a registry of standardized /// functions. /// /// # Note /// /// There is a function in `serde_json_path_core/src/spec/functions.rs` that gives /// the return type for each function registered here. When adding new functions to /// the register, i.e., when new functions are standardized, the function there needs /// to be updated too. pub(crate) static REGISTRY: LazyLock> = LazyLock::new(|| { let mut m = HashMap::new(); m.insert("length", &LENGTH_FUNC); m.insert("count", &COUNT_FUNC); #[cfg(feature = "regex")] { m.insert("match", &MATCH_FUNC); m.insert("search", &SEARCH_FUNC); } m.insert("value", &VALUE_FUNC); m }); fn value_length(value: &Value) -> Option { match value { Value::String(s) => Some(s.chars().count()), Value::Array(a) => Some(a.len()), Value::Object(o) => Some(o.len()), _ => None, } } #[serde_json_path_macros::register(target = LENGTH_FUNC)] fn length(value: ValueType) -> ValueType { match value { ValueType::Value(v) => value_length(&v), ValueType::Node(v) => value_length(v), ValueType::Nothing => None, } .map_or(ValueType::Nothing, |l| ValueType::Value(l.into())) } #[serde_json_path_macros::register(target = COUNT_FUNC)] fn count(nodes: NodesType) -> ValueType { nodes.len().into() } #[cfg(feature = "regex")] #[serde_json_path_macros::register(name = "match", target = MATCH_FUNC)] fn match_func(value: ValueType, rgx: ValueType) -> LogicalType { match (value.as_value(), rgx.as_value()) { (Some(Value::String(s)), Some(Value::String(r))) => { regex::Regex::new(format!("(?R)^({r})$").as_str()) .map(|r| r.is_match(s)) .map(Into::into) .unwrap_or_default() } _ => LogicalType::False, } } #[cfg(feature = "regex")] #[serde_json_path_macros::register(target = SEARCH_FUNC)] fn search(value: ValueType, rgx: ValueType) -> LogicalType { match (value.as_value(), rgx.as_value()) { (Some(Value::String(s)), Some(Value::String(r))) => { regex::Regex::new(format!("(?R)({r})").as_str()) .map(|r| r.is_match(s)) .map(Into::into) .unwrap_or_default() } _ => LogicalType::False, } } #[serde_json_path_macros::register(target = VALUE_FUNC)] fn value(nodes: NodesType) -> ValueType { if nodes.len() > 1 { ValueType::Nothing } else { match nodes.first() { Some(v) => ValueType::Node(v), None => ValueType::Nothing, } } } serde_json_path-0.7.2/src/parser/selector/mod.rs000064400000000000000000000070371046102023000200400ustar 00000000000000use nom::branch::alt; use nom::character::complete::char; use nom::combinator::map; use nom::error::context; use serde_json_path_core::spec::selector::index::Index; use serde_json_path_core::spec::selector::name::Name; use serde_json_path_core::spec::selector::Selector; use self::filter::parse_filter; use self::slice::parse_array_slice; use super::primitive::int::parse_int; use super::primitive::string::parse_string_literal; use super::PResult; pub(crate) mod filter; pub(crate) mod function; pub(crate) mod slice; #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_wildcard_selector(input: &str) -> PResult { map(char('*'), |_| Selector::Wildcard)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_name(input: &str) -> PResult { map(parse_string_literal, Name)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_name_selector(input: &str) -> PResult { map(parse_name, Selector::Name)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_index(input: &str) -> PResult { map(parse_int, Index)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_index_selector(input: &str) -> PResult { map(parse_index, Selector::Index)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_array_slice_selector(input: &str) -> PResult { map(parse_array_slice, Selector::ArraySlice)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_filter_selector(input: &str) -> PResult { map(parse_filter, Selector::Filter)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_selector(input: &str) -> PResult { context( "selector", alt(( parse_wildcard_selector, parse_name_selector, parse_array_slice_selector, parse_index_selector, parse_filter_selector, )), )(input) } #[cfg(test)] mod tests { use serde_json_path_core::spec::{ integer::Integer, selector::{name::Name, slice::Slice}, }; use super::{parse_selector, parse_wildcard_selector, Index, Selector}; #[test] fn wildcard() { assert!(matches!( parse_wildcard_selector("*"), Ok(("", Selector::Wildcard)) )); } #[test] fn all_selectors() { { let (_, s) = parse_selector("0").unwrap(); assert_eq!(s, Selector::Index(Index(Integer::from_i64_unchecked(0)))); } { let (_, s) = parse_selector("10").unwrap(); assert_eq!(s, Selector::Index(Index(Integer::from_i64_unchecked(10)))); } { let (_, s) = parse_selector("'name'").unwrap(); assert_eq!(s, Selector::Name(Name(String::from("name")))); } { let (_, s) = parse_selector("\"name\"").unwrap(); assert_eq!(s, Selector::Name(Name(String::from("name")))); } { let (_, s) = parse_selector("0:3").unwrap(); assert_eq!( s, Selector::ArraySlice(Slice::new().with_start(0).with_end(3)) ); } } } serde_json_path-0.7.2/src/parser/selector/slice.rs000064400000000000000000000064711046102023000203610ustar 00000000000000use nom::{ branch::alt, character::complete::{char, multispace0}, combinator::{map, opt}, sequence::{preceded, separated_pair, terminated}, }; use serde_json_path_core::spec::{integer::Integer, selector::slice::Slice}; use crate::parser::{primitive::int::parse_int, PResult}; #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_int_space_after(input: &str) -> PResult { terminated(parse_int, multispace0)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] fn parse_int_space_before(input: &str) -> PResult { preceded(multispace0, parse_int)(input) } #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] pub(crate) fn parse_array_slice(input: &str) -> PResult { map( separated_pair( opt(parse_int_space_after), char(':'), preceded( multispace0, alt(( separated_pair( opt(parse_int_space_after), char(':'), opt(parse_int_space_before), ), map(opt(parse_int_space_after), |i| (i, None)), )), ), ), |(start, (end, step))| Slice { start, end, step }, )(input) } #[cfg(test)] mod tests { use crate::parser::selector::slice::Slice; use super::parse_array_slice; #[test] fn valid_forward() { assert_eq!( parse_array_slice("1:5:1"), Ok(("", Slice::new().with_start(1).with_end(5).with_step(1))) ); assert_eq!( parse_array_slice("1:10:3"), Ok(("", Slice::new().with_start(1).with_end(10).with_step(3))) ); assert_eq!( parse_array_slice("1:5:-1"), Ok(("", Slice::new().with_start(1).with_end(5).with_step(-1))) ); assert_eq!( parse_array_slice(":5:1"), Ok(("", Slice::new().with_end(5).with_step(1))) ); assert_eq!( parse_array_slice("1::1"), Ok(("", Slice::new().with_start(1).with_step(1))) ); assert_eq!( parse_array_slice("1:5"), Ok(("", Slice::new().with_start(1).with_end(5))) ); assert_eq!(parse_array_slice("::"), Ok(("", Slice::new()))); } #[test] fn optional_whitespace() { assert_eq!( parse_array_slice("1 :5:1"), Ok(("", Slice::new().with_start(1).with_end(5).with_step(1))) ); assert_eq!( parse_array_slice("1: 5 :1"), Ok(("", Slice::new().with_start(1).with_end(5).with_step(1))) ); assert_eq!( parse_array_slice("1: :1"), Ok(("", Slice::new().with_start(1).with_step(1))) ); assert_eq!( parse_array_slice("1:5\n:1"), Ok(("", Slice::new().with_start(1).with_end(5).with_step(1))) ); assert_eq!( parse_array_slice("1 : 5 :1"), Ok(("", Slice::new().with_start(1).with_end(5).with_step(1))) ); assert_eq!( parse_array_slice("1:5: 1"), Ok(("", Slice::new().with_start(1).with_end(5).with_step(1))) ); } } serde_json_path-0.7.2/src/parser/utils.rs000064400000000000000000000053211046102023000165730ustar 00000000000000use nom::{error::ParseError, IResult, Parser}; use super::FromInternalError; /// Prevent upstream poisoning by [`nom::combinator::cut`] /// /// It is common when using [`nom::branch::alt`] to terminate a branch, and the entire `alt` tree, /// using the `cut` combinator. This has the unfortunate effect of "poisoning" upstream parsers by /// propagating a nom `Failure` up the parser chain. Any upstream usage of, e.g., `alt`, would then /// also terminate, and not attempt other branches. /// /// Using `uncut`, you can wrap `alt` to prevent this poisoning. /// /// # Example /// /// ```ignore /// fn unpoisoned_alt(input: &str) -> IResult<&str, &str> { /// uncut( /// alt(( /// pair(tag("foo"), cut(tag("bar"))), /// tag("baz"), /// )) /// )(input) /// } /// ``` pub(crate) fn uncut, F: Parser>( mut parser: F, ) -> impl FnMut(I) -> IResult { move |input: I| match parser.parse(input) { Err(nom::Err::Failure(e)) => Err(nom::Err::Error(e)), rest => rest, } } #[allow(dead_code)] /// Map a parser error into another error pub(crate) fn map_err( mut parser: F, mut f: G, ) -> impl FnMut(I) -> IResult where F: nom::Parser, G: FnMut(E1) -> E2, E1: FromInternalError, { move |input: I| { let i = input.clone(); match parser.parse(i) { Ok((remainder, value)) => Ok((remainder, value)), Err(nom::Err::Error(e)) => Err(nom::Err::Error(E1::from_internal_error(input, f(e)))), Err(nom::Err::Failure(e)) => { Err(nom::Err::Failure(E1::from_internal_error(input, f(e)))) } Err(nom::Err::Incomplete(_)) => unreachable!(), } } } /// Use the `cut` parser with a custom error wrapper pub(crate) fn cut_with( mut parser: F, mut f: G, ) -> impl FnMut(I) -> IResult where I: Clone, F: nom::Parser, G: FnMut(E1) -> E2, E1: FromInternalError, { move |input: I| { let i = input.clone(); match parser.parse(i) { Ok((remainder, value)) => Ok((remainder, value)), Err(nom::Err::Error(e) | nom::Err::Failure(e)) => { Err(nom::Err::Failure(E1::from_internal_error(input, f(e)))) } Err(nom::Err::Incomplete(_)) => unreachable!(), } } } #[allow(dead_code)] /// Fail with an internal error pub(crate) fn fail_with(mut f: G) -> impl FnMut(I) -> IResult where E1: FromInternalError, G: FnMut() -> E2, { move |input: I| Err(nom::Err::Failure(E1::from_internal_error(input, f()))) } serde_json_path-0.7.2/src/path.rs000064400000000000000000000124471046102023000151020ustar 00000000000000use std::str::FromStr; use serde::{de::Visitor, Deserialize, Serialize}; use serde_json::Value; use serde_json_path_core::{ node::{LocatedNodeList, NodeList}, spec::query::{Query, Queryable}, }; use crate::{parser::parse_query_main, ParseError}; /// A parsed JSON Path query string /// /// This type represents a valid, parsed JSON Path query string. Please refer to the /// JSONPath standard ([RFC 9535][rfc]) for the details on what constitutes a valid JSON Path /// query. /// /// # Usage /// /// A `JsonPath` can be parsed directly from an `&str` using the [`parse`][JsonPath::parse] method: /// ```rust /// # use serde_json_path::JsonPath; /// # fn main() { /// let path = JsonPath::parse("$.foo.*").expect("valid JSON Path"); /// # } /// ``` /// It can then be used to query [`serde_json::Value`]'s with the [`query`][JsonPath::query] method: /// ```rust /// # use serde_json::json; /// # use serde_json_path::JsonPath; /// # fn main() { /// # let path = JsonPath::parse("$.foo.*").expect("valid JSON Path"); /// let value = json!({"foo": [1, 2, 3, 4]}); /// let nodes = path.query(&value); /// assert_eq!(nodes.all(), vec![1, 2, 3, 4]); /// # } /// ``` /// /// [rfc]: https://www.rfc-editor.org/rfc/rfc9535.html #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct JsonPath(Query); impl JsonPath { /// Create a [`JsonPath`] by parsing a valid JSON Path query string /// /// # Example /// ```rust /// # use serde_json_path::JsonPath; /// # fn main() { /// let path = JsonPath::parse("$.foo[1:10:2].baz").expect("valid JSON Path"); /// # } /// ``` pub fn parse(path_str: &str) -> Result { let (_, path) = parse_query_main(path_str).map_err(|err| match err { nom::Err::Error(e) | nom::Err::Failure(e) => (path_str, e), nom::Err::Incomplete(_) => unreachable!("we do not use streaming parsers"), })?; Ok(Self(path)) } /// Query a [`serde_json::Value`] using this [`JsonPath`] /// /// # Example /// ```rust /// # use serde_json::json; /// # use serde_json_path::JsonPath; /// # fn main() -> Result<(), serde_json_path::ParseError> { /// let value = json!({"foo": [1, 2, 3, 4]}); /// let path = JsonPath::parse("$.foo[::2]")?; /// let nodes = path.query(&value); /// assert_eq!(nodes.all(), vec![1, 3]); /// # Ok(()) /// # } /// ``` pub fn query<'b>(&self, value: &'b Value) -> NodeList<'b> { self.0.query(value, value).into() } /// Query a [`serde_json::Value`] using this [`JsonPath`] to produce a [`LocatedNodeList`] /// /// # Example /// ```rust /// # use serde_json::{json, Value}; /// # use serde_json_path::{JsonPath,NormalizedPath}; /// # fn main() -> Result<(), serde_json_path::ParseError> { /// let value = json!({"foo": {"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']"]); /// # Ok(()) /// # } /// ``` pub fn query_located<'b>(&self, value: &'b Value) -> LocatedNodeList<'b> { self.0 .query_located(value, value, Default::default()) .into() } } impl FromStr for JsonPath { type Err = ParseError; fn from_str(s: &str) -> Result { JsonPath::parse(s) } } impl std::fmt::Display for JsonPath { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{path}", path = self.0) } } impl Serialize for JsonPath { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.collect_str(self) } } impl<'de> Deserialize<'de> for JsonPath { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { struct JsonPathVisitor; impl Visitor<'_> for JsonPathVisitor { type Value = JsonPath; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { write!(formatter, "a string representing a JSON Path query") } fn visit_str(self, v: &str) -> Result where E: serde::de::Error, { JsonPath::parse(v).map_err(serde::de::Error::custom) } } deserializer.deserialize_str(JsonPathVisitor) } } #[cfg(test)] mod tests { use serde_json::{from_value, json, to_value}; use crate::JsonPath; #[test] fn test_send() { fn assert_send() {} assert_send::(); } #[test] fn test_sync() { fn assert_sync() {} assert_sync::(); } #[test] fn serde_round_trip() { let j1 = json!("$.foo['bar'][1:10][?@.baz > 10 && @.foo.bar < 20]"); let p1 = from_value::(j1).expect("deserializes"); let p2 = to_value(&p1) .and_then(from_value::) .expect("round trip"); assert_eq!(p1, p2); } } serde_json_path-0.7.2/tests/compliance.rs000064400000000000000000000074101046102023000166250ustar 00000000000000use std::fs; use serde::Deserialize; use serde_json::Value; use serde_json_path::JsonPath; #[cfg(feature = "trace")] use test_log::test; #[derive(Deserialize)] struct TestSuite { tests: Vec, } #[derive(Deserialize)] struct TestCase { name: String, selector: String, #[serde(default)] document: Value, #[serde(flatten)] result: TestResult, } #[derive(Deserialize)] #[serde(untagged)] enum TestResult { Deterministic { result: Vec }, NonDeterministic { results: Vec> }, InvalidSelector { invalid_selector: bool }, } impl TestResult { fn verify(&self, name: &str, actual: Vec<&Value>) { match self { TestResult::Deterministic { result } => assert_eq!( result.iter().collect::>(), actual, "{name}: incorrect result, expected {result:?}, got {actual:?}" ), TestResult::NonDeterministic { results } => { assert!(results .iter() .any(|r| r.iter().collect::>().eq(&actual))) } TestResult::InvalidSelector { .. } => unreachable!(), } } fn is_invalid_selector(&self) -> bool { matches!(self, Self::InvalidSelector { invalid_selector } if *invalid_selector) } } #[test] fn compliance_test_suite() { let cts_json_str = fs::read_to_string("../jsonpath-compliance-test-suite/cts.json") .expect("read cts.json file"); let test_cases: TestSuite = serde_json::from_str(cts_json_str.as_str()).expect("parse cts_json_str"); for ( i, TestCase { name, selector, document, result, }, ) in test_cases.tests.iter().enumerate() { println!("Test ({i}): {name}"); let path = JsonPath::parse(selector); if result.is_invalid_selector() { assert!( path.is_err(), "{name}: parsing {selector:?} should have failed", ); } else { let path = path.expect("valid JSON Path string"); { // Query using JsonPath::query let actual = path.query(document).all(); result.verify(name, actual); } { // Query using JsonPath::query_located let q = path.query_located(document); let actual = q.nodes().collect::>(); result.verify(name, actual); } } } } const TEST_CASE_N: usize = 10; #[test] #[ignore = "this is only for testing individual CTS test cases as needed"] fn compliance_single() { let cts_json_str = fs::read_to_string("../jsonpath-compliance-test-suite/cts.json") .expect("read cts.json file"); let test_cases: TestSuite = serde_json::from_str(cts_json_str.as_str()).expect("parse cts_json_str"); let TestCase { name, selector, document, result, } = &test_cases.tests[TEST_CASE_N]; println!("Test Case: {name}"); let path = JsonPath::parse(selector); if result.is_invalid_selector() { println!("...this test should fail"); assert!( path.is_err(), "{name}: parsing {selector:?} should have failed", ); } else { let path = path.expect("valid JSON Path string"); { // Query using JsonPath::query let actual = path.query(document).all(); result.verify(name, actual); } { // Query using JsonPath::query_located let q = path.query_located(document); let actual = q.nodes().collect::>(); result.verify(name, actual); } } } serde_json_path-0.7.2/tests/functions.rs000064400000000000000000000153241046102023000165260ustar 00000000000000use std::cmp::Ordering; use serde_json::{json, Value}; use serde_json_path::{JsonPath, JsonPathExt}; use serde_json_path_core::spec::functions::{NodesType, ValueType}; #[cfg(feature = "trace")] use test_log::test; #[test] fn test_length() { let value = json!([ "a short string", "a slightly longer string", "a string that is even longer!", ]); let query = JsonPath::parse("$[?length(@) > 14]").unwrap(); let nodes = query.query(&value); println!("{nodes:#?}"); assert_eq!(nodes.len(), 2); } #[test] fn test_length_invalid_args() { let error = JsonPath::parse("$[? length(@.foo.*)]").unwrap_err(); println!("{error:#?}"); } #[test] fn test_length_incorrect_use() { let error = JsonPath::parse("$[?length(@.author)]").unwrap_err(); assert!( error .to_string() .contains("in long-hand segment, function with incorrect return type used"), "error did not contain the expected message" ); } #[test] fn test_count() { let value = json!([ {"foo": [1]}, {"foo": [1, 2]}, ]); let path = JsonPath::parse("$[?count(@.foo.*) > 1]").unwrap(); let q = path.query(&value); assert_eq!(1, q.len()); } #[test] fn test_count_singular_query() { let value = json!([ {"foo": "bar"}, {"foo": "baz"}, ]); let path = JsonPath::parse("$[? count(@.foo) == 1]").unwrap(); let q = path.query(&value); println!("{q:#?}"); assert_eq!(q.len(), 2); } fn simpsons_characters() -> Value { json!([ { "name": "Homer Simpson" }, { "name": "Marge Simpson" }, { "name": "Bart Simpson" }, { "name": "Lisa Simpson" }, { "name": "Maggie Simpson" }, { "name": "Ned Flanders" }, { "name": "Rod Flanders" }, { "name": "Todd Flanders" }, ]) } #[test] fn test_match() { let value = simpsons_characters(); let path = JsonPath::parse("$[? match(@.name, 'M[A-Za-z ]*Simpson')].name").unwrap(); let nodes = path.query(&value); println!("{nodes:#?}"); assert_eq!(2, nodes.len()); assert_eq!("Marge Simpson", nodes.first().unwrap().as_str().unwrap(),); } #[test] fn test_search() { let value = simpsons_characters(); let path = JsonPath::parse("$[? search(@.name, 'Flanders')]").unwrap(); let nodes = path.query(&value); println!("{nodes:#?}"); assert_eq!(3, nodes.len()); } fn get_some_books() -> Value { json!([ { "books": [ { "author": "Alexandre Dumas", "title": "The Three Musketeers", "price": 14.99 }, { "author": "Leo Tolstoy", "title": "War and Peace", "price": 19.99 } ] }, { "books": [ { "author": "Charles Dickens", "title": "Great Expectations", "price": 13.99 }, { "author": "Fyodor Dostoevsky", "title": "The Brothers Karamazov", "price": 12.99 } ] } ]) } #[serde_json_path::function] fn first(nodes: NodesType) -> ValueType { match nodes.first() { Some(v) => ValueType::Node(v), None => ValueType::Nothing, } } #[serde_json_path::function] fn sort<'a>(nodes: NodesType<'a>, on: ValueType<'a>) -> NodesType<'a> { if let Some(Ok(path)) = on.as_value().and_then(Value::as_str).map(JsonPath::parse) { let mut nl = nodes.all(); nl.sort_by(|a, b| { if let (Some(a), Some(b)) = ( a.json_path(&path).exactly_one().ok(), b.json_path(&path).exactly_one().ok(), ) { match (a, b) { (Value::Bool(b1), Value::Bool(b2)) => b1.cmp(b2), (Value::Number(n1), Value::Number(n2)) => { if let (Some(f1), Some(f2)) = (n1.as_f64(), n2.as_f64()) { f1.partial_cmp(&f2).unwrap_or(Ordering::Equal) } else if let (Some(u1), Some(u2)) = (n1.as_u64(), n2.as_u64()) { u1.cmp(&u2) } else if let (Some(i1), Some(i2)) = (n1.as_i64(), n2.as_i64()) { i1.cmp(&i2) } else { Ordering::Equal } } (Value::String(s1), Value::String(s2)) => s1.cmp(s2), _ => Ordering::Equal, } } else { Ordering::Equal } }); nl.into() } else { nodes } } #[serde_json_path::function] fn get<'a>(node: ValueType<'a>, path: ValueType<'a>) -> ValueType<'a> { if let Some(Ok(path)) = path.as_value().and_then(Value::as_str).map(JsonPath::parse) { match node { ValueType::Node(v) => v .json_path(&path) .exactly_one() .map(ValueType::Node) .unwrap_or_default(), // This is awkward, because we can't return a reference to a value owned by `node` // This means that `get` can only be used when dealing with nodes within the JSON object // being queried. _ => ValueType::Nothing, } } else { ValueType::Nothing } } #[test] fn custom_first_function() { let value = get_some_books(); let path = JsonPath::parse("$[? first(@.books.*.author) == 'Alexandre Dumas']").unwrap(); let node = path.query(&value).exactly_one().unwrap(); println!("{node:#?}"); assert_eq!( "War and Peace", node.pointer("/books/1/title").unwrap().as_str().unwrap(), ); } #[test] fn function_as_argument() { let value = get_some_books(); let path = JsonPath::parse("$[? length(first(@.books.*.title)) == 18 ]").unwrap(); let node = path.query(&value).exactly_one().unwrap(); println!("{node:#?}"); assert_eq!( "The Brothers Karamazov", node.pointer("/books/1/title").unwrap().as_str().unwrap(), ) } #[test] fn combine_custom_functions() { let value = get_some_books(); let path = JsonPath::parse( // `sort` the books in each node by price, take the `first` one, and check for specific // title by using `get`: "$[? get(first(sort(@.books.*, '$.price')), '$.title') == 'The Brothers Karamazov']", ) .unwrap(); let node = path.query(&value).exactly_one().unwrap(); println!("{node:#?}"); assert_eq!( "The Brothers Karamazov", node.pointer("/books/1/title").unwrap().as_str().unwrap(), ); } serde_json_path-0.7.2/tests/regressions.rs000064400000000000000000000013411046102023000170530ustar 00000000000000use serde_json::json; use serde_json_path::JsonPath; #[cfg(feature = "trace")] use test_log::test; // This test is meant for issue #49, which can be found here: // https://github.com/hiltontj/serde_json_path/issues/49 #[test] fn issue_49() { let value = json!({"a": 1, "b": 2}); let path = JsonPath::parse("$[?(@.a == 2)]").expect("parses JSONPath"); assert!(path.query(&value).is_empty()); } // This test is meant for issue #60, which can be found here: // https://github.com/hiltontj/serde_json_path/issues/60 #[test] fn issue_60() { let value = json!([{"foo": "bar"}, {"foo": "biz"}]); let path = JsonPath::parse("$[? match(@.foo, '|')]").expect("parses JSONPath"); assert!(path.query(&value).is_empty()); } serde_json_path-0.7.2/tests/serde.rs000064400000000000000000000007121046102023000156130ustar 00000000000000use serde::Deserialize; use serde_json::{from_value, json}; use serde_json_path::JsonPath; #[derive(Deserialize)] struct Config { pub path: JsonPath, } #[test] fn can_deserialize_json_path() { let config_json = json!({ "path": "$.foo.*" }); let config = from_value::(config_json).expect("deserializes"); let value = json!({"foo": [1, 2, 3]}); let nodes = config.path.query(&value).all(); assert_eq!(nodes, vec![1, 2, 3]); } serde_json_path-0.7.2/tests/spec_examples.rs000064400000000000000000000076511046102023000173520ustar 00000000000000use serde_json::{json, Value}; use serde_json_path::JsonPath; #[cfg(feature = "trace")] use test_log::test; fn spec_example_json() -> Value { json!({ "store": { "book": [ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 } ], "bicycle": { "color": "red", "price": 399 } } }) } #[test] fn spec_example_1() { let value = spec_example_json(); let path = JsonPath::parse("$.store.book[*].author").unwrap(); let nodes = path.query(&value).all(); assert_eq!( nodes, vec![ "Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien" ] ); } #[test] fn spec_example_2() { let value = spec_example_json(); let path = JsonPath::parse("$..author").unwrap(); let nodes = path.query(&value).all(); assert_eq!( nodes, vec![ "Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien" ] ); } #[test] fn spec_example_3() { let value = spec_example_json(); let path = JsonPath::parse("$.store.*").unwrap(); let nodes = path.query(&value).all(); assert_eq!(nodes.len(), 2); assert!(nodes .iter() .any(|&node| node == value.pointer("/store/book").unwrap())); } #[test] fn spec_example_4() { let value = spec_example_json(); let path = JsonPath::parse("$.store..price").unwrap(); let nodes = path.query(&value).all(); assert_eq!(nodes, vec![399., 8.95, 12.99, 8.99, 22.99]); } #[test] fn spec_example_5() { let value = spec_example_json(); let path = JsonPath::parse("$..book[2]").unwrap(); let node = path.query(&value).at_most_one().unwrap(); assert!(node.is_some()); assert_eq!(node, value.pointer("/store/book/2")); } #[test] fn spec_example_6() { let value = spec_example_json(); let path = JsonPath::parse("$..book[-1]").unwrap(); let node = path.query(&value).at_most_one().unwrap(); assert!(node.is_some()); assert_eq!(node, value.pointer("/store/book/3")); } #[test] fn spec_example_7() { let value = spec_example_json(); { let path = JsonPath::parse("$..book[0,1]").unwrap(); let nodes = path.query(&value).all(); assert_eq!(nodes.len(), 2); } { let path = JsonPath::parse("$..book[:2]").unwrap(); let nodes = path.query(&value).all(); assert_eq!(nodes.len(), 2); } } #[test] fn spec_example_8() { let value = spec_example_json(); let path = JsonPath::parse("$..book[?(@.isbn)]").unwrap(); let nodes = path.query(&value); assert_eq!(nodes.len(), 2); } #[test] fn spec_example_9() { let value = spec_example_json(); let path = JsonPath::parse("$..book[?(@.price<10)]").unwrap(); let nodes = path.query(&value); assert_eq!(nodes.len(), 2); } #[test] fn spec_example_10() { let value = spec_example_json(); let path = JsonPath::parse("$..*").unwrap(); let nodes = path.query(&value); assert_eq!(nodes.len(), 27); }