├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── cts.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── RELEASE-CHECKLIST.md ├── deny.toml ├── serde_json_path ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── src │ ├── error.rs │ ├── ext.rs │ ├── lib.rs │ ├── parser │ │ ├── mod.rs │ │ ├── primitive │ │ │ ├── int.rs │ │ │ ├── mod.rs │ │ │ ├── number.rs │ │ │ └── string.rs │ │ ├── segment.rs │ │ ├── selector │ │ │ ├── filter.rs │ │ │ ├── function │ │ │ │ ├── mod.rs │ │ │ │ └── registry.rs │ │ │ ├── mod.rs │ │ │ └── slice.rs │ │ └── utils.rs │ └── path.rs └── tests │ ├── compliance.rs │ ├── functions.rs │ ├── regressions.rs │ ├── serde.rs │ └── spec_examples.rs ├── serde_json_path_core ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-MIT ├── README.md └── src │ ├── lib.rs │ ├── node.rs │ ├── path.rs │ └── spec │ ├── functions.rs │ ├── integer.rs │ ├── mod.rs │ ├── query.rs │ ├── segment.rs │ └── selector │ ├── filter.rs │ ├── index.rs │ ├── mod.rs │ ├── name.rs │ └── slice.rs └── serde_json_path_macros ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-MIT ├── README.md └── src ├── internal ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── common │ ├── args.rs │ ├── define.rs │ ├── extract.rs │ └── mod.rs ├── func │ ├── args.rs │ ├── define.rs │ └── mod.rs ├── mod.rs └── reg │ ├── args.rs │ ├── define.rs │ └── mod.rs └── lib.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "monthly" 9 | reviewers: 10 | - "hiltontj" 11 | commit-message: 12 | prefix: "chore" 13 | rebase-strategy: "disabled" 14 | 15 | - package-ecosystem: "cargo" 16 | directory: "/" 17 | schedule: 18 | interval: "weekly" 19 | reviewers: 20 | - "hiltontj" 21 | commit-message: 22 | prefix: "chore" 23 | include: "scope" 24 | rebase-strategy: "disabled" 25 | 26 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Rust CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | 14 | jobs: 15 | fmt: 16 | name: Format 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: 📦 Checkout 20 | uses: actions/checkout@v4 21 | - name: 🦀 Rust Toolchain & Cache 22 | uses: actions-rust-lang/setup-rust-toolchain@v1 23 | - name: 📝 Check Rust Code Formatting 24 | run: cargo fmt --all -- --check 25 | 26 | audit: 27 | name: Audit 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: 📦 Checkout 31 | uses: actions/checkout@v4 32 | - name: 👮 Check Rust Code with Cargo Deny 33 | uses: EmbarkStudios/cargo-deny-action@v2 34 | 35 | clippy: 36 | name: Clippy 37 | runs-on: ubuntu-latest 38 | steps: 39 | - name: 📦 Checkout 40 | uses: actions/checkout@v4 41 | - name: 🦀 Rust Toolchain & Cache 42 | uses: actions-rust-lang/setup-rust-toolchain@v1 43 | - name: 📎 Check Rust Code with Clippy 44 | run: cargo clippy --all-targets -- -D warnings 45 | 46 | docs: 47 | name: Docs 48 | runs-on: ubuntu-latest 49 | steps: 50 | - name: 📦 Checkout 51 | uses: actions/checkout@v4 52 | - name: 🦀 Rust Toolchain & Cache 53 | uses: actions-rust-lang/setup-rust-toolchain@v1 54 | - name: 📓 Check Docs 55 | env: 56 | RUSTDOCFLAGS: "-D broken-intra-doc-links" 57 | run: cargo doc --no-deps 58 | 59 | test: 60 | name: Test 61 | runs-on: ubuntu-latest 62 | steps: 63 | - name: 📦 Checkout 64 | uses: actions/checkout@v4 65 | with: 66 | submodules: true 67 | - name: 🦀 Rust Toolchain & Cache 68 | uses: actions-rust-lang/setup-rust-toolchain@v1 69 | - name: 🧪 Test 70 | run: cargo test 71 | -------------------------------------------------------------------------------- /.github/workflows/cts.yml: -------------------------------------------------------------------------------- 1 | name: Compliance 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: 0 0 * * * 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | submodule-update: 13 | name: Submodule Update & Test 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: 📦 Checkout 17 | uses: actions/checkout@v4 18 | with: 19 | submodules: true 20 | - name: 🧱 Update Submodules 21 | run: | 22 | git pull --recurse-submodules 23 | git submodule update --recursive --remote 24 | - name: 🦀 Rust Toolchain & Cache 25 | uses: actions-rust-lang/setup-rust-toolchain@v1 26 | - name: 🧪 Test 27 | run: cargo test 28 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | .local 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "jsonpath-compliance-test-suite"] 2 | path = jsonpath-compliance-test-suite 3 | url = https://github.com/jsonpath-standard/jsonpath-compliance-test-suite 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | `serde_json_path`'s changelog can be found [here](https://github.com/hiltontj/serde_json_path/blob/main/serde_json_path/CHANGELOG.md) 2 | 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "serde_json_path", 4 | "serde_json_path_core", 5 | "serde_json_path_macros", 6 | "serde_json_path_macros/src/internal" 7 | ] 8 | resolver = "2" 9 | 10 | [workspace.dependencies] 11 | # crates.io crates: 12 | inventory = { version = "0.3.19" } 13 | nom = "7.1.3" 14 | proc-macro2 = "1.0.93" 15 | quote = "1.0.38" 16 | regex = { version="1.11.1" } 17 | serde = { version = "1.0.217", features = ["derive"] } 18 | serde_json = "1.0.138" 19 | syn = { version = "2.0.97", features = ["full"] } 20 | thiserror = "2.0.11" 21 | tracing = { version = "0.1.40" } 22 | 23 | # dev dependencies 24 | test-log = { version = "0.2.17", default-features = false, features=["trace"] } 25 | tracing-subscriber = { version = "0.3.18", default-features = false, features=["env-filter", "fmt"] } 26 | 27 | [profile.dev] 28 | incremental = false 29 | 30 | [profile.release] 31 | codegen-units = 1 32 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Trevor Hilton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | serde_json_path/README.md -------------------------------------------------------------------------------- /RELEASE-CHECKLIST.md: -------------------------------------------------------------------------------- 1 | # Steps to Perform in the Event of a Release 2 | 3 | - [ ] Ensure local `main` is up-to-date with `origin/main` 4 | - [ ] Run `cargo update` 5 | - [ ] Run `cargo test` 6 | - [ ] Create a new branch: `release-X-Y-Z` 7 | - [ ] Run `git diff` between current commit and previous tagged release commit 8 | - [ ] Check which crates have been modified; these will need their versions bumped 9 | - [ ] If sub-crates, e.g., `serde_json_path_core`, have their version bumped, check their super-crates, e.g., `serde_json_path`, for dependency update 10 | - [ ] Move Unreleased changes into the new version header in `serde_json_path/CHANGELOG.md` 11 | - [ ] Commit changes and push to `origin/main` 12 | - [ ] Open a pull request to merge changes into `main`, and allow CI to run successfully 13 | - [ ] Merge the PR and jump back to `main` locally 14 | - [ ] For each crate, in sub-crate to super-crate order, publish the crates from the workspace that had their versions bumped: 15 | - [ ] Run `cargo publish -p —dry-run`, to check that all is good 16 | - [ ] Run `cargo publish -p `, to do the actual release 17 | - [ ] Create a new release tag in Github using the naming convention `vX.Y.Z` and copy the relevant section from the changelog as the release documentation. 18 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/EmbarkStudios/cargo-deny/blob/0.16.0/deny.template.toml 2 | # (diff with that version to see what has been manually changed here) 3 | 4 | # This template contains all of the possible sections and their default values 5 | 6 | # Note that all fields that take a lint level have these possible values: 7 | # * deny - An error will be produced and the check will fail 8 | # * warn - A warning will be produced, but the check will not fail 9 | # * allow - No warning or error will be produced, though in some cases a note 10 | # will be 11 | 12 | # The values provided in this template are the default values that will be used 13 | # when any section or field is not specified in your own configuration 14 | 15 | # Root options 16 | 17 | # The graph table configures how the dependency graph is constructed and thus 18 | # which crates the checks are performed against 19 | [graph] 20 | # If 1 or more target triples (and optionally, target_features) are specified, 21 | # only the specified targets will be checked when running `cargo deny check`. 22 | # This means, if a particular package is only ever used as a target specific 23 | # dependency, such as, for example, the `nix` crate only being used via the 24 | # `target_family = "unix"` configuration, that only having windows targets in 25 | # this list would mean the nix crate, as well as any of its exclusive 26 | # dependencies not shared by any other crates, would be ignored, as the target 27 | # list here is effectively saying which targets you are building for. 28 | targets = [ 29 | # The triple can be any string, but only the target triples built in to 30 | # rustc (as of 1.40) can be checked against actual config expressions 31 | #"x86_64-unknown-linux-musl", 32 | # You can also specify which target_features you promise are enabled for a 33 | # particular target. target_features are currently not validated against 34 | # the actual valid features supported by the target architecture. 35 | #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, 36 | ] 37 | # When creating the dependency graph used as the source of truth when checks are 38 | # executed, this field can be used to prune crates from the graph, removing them 39 | # from the view of cargo-deny. This is an extremely heavy hammer, as if a crate 40 | # is pruned from the graph, all of its dependencies will also be pruned unless 41 | # they are connected to another crate in the graph that hasn't been pruned, 42 | # so it should be used with care. The identifiers are [Package ID Specifications] 43 | # (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) 44 | #exclude = [] 45 | # If true, metadata will be collected with `--all-features`. Note that this can't 46 | # be toggled off if true, if you want to conditionally enable `--all-features` it 47 | # is recommended to pass `--all-features` on the cmd line instead 48 | all-features = false 49 | # If true, metadata will be collected with `--no-default-features`. The same 50 | # caveat with `all-features` applies 51 | no-default-features = false 52 | # If set, these feature will be enabled when collecting metadata. If `--features` 53 | # is specified on the cmd line they will take precedence over this option. 54 | #features = [] 55 | 56 | # The output table provides options for how/if diagnostics are outputted 57 | [output] 58 | # When outputting inclusion graphs in diagnostics that include features, this 59 | # option can be used to specify the depth at which feature edges will be added. 60 | # This option is included since the graphs can be quite large and the addition 61 | # of features from the crate(s) to all of the graph roots can be far too verbose. 62 | # This option can be overridden via `--feature-depth` on the cmd line 63 | feature-depth = 1 64 | 65 | # This section is considered when running `cargo deny check advisories` 66 | # More documentation for the advisories section can be found here: 67 | # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html 68 | [advisories] 69 | # The path where the advisory databases are cloned/fetched into 70 | #db-path = "$CARGO_HOME/advisory-dbs" 71 | # The url(s) of the advisory databases to use 72 | #db-urls = ["https://github.com/rustsec/advisory-db"] 73 | # A list of advisory IDs to ignore. Note that ignored advisories will still 74 | # output a note when they are encountered. 75 | ignore = [ 76 | #"RUSTSEC-0000-0000", 77 | #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, 78 | #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish 79 | #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, 80 | ] 81 | # If this is true, then cargo deny will use the git executable to fetch advisory database. 82 | # If this is false, then it uses a built-in git library. 83 | # Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. 84 | # See Git Authentication for more information about setting up git authentication. 85 | #git-fetch-with-cli = true 86 | 87 | # This section is considered when running `cargo deny check licenses` 88 | # More documentation for the licenses section can be found here: 89 | # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html 90 | [licenses] 91 | # List of explicitly allowed licenses 92 | # See https://spdx.org/licenses/ for list of possible licenses 93 | # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. 94 | allow = [ 95 | "MIT", 96 | "Apache-2.0", 97 | "Unicode-3.0", 98 | #"Apache-2.0 WITH LLVM-exception", 99 | ] 100 | # The confidence threshold for detecting a license from license text. 101 | # The higher the value, the more closely the license text must be to the 102 | # canonical license text of a valid SPDX license file. 103 | # [possible values: any between 0.0 and 1.0]. 104 | confidence-threshold = 0.8 105 | # Allow 1 or more licenses on a per-crate basis, so that particular licenses 106 | # aren't accepted for every possible crate as with the normal allow list 107 | exceptions = [ 108 | # Each entry is the crate and version constraint, and its specific allow 109 | # list 110 | #{ allow = ["Zlib"], crate = "adler32" }, 111 | ] 112 | 113 | # Some crates don't have (easily) machine readable licensing information, 114 | # adding a clarification entry for it allows you to manually specify the 115 | # licensing information 116 | #[[licenses.clarify]] 117 | # The package spec the clarification applies to 118 | #crate = "ring" 119 | # The SPDX expression for the license requirements of the crate 120 | #expression = "MIT AND ISC AND OpenSSL" 121 | # One or more files in the crate's source used as the "source of truth" for 122 | # the license expression. If the contents match, the clarification will be used 123 | # when running the license check, otherwise the clarification will be ignored 124 | # and the crate will be checked normally, which may produce warnings or errors 125 | # depending on the rest of your configuration 126 | #license-files = [ 127 | # Each entry is a crate relative path, and the (opaque) hash of its contents 128 | #{ path = "LICENSE", hash = 0xbd0eed23 } 129 | #] 130 | 131 | [licenses.private] 132 | # If true, ignores workspace crates that aren't published, or are only 133 | # published to private registries. 134 | # To see how to mark a crate as unpublished (to the official registry), 135 | # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. 136 | ignore = false 137 | # One or more private registries that you might publish crates to, if a crate 138 | # is only published to private registries, and ignore is true, the crate will 139 | # not have its license(s) checked 140 | registries = [ 141 | #"https://sekretz.com/registry 142 | ] 143 | 144 | # This section is considered when running `cargo deny check bans`. 145 | # More documentation about the 'bans' section can be found here: 146 | # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html 147 | [bans] 148 | # Lint level for when multiple versions of the same crate are detected 149 | multiple-versions = "warn" 150 | # Lint level for when a crate version requirement is `*` 151 | wildcards = "allow" 152 | # The graph highlighting used when creating dotgraphs for crates 153 | # with multiple versions 154 | # * lowest-version - The path to the lowest versioned duplicate is highlighted 155 | # * simplest-path - The path to the version with the fewest edges is highlighted 156 | # * all - Both lowest-version and simplest-path are used 157 | highlight = "all" 158 | # The default lint level for `default` features for crates that are members of 159 | # the workspace that is being checked. This can be overridden by allowing/denying 160 | # `default` on a crate-by-crate basis if desired. 161 | workspace-default-features = "allow" 162 | # The default lint level for `default` features for external crates that are not 163 | # members of the workspace. This can be overridden by allowing/denying `default` 164 | # on a crate-by-crate basis if desired. 165 | external-default-features = "allow" 166 | # List of crates that are allowed. Use with care! 167 | allow = [ 168 | #"ansi_term@0.11.0", 169 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, 170 | ] 171 | # List of crates to deny 172 | deny = [ 173 | #"ansi_term@0.11.0", 174 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, 175 | # Wrapper crates can optionally be specified to allow the crate when it 176 | # is a direct dependency of the otherwise banned crate 177 | #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, 178 | ] 179 | 180 | # List of features to allow/deny 181 | # Each entry the name of a crate and a version range. If version is 182 | # not specified, all versions will be matched. 183 | #[[bans.features]] 184 | #crate = "reqwest" 185 | # Features to not allow 186 | #deny = ["json"] 187 | # Features to allow 188 | #allow = [ 189 | # "rustls", 190 | # "__rustls", 191 | # "__tls", 192 | # "hyper-rustls", 193 | # "rustls", 194 | # "rustls-pemfile", 195 | # "rustls-tls-webpki-roots", 196 | # "tokio-rustls", 197 | # "webpki-roots", 198 | #] 199 | # If true, the allowed features must exactly match the enabled feature set. If 200 | # this is set there is no point setting `deny` 201 | #exact = true 202 | 203 | # Certain crates/versions that will be skipped when doing duplicate detection. 204 | skip = [ 205 | #"ansi_term@0.11.0", 206 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, 207 | ] 208 | # Similarly to `skip` allows you to skip certain crates during duplicate 209 | # detection. Unlike skip, it also includes the entire tree of transitive 210 | # dependencies starting at the specified crate, up to a certain depth, which is 211 | # by default infinite. 212 | skip-tree = [ 213 | #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies 214 | #{ crate = "ansi_term@0.11.0", depth = 20 }, 215 | ] 216 | 217 | # This section is considered when running `cargo deny check sources`. 218 | # More documentation about the 'sources' section can be found here: 219 | # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html 220 | [sources] 221 | # Lint level for what to happen when a crate from a crate registry that is not 222 | # in the allow list is encountered 223 | unknown-registry = "warn" 224 | # Lint level for what to happen when a crate from a git repository that is not 225 | # in the allow list is encountered 226 | unknown-git = "warn" 227 | # List of URLs for allowed crate registries. Defaults to the crates.io index 228 | # if not specified. If it is specified but empty, no registries are allowed. 229 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 230 | # List of URLs for allowed Git repositories 231 | allow-git = [] 232 | 233 | [sources.allow-org] 234 | # 1 or more github.com organizations to allow git sources for 235 | github = [] 236 | # 1 or more gitlab.com organizations to allow git sources for 237 | gitlab = [] 238 | # 1 or more bitbucket.org organizations to allow git sources for 239 | bitbucket = [] 240 | -------------------------------------------------------------------------------- /serde_json_path/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | # Unreleased 9 | 10 | # 0.7.2 (2 February 2025) 11 | 12 | - **added**: better error message for invalid use of function in selector ([#118]) 13 | - **fixed**: properly format normalized path names ([#113], thanks [@theory]) 14 | - **internal**: fix clippy lints from 1.83.0 ([#110]) 15 | 16 | [#118]: https://github.com/hiltontj/serde_json_path/pull/118 17 | [#113]: https://github.com/hiltontj/serde_json_path/pull/113 18 | [@theory]: https://github.com/theory 19 | [#110]: https://github.com/hiltontj/serde_json_path/pull/110 20 | 21 | # 0.7.1 (3 November 2024) 22 | 23 | - **internal**: update `serde_json` to the latest version ([#107]) 24 | - **fixed**: edge case where `.` in regexes for `match` and `search` functions was matching `\r\n` properly ([#92]) 25 | - **breaking**: added `regex` feature flag that gates regex functions `match` and `search` ([#93], thanks [@LucasPickering]) 26 | - 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 27 | - **breaking**(`serde_json_path_core`): ensure integers used as indices are within the [valid range for I-JSON][i-json-range] ([#98]) 28 | - **internal**: remove use of `once_cell` and use specific versions for crate dependencies ([#105]) 29 | 30 | [#92]: https://github.com/hiltontj/serde_json_path/pull/92 31 | [#93]: https://github.com/hiltontj/serde_json_path/pull/93 32 | [@LucasPickering]: https://github.com/LucasPickering 33 | [#98]: https://github.com/hiltontj/serde_json_path/pull/98 34 | [i-json-range]: https://www.rfc-editor.org/rfc/rfc9535.html#section-2.1-4.1 35 | [#105]: https://github.com/hiltontj/serde_json_path/pull/105 36 | [#107]: https://github.com/hiltontj/serde_json_path/pull/107 37 | 38 | # 0.6.7 (3 March 2024) 39 | 40 | - **testing**: support tests for non-determinism in compliance test suite ([#85]) 41 | - **fixed**: bug preventing registered functions from being used as arguments to other functions ([#84]) 42 | 43 | [#85]: https://github.com/hiltontj/serde_json_path/pull/85 44 | [#84]: https://github.com/hiltontj/serde_json_path/pull/84 45 | 46 | # 0.6.6 (23 February 2024) 47 | 48 | - **docs**: update links to refer to RFC 9535 ([#81]) 49 | 50 | [#81]: https://github.com/hiltontj/serde_json_path/pull/81 51 | 52 | # 0.6.5 (2 February 2024) 53 | 54 | ## Added: `NormalizedPath` and `PathElement` types ([#78]) 55 | 56 | The `NormalizedPath` struct represents the location of a node within a JSON object. Its representation is like so: 57 | 58 | ```rust 59 | pub struct NormalizedPath<'a>(Vec); 60 | 61 | pub enum PathElement<'a> { 62 | Name(&'a str), 63 | Index(usize), 64 | } 65 | ``` 66 | 67 | 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. 68 | 69 | [pointer]: https://docs.rs/serde_json/latest/serde_json/enum.Value.html#method.pointer 70 | [pointer-mut]: https://docs.rs/serde_json/latest/serde_json/enum.Value.html#method.pointer_mut 71 | 72 | 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. 73 | 74 | ## Added: `LocatedNodeList` and `LocatedNode` types ([#78]) 75 | 76 | 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: 77 | 78 | ```rust 79 | use serde_json::{json, Value}; 80 | use serde_json_path::JsonPath; 81 | let value = json!({"foo": {"bar": 1, "baz": 2}}); 82 | let path = JsonPath::parse("$.foo.*")?; 83 | let query = path.query_located(&value); 84 | let nodes: Vec<&Value> = query.nodes().collect(); 85 | assert_eq!(nodes, vec![1, 2]); 86 | let locs: Vec = query 87 | .locations() 88 | .map(|loc| loc.to_string()) 89 | .collect(); 90 | assert_eq!(locs, ["$['foo']['bar']", "$['foo']['baz']"]); 91 | ``` 92 | 93 | The location/node pairs are represented by the `LocatedNode` type. 94 | 95 | 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. 96 | 97 | [#78]: https://github.com/hiltontj/serde_json_path/pull/78 98 | 99 | ## Other Changes 100 | 101 | - **internal**: address new clippy lints in Rust 1.75 ([#75]) 102 | - **internal**: address new clippy lints in Rust 1.74 ([#70]) 103 | - **internal**: code clean-up ([#72]) 104 | 105 | [#70]: https://github.com/hiltontj/serde_json_path/pull/70 106 | [#72]: https://github.com/hiltontj/serde_json_path/pull/72 107 | [#75]: https://github.com/hiltontj/serde_json_path/pull/75 108 | 109 | # 0.6.4 (9 November 2023) 110 | 111 | - **added**: `is_empty`, `is_more_than_one`, and `as_more_than_one` methods to `ExactlyOneError` ([#65]) 112 | - **fixed**: allow whitespace before dot-name selectors ([#67]) 113 | - **fixed**: ensure that the check `== -0` in filters works as expected ([#67]) 114 | 115 | [#65]: https://github.com/hiltontj/serde_json_path/pull/65 116 | [#67]: https://github.com/hiltontj/serde_json_path/pull/67 117 | 118 | # 0.6.3 (17 September 2023) 119 | 120 | - **documentation**: Add line describing Descendant Operator ([#53]) 121 | - **documentation**: Improve example in Filter Selector section of main docs ([#54]) 122 | - **documentation**: Improve examples in Slice Selector section of main docs ([#55]) 123 | - **documentation**: Other improvements to documentation ([#56]) 124 | - **fixed**: Formulate the regex used by the `match` function to correctly handle regular expressions with leading or trailing `|` characters ([#61]) 125 | 126 | [#53]: https://github.com/hiltontj/serde_json_path/pull/53 127 | [#54]: https://github.com/hiltontj/serde_json_path/pull/54 128 | [#55]: https://github.com/hiltontj/serde_json_path/pull/55 129 | [#56]: https://github.com/hiltontj/serde_json_path/pull/56 130 | [#61]: https://github.com/hiltontj/serde_json_path/pull/61 131 | 132 | # 0.6.2 (13 July 2023) 133 | 134 | * **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]) 135 | 136 | [#50]: https://github.com/hiltontj/serde_json_path/pull/50 137 | 138 | # 0.6.1 (5 July 2023) 139 | 140 | * **documentation**: Updated links to JSONPath specification to latest version (base 14) ([#43]) 141 | * **fixed**: Support newline characters in query strings where previously they were not being supported ([#44]) 142 | 143 | [#43]: https://github.com/hiltontj/serde_json_path/pull/43 144 | [#44]: https://github.com/hiltontj/serde_json_path/pull/44 145 | 146 | # 0.6.0 (2 April 2023) 147 | 148 | ## Function Extensions ([#32]) 149 | 150 | This release introduces the implementation of [Function Extensions][jpspec_func_ext] in `serde_json_path`. 151 | 152 | This release ships with support for the standard built-in functions that are part of the base JSONPath specification: 153 | 154 | - `length` 155 | - `count` 156 | - `match` 157 | - `search` 158 | - `value` 159 | 160 | These can now be used in your JSONPath query filter selectors, and are defined in the crate documentation 161 | in the `functions` module. 162 | 163 | In addition, the `#[function]` attribute macro was introduced to enable users of `serde_json_path` to define 164 | their own custom functions for use in their JSONPath queries. 165 | 166 | ### The `functions` module (**added**) 167 | 168 | In addition to the documentation/definitions for built-in functions, the `functions` module includes three new types: 169 | 170 | - `ValueType` 171 | - `NodesType` 172 | - `LogicalType` 173 | 174 | These reflect the type system defined in the JSONPath spec. Each is available through the public API, to be used in custom 175 | function definitions, along with the `#[function]` attribute macro. 176 | 177 | ### The `#[function]` attribute macro (**added**) 178 | 179 | A new attribute macro: `#[function]` was introduced to allow users of `serde_json_path` to define their 180 | own custom functions for use in their JSONPath queries. 181 | 182 | Along with the new types introduced by the `functions` module, it can be used like so: 183 | 184 | ```rust 185 | use serde_json_path::functions::{NodesType, ValueType}; 186 | 187 | /// A function that takes a node list, and optionally produces the first element as 188 | /// a value, if there are any elements in the list. 189 | #[serde_json_path::function] 190 | fn first(nodes: NodesType) -> ValueType { 191 | match nodes.first() { 192 | Some(v) => ValueType::Node(v), 193 | None => ValueType::Nothing, 194 | } 195 | } 196 | ``` 197 | 198 | Which will then allow you to use a `first` function in your JSONPath queries: 199 | 200 | ``` 201 | $[? first(@.*) > 5 ] 202 | ``` 203 | 204 | Usage of `first` in you JSONPath queries, like any of the built-in functions, will be validated at parse-time. 205 | 206 | The `#[function]` macro is gated behind the `functions` feature, which is enabled by default. 207 | 208 | Functions defined using the `#[function]` macro will override any of the built-in functions that are part 209 | of the standard, e.g., `length`, `count`, etc. 210 | 211 | ### Changed the `Error` type (**breaking**) 212 | 213 | The `Error` type was renamed to `ParseError` and was updated to have more concise error messages. It was 214 | refactored internally to better support future improvements to the parser. It is now a struct, vs. an enum, 215 | with a private implementation, and two core APIs: 216 | 217 | - `message()`: the parser error message 218 | - `position()`: indicate where the parser error was encountered in the JSONPath query string 219 | 220 | This gives far more concise errors than the pre-existing usage of `nom`'s built-in `VerboseError` type. 221 | However, for now, this leads to somewhat of a trade-off, in that errors that are not specially handled 222 | by the parser will present as just `"parser error"` with a position. Over time, the objective is to 223 | isolate cases where specific errors can be propagated up, and give better error messages. 224 | 225 | ### Repository switched to a workspace 226 | 227 | With this release, `serde_json_path` is split into four separate crates: 228 | 229 | - `serde_json_path` 230 | - `serde_json_path_macros` 231 | - `serde_json_path_macros_internal` 232 | - `serde_json_path_core` 233 | 234 | `serde_json_path` is still the entry point for general consumption. It still contains some of the key 235 | components of the API, e.g., `JsonPath`, `JsonPathExt`, and `Error`, as well as the entire `parser` module. 236 | However, many of the core types used to represent the JSONPath model, as defined in the specification, 237 | were moved into `serde_json_path_core`. 238 | 239 | This split was done to accommodate the new `#[function]` attribute macro, which is defined within the 240 | `serde_json_path_macros`/`macros_internal` crates, and discussed below. 241 | 242 | [#32]: https://github.com/hiltontj/serde_json_path/pull/32 243 | [jpspec_func_ext]: https://www.ietf.org/archive/id/draft-ietf-jsonpath-base-14.html#name-function-extensions 244 | 245 | ## Other Changes 246 | 247 | - **added:** updated to latest version of CTS to ensure compliance [#33] 248 | - **added:** implement `Eq` for `JsonPath` [#34] 249 | - **breaking:**: Changed the name of `Error` type to `ParseError` [#36] 250 | 251 | [#33]: https://github.com/hiltontj/serde_json_path/pull/33 252 | [#34]: https://github.com/hiltontj/serde_json_path/pull/34 253 | [#36]: https://github.com/hiltontj/serde_json_path/pull/36 254 | 255 | # 0.5.3 (14 March 2023) 256 | 257 | - **fixed:** Fix serialization behavior of `NodeList` ([#30]) 258 | 259 | [#30]: https://github.com/hiltontj/serde_json_path/pull/30 260 | 261 | # 0.5.2 (13 March 2023) 262 | 263 | - **added:** Add `first`, `last`, and `get` methods to `NodeList` type ([#16]) 264 | - **changed:** Make `NodeList::at_most_one` and `NodeList::exactly_one` take `&self` instead of `self` ([#16]) 265 | - **docs:** Update crate-level docs to better reflect recent changes ([#21]) 266 | - **docs:** Corrected a broken link in crate-level docs ([#21]) 267 | - **added:** derive `Clone` for `JsonPath` and its descendants ([#24]) 268 | - **added:** derive `Default` for `JsonPath` ([#25]) 269 | - **added:** implement `Display` and `Serialize` for `JsonPath` ([#26]) 270 | 271 | [#16]: https://github.com/hiltontj/serde_json_path/pull/16 272 | [#21]: https://github.com/hiltontj/serde_json_path/pull/21 273 | [#24]: https://github.com/hiltontj/serde_json_path/pull/24 274 | [#25]: https://github.com/hiltontj/serde_json_path/pull/25 275 | [#26]: https://github.com/hiltontj/serde_json_path/pull/26 276 | 277 | # 0.5.1 (11 March 2023) 278 | 279 | - **added:** Derive `PartialEq` on `JsonPath` ([#13]) 280 | - **added:** Add the `NodeList::at_most_one` method ([#13]) 281 | - **added:** Add the `NodeList::exactly_one` method ([#13]) 282 | - **deprecated:** Deprecate the `NodeList::one` method in favor of the new `NodeList::at_most_one` method ([#13]) 283 | 284 | [#13]: https://github.com/hiltontj/serde_json_path/pull/13 285 | 286 | # 0.5.0 (10 March 2023) 287 | 288 | ## The `JsonPath` type 289 | 290 | - **added:** Add the `JsonPath` type ([#10]) 291 | 292 | `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. 293 | 294 | ```rust 295 | let value = json!({"foo": [1, 2, 3]}); 296 | let path = JsonPath::parse("$.foo.*")?; 297 | let nodes = path.query(&value).all(); 298 | assert_eq!(nodes, vec![1, 2, 3]); 299 | ``` 300 | 301 | `JsonPath` implements `serde`'s `Deserialize`, which allows it to be used directly in serialized formats 302 | 303 | ```rust 304 | #[derive(Deserialize)] 305 | struct Config { 306 | pub path: JsonPath, 307 | } 308 | let config_json = json!({ "path": "$.foo.*" }); 309 | let config = from_value::(config_json).expect("deserializes"); 310 | let value = json!({"foo": [1, 2, 3]}); 311 | let nodes = config.path.query(&value).all(); 312 | assert_eq!(nodes, vec![1, 2, 3]); 313 | ``` 314 | 315 | `JsonPath` also implements `FromStr`, for convenience, e.g., 316 | 317 | ```rust 318 | let path = "$.foo.*".parse::()?; 319 | ``` 320 | 321 | ## Other changes 322 | 323 | - **breaking:** Alter the `JsonPathExt::json_path` API to accept a `&JsonPath` and to be infallible ([#10]) 324 | 325 | ```rust 326 | let value = json!({"foo": [1, 2, 3]}); 327 | let path = JsonPath::parse("$.foo.*")?; // <- note, this is fallible 328 | let nodes = value.json_path(&path).all(); // <- while this is not 329 | assert_eq!(nodes, vec![1, 2, 3]); 330 | ``` 331 | 332 | [#10]: https://github.com/hiltontj/serde_json_path/pull/10 333 | 334 | # Previous Versions 335 | 336 | Previous versions are not documented here. 337 | -------------------------------------------------------------------------------- /serde_json_path/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serde_json_path" 3 | version = "0.7.2" 4 | edition = "2021" 5 | license = "MIT" 6 | authors = ["Trevor Hilton "] 7 | description = "Query serde_json Values using JSONPath" 8 | documentation = "https://docs.rs/serde_json_path" 9 | repository = "https://github.com/hiltontj/serde_json_path" 10 | readme = "README.md" 11 | keywords = ["json", "jsonpath", "json_path", "serde", "serde_json"] 12 | 13 | [features] 14 | default = ["functions", "regex"] 15 | regex = ["dep:regex"] 16 | trace = ["dep:tracing", "serde_json_path_core/trace"] 17 | functions = ["serde_json_path_core/functions"] 18 | 19 | [dependencies] 20 | # local crates: 21 | serde_json_path_core = { path = "../serde_json_path_core", version = "0.2.2" } 22 | serde_json_path_macros = { path = "../serde_json_path_macros", version = "0.1.6" } 23 | 24 | # crates.io crates: 25 | inventory.workspace = true 26 | nom.workspace = true 27 | serde.workspace = true 28 | serde_json.workspace = true 29 | thiserror.workspace = true 30 | 31 | [dependencies.regex] 32 | workspace = true 33 | optional = true 34 | 35 | [dependencies.tracing] 36 | workspace = true 37 | optional = true 38 | 39 | [dev-dependencies] 40 | test-log.workspace = true 41 | tracing-subscriber.workspace = true 42 | -------------------------------------------------------------------------------- /serde_json_path/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Trevor Hilton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /serde_json_path/README.md: -------------------------------------------------------------------------------- 1 | # serde_json_path 2 | 3 | `serde_json_path` allows you to use JSONPath ([RFC 9535][rfc]) to query the [`serde_json::Value`][serde_json_value] type. 4 | 5 | [![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) 6 | [![Crates.io](https://img.shields.io/crates/v/serde_json_path)](https://crates.io/crates/serde_json_path) 7 | [![Documentation](https://docs.rs/serde_json_path/badge.svg)][docs] 8 | [![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) 9 | 10 | ## Learn More 11 | 12 | * See the [Crate Documentation][docs] for usage and examples. 13 | * See the JSONPath standard ([RFC 9535][rfc]) for more details about JSONPath query syntax and examples of its usage. 14 | * Try it out in the [Sandbox](https://serdejsonpath.live) 15 | 16 | ## License 17 | 18 | This project is licensed under the [MIT license][license]. 19 | 20 | ### Contribution 21 | 22 | Unless you explicitly state otherwise, any contribution intentionally submitted 23 | for inclusion in `serde_json_path` by you, shall be licensed as MIT, without any 24 | additional terms or conditions. 25 | 26 | [rfc]: https://www.rfc-editor.org/rfc/rfc9535.html 27 | [docs]: https://docs.rs/serde_json_path 28 | [serde_json_value]: https://docs.rs/serde_json/latest/serde_json/enum.Value.html 29 | [license]: https://github.com/hiltontj/serde_json_path/blob/main/LICENSE-MIT 30 | 31 | -------------------------------------------------------------------------------- /serde_json_path/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | 3 | use crate::parser::Error; 4 | 5 | /// Error type for JSONPath query string parsing errors 6 | #[derive(Debug, thiserror::Error)] 7 | #[error("{err}")] 8 | pub struct ParseError { 9 | err: Box, 10 | } 11 | 12 | impl ParseError { 13 | /// Get the 1-indexed error position 14 | pub fn position(&self) -> usize { 15 | self.err.position 16 | } 17 | 18 | /// Get the error message 19 | pub fn message(&self) -> &str { 20 | &self.err.message 21 | } 22 | } 23 | 24 | #[derive(Debug, thiserror::Error)] 25 | #[error("at position {position}, {message}")] 26 | struct ErrorImpl { 27 | position: usize, 28 | message: Box, 29 | } 30 | 31 | impl From<(I, Error)> for ParseError 32 | where 33 | I: Deref + std::fmt::Debug, 34 | { 35 | fn from((input, pe): (I, Error)) -> Self { 36 | #[cfg(feature = "trace")] 37 | tracing::trace!(input = %input.to_string(), parser_error = ?pe); 38 | let position = pe.calculate_position(input); 39 | let message = pe.to_string().into(); 40 | Self { 41 | err: Box::new(ErrorImpl { position, message }), 42 | } 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | mod tests { 48 | use crate::ParseError; 49 | #[cfg(feature = "trace")] 50 | use test_log::test; 51 | 52 | #[test] 53 | fn test_send() { 54 | fn assert_send() {} 55 | assert_send::(); 56 | } 57 | 58 | #[test] 59 | fn test_sync() { 60 | fn assert_sync() {} 61 | assert_sync::(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /serde_json_path/src/ext.rs: -------------------------------------------------------------------------------- 1 | use serde_json::Value; 2 | 3 | use crate::{JsonPath, NodeList}; 4 | 5 | /// Extension trait that allows for JSONPath queries directly on [`serde_json::Value`] 6 | /// 7 | /// ## Usage 8 | /// ```rust 9 | /// use serde_json::json; 10 | /// use serde_json_path::{JsonPath, JsonPathExt}; 11 | /// 12 | /// # fn main() -> Result<(), serde_json_path::ParseError> { 13 | /// let value = json!({"foo": ["bar", "baz"]}); 14 | /// let query = JsonPath::parse("$.foo[*]")?; 15 | /// let nodes = value.json_path(&query).all(); 16 | /// assert_eq!(nodes, vec!["bar", "baz"]); 17 | /// # Ok(()) 18 | /// # } 19 | /// ``` 20 | pub trait JsonPathExt { 21 | /// Query a [`serde_json::Value`] with a JSONPath query string 22 | fn json_path(&self, path: &JsonPath) -> NodeList; 23 | } 24 | 25 | impl JsonPathExt for Value { 26 | fn json_path(&self, path: &JsonPath) -> NodeList { 27 | path.query(self) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /serde_json_path/src/parser/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use std::ops::Deref; 3 | 4 | use nom::character::complete::char; 5 | use nom::combinator::all_consuming; 6 | use nom::error::{ContextError, ErrorKind, FromExternalError, ParseError}; 7 | use nom::Offset; 8 | use nom::{branch::alt, combinator::map, multi::many0, sequence::preceded, IResult}; 9 | use serde_json_path_core::spec::query::{Query, QueryKind}; 10 | use serde_json_path_core::spec::segment::QuerySegment; 11 | 12 | use self::segment::parse_segment; 13 | 14 | pub(crate) mod primitive; 15 | pub(crate) mod segment; 16 | pub(crate) mod selector; 17 | pub(crate) mod utils; 18 | 19 | type PResult<'a, O> = IResult<&'a str, O, Error<&'a str>>; 20 | 21 | #[derive(Debug, PartialEq)] 22 | pub(crate) struct Error { 23 | pub(crate) errors: Vec>, 24 | } 25 | 26 | impl Error 27 | where 28 | I: Deref, 29 | { 30 | pub(crate) fn calculate_position(&self, input: I) -> usize { 31 | self.errors 32 | .first() 33 | .map(|e| input.offset(&e.input)) 34 | .unwrap_or(0) 35 | } 36 | } 37 | 38 | impl std::fmt::Display for Error { 39 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 40 | if let Some(e) = self.errors.first() { 41 | if let Some(ctx) = e.context { 42 | write!(f, "in {ctx}, ")?; 43 | } 44 | write!(f, "{err}", err = e.kind) 45 | } else { 46 | write!(f, "empty error") 47 | } 48 | } 49 | } 50 | 51 | impl std::error::Error for Error {} 52 | 53 | #[derive(Debug, PartialEq)] 54 | pub(crate) struct ParserErrorInner { 55 | input: I, 56 | kind: ParserErrorKind, 57 | context: Option<&'static str>, 58 | } 59 | 60 | #[derive(Debug, PartialEq, thiserror::Error)] 61 | pub(crate) enum ParserErrorKind { 62 | #[error("{0}")] 63 | Message(Box), 64 | #[error("parser error")] 65 | Nom(ErrorKind), 66 | } 67 | 68 | impl ParseError for Error { 69 | fn from_error_kind(input: I, kind: ErrorKind) -> Self { 70 | Self { 71 | errors: vec![ParserErrorInner { 72 | input, 73 | kind: ParserErrorKind::Nom(kind), 74 | context: None, 75 | }], 76 | } 77 | } 78 | 79 | fn append(input: I, kind: ErrorKind, mut other: Self) -> Self { 80 | other.errors.push(ParserErrorInner { 81 | input, 82 | kind: ParserErrorKind::Nom(kind), 83 | context: None, 84 | }); 85 | other 86 | } 87 | } 88 | 89 | impl ContextError for Error { 90 | fn add_context(_input: I, ctx: &'static str, mut other: Self) -> Self { 91 | if let Some(e) = other.errors.first_mut() { 92 | e.context = Some(ctx); 93 | }; 94 | other 95 | } 96 | } 97 | 98 | impl FromExternalError for Error 99 | where 100 | E: std::error::Error + Display, 101 | { 102 | fn from_external_error(input: I, _kind: ErrorKind, e: E) -> Self { 103 | Self { 104 | errors: vec![ParserErrorInner { 105 | input, 106 | kind: ParserErrorKind::Message(e.to_string().into()), 107 | context: None, 108 | }], 109 | } 110 | } 111 | } 112 | 113 | pub(crate) trait FromInternalError { 114 | fn from_internal_error(input: I, e: E) -> Self; 115 | } 116 | 117 | impl FromInternalError for Error 118 | where 119 | E: std::error::Error + Display, 120 | { 121 | fn from_internal_error(input: I, e: E) -> Self { 122 | Self { 123 | errors: vec![ParserErrorInner { 124 | input, 125 | kind: ParserErrorKind::Message(e.to_string().into()), 126 | context: None, 127 | }], 128 | } 129 | } 130 | } 131 | 132 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 133 | fn parse_path_segments(input: &str) -> PResult> { 134 | many0(parse_segment)(input) 135 | } 136 | 137 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 138 | fn parse_root_query(input: &str) -> PResult { 139 | map(preceded(char('$'), parse_path_segments), |segments| Query { 140 | kind: QueryKind::Root, 141 | segments, 142 | })(input) 143 | } 144 | 145 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 146 | fn parse_current_query(input: &str) -> PResult { 147 | map(preceded(char('@'), parse_path_segments), |segments| Query { 148 | kind: QueryKind::Current, 149 | segments, 150 | })(input) 151 | } 152 | 153 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 154 | fn parse_query(input: &str) -> PResult { 155 | alt((parse_root_query, parse_current_query))(input) 156 | } 157 | 158 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 159 | pub(crate) fn parse_query_main(input: &str) -> PResult { 160 | all_consuming(parse_root_query)(input) 161 | } 162 | 163 | #[cfg(test)] 164 | mod tests { 165 | use serde_json_path_core::spec::{ 166 | query::QueryKind, 167 | segment::Segment, 168 | selector::{name::Name, Selector}, 169 | }; 170 | 171 | use super::{parse_query, parse_query_main}; 172 | 173 | #[test] 174 | fn root_path() { 175 | { 176 | let (_, p) = parse_query("$").unwrap(); 177 | assert!(matches!(p.kind, QueryKind::Root)); 178 | } 179 | { 180 | let (_, p) = parse_query("$.name").unwrap(); 181 | assert_eq!(p.segments[0].segment.as_dot_name().unwrap(), "name"); 182 | } 183 | { 184 | let (_, p) = parse_query("$.names['first_name']..*").unwrap(); 185 | assert_eq!(p.segments[0].segment.as_dot_name().unwrap(), "names"); 186 | let clh = p.segments[1].segment.as_long_hand().unwrap(); 187 | assert!(matches!(&clh[0], Selector::Name(Name(s)) if s == "first_name")); 188 | assert!(matches!(p.segments[2].segment, Segment::Wildcard)); 189 | } 190 | } 191 | 192 | #[test] 193 | fn current_path() { 194 | { 195 | let (_, p) = parse_query("@").unwrap(); 196 | assert!(matches!(p.kind, QueryKind::Current)); 197 | } 198 | } 199 | 200 | #[test] 201 | fn no_tail() { 202 | assert!(parse_query_main("$.a['b']tail").is_err()); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /serde_json_path/src/parser/primitive/int.rs: -------------------------------------------------------------------------------- 1 | use nom::character::complete::char; 2 | use nom::{ 3 | branch::alt, 4 | bytes::complete::{tag, take_while_m_n}, 5 | character::complete::digit0, 6 | combinator::{map_res, opt, recognize}, 7 | sequence::tuple, 8 | }; 9 | use serde_json_path_core::spec::integer::Integer; 10 | 11 | use crate::parser::PResult; 12 | 13 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 14 | fn parse_zero(input: &str) -> PResult<&str> { 15 | tag("0")(input) 16 | } 17 | 18 | fn is_non_zero_digit(chr: char) -> bool { 19 | ('1'..='9').contains(&chr) 20 | } 21 | 22 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 23 | pub(crate) fn parse_non_zero_first_digit(input: &str) -> PResult<&str> { 24 | take_while_m_n(1, 1, is_non_zero_digit)(input) 25 | } 26 | 27 | /// Parse a non-zero integer as `i64` 28 | /// 29 | /// This does not allow leading `0`'s, e.g., `0123` 30 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 31 | fn parse_non_zero_int(input: &str) -> PResult<&str> { 32 | recognize(tuple((opt(char('-')), parse_non_zero_first_digit, digit0)))(input) 33 | } 34 | 35 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 36 | pub(crate) fn parse_int_string(input: &str) -> PResult<&str> { 37 | alt((parse_zero, parse_non_zero_int))(input) 38 | } 39 | 40 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 41 | pub(crate) fn parse_int(input: &str) -> PResult { 42 | map_res(parse_int_string, |i_str| i_str.parse())(input) 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use serde_json_path_core::spec::integer::Integer; 48 | 49 | use crate::parser::primitive::int::parse_int; 50 | 51 | #[test] 52 | fn parse_integers() { 53 | assert_eq!(parse_int("0"), Ok(("", Integer::from_i64_unchecked(0)))); 54 | assert_eq!(parse_int("10"), Ok(("", Integer::from_i64_unchecked(10)))); 55 | assert_eq!(parse_int("-10"), Ok(("", Integer::from_i64_unchecked(-10)))); 56 | assert_eq!(parse_int("010"), Ok(("10", Integer::from_i64_unchecked(0)))); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /serde_json_path/src/parser/primitive/mod.rs: -------------------------------------------------------------------------------- 1 | use nom::{branch::alt, bytes::complete::tag, combinator::value}; 2 | 3 | use super::PResult; 4 | 5 | pub(crate) mod int; 6 | pub(crate) mod number; 7 | pub(crate) mod string; 8 | 9 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 10 | pub(crate) fn parse_null(input: &str) -> PResult<()> { 11 | value((), tag("null"))(input) 12 | } 13 | 14 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 15 | pub(crate) fn parse_bool(input: &str) -> PResult { 16 | let parse_true = value(true, tag("true")); 17 | let parse_false = value(false, tag("false")); 18 | alt((parse_true, parse_false))(input) 19 | } 20 | -------------------------------------------------------------------------------- /serde_json_path/src/parser/primitive/number.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use nom::{ 4 | branch::alt, 5 | bytes::complete::tag, 6 | character::complete::{char, digit0, digit1, one_of}, 7 | combinator::{map_res, opt, recognize}, 8 | sequence::{preceded, tuple}, 9 | }; 10 | use serde_json::Number; 11 | 12 | use crate::parser::PResult; 13 | 14 | use super::int::parse_int_string; 15 | 16 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 17 | fn parse_fractional(input: &str) -> PResult<&str> { 18 | preceded(char('.'), digit1)(input) 19 | } 20 | 21 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 22 | fn parse_exponent(input: &str) -> PResult<&str> { 23 | recognize(tuple((one_of("eE"), opt(one_of("-+")), digit0)))(input) 24 | } 25 | 26 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 27 | fn parse_number_string(input: &str) -> PResult<&str> { 28 | recognize(tuple(( 29 | alt((parse_int_string, tag("-0"))), 30 | opt(parse_fractional), 31 | opt(parse_exponent), 32 | )))(input) 33 | } 34 | 35 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 36 | pub(crate) fn parse_number(input: &str) -> PResult { 37 | map_res(parse_number_string, Number::from_str)(input) 38 | } 39 | 40 | #[cfg(test)] 41 | mod tests { 42 | use serde_json::Number; 43 | 44 | use super::parse_number; 45 | 46 | #[test] 47 | fn test_numbers() { 48 | assert_eq!(parse_number("123"), Ok(("", Number::from(123)))); 49 | assert_eq!(parse_number("-1"), Ok(("", Number::from(-1)))); 50 | assert_eq!( 51 | parse_number("1e10"), 52 | Ok(("", Number::from_f64(1e10).unwrap())) 53 | ); 54 | assert_eq!( 55 | parse_number("1.0001"), 56 | Ok(("", Number::from_f64(1.0001).unwrap())) 57 | ); 58 | assert_eq!( 59 | parse_number("-0"), 60 | Ok(("", Number::from_f64(-0.0).unwrap())) 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /serde_json_path/src/parser/primitive/string.rs: -------------------------------------------------------------------------------- 1 | use nom::bytes::complete::{tag, take_while}; 2 | use nom::character::complete::{anychar, char}; 3 | use nom::character::streaming::one_of; 4 | use nom::combinator::{cut, map, recognize, verify}; 5 | use nom::error::context; 6 | use nom::sequence::{pair, separated_pair, tuple}; 7 | use nom::{ 8 | branch::alt, 9 | bytes::complete::take_while_m_n, 10 | combinator::{map_opt, map_res, value}, 11 | multi::fold_many0, 12 | sequence::{delimited, preceded}, 13 | }; 14 | 15 | use crate::parser::utils::cut_with; 16 | use crate::parser::PResult; 17 | 18 | #[derive(Debug, Copy, Clone)] 19 | enum Quotes { 20 | Single, 21 | Double, 22 | } 23 | 24 | fn is_digit(chr: &char) -> bool { 25 | chr.is_ascii_digit() 26 | } 27 | 28 | fn is_hex_digit(chr: char) -> bool { 29 | is_digit(&chr) || ('A'..='F').contains(&chr) || ('a'..='f').contains(&chr) 30 | } 31 | 32 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 33 | fn parse_digit(input: &str) -> PResult { 34 | verify(anychar, is_digit)(input) 35 | } 36 | 37 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None))] 38 | fn parse_n_hex_digits(n: usize) -> impl Fn(&str) -> PResult<&str> { 39 | move |input: &str| take_while_m_n(n, n, is_hex_digit)(input) 40 | } 41 | 42 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 43 | fn parse_non_surrogate(input: &str) -> PResult { 44 | let non_d_base = alt((parse_digit, one_of("ABCEFabcdef"))); 45 | let non_d_based = pair(non_d_base, parse_n_hex_digits(3)); 46 | let zero_to_7 = verify(anychar, |c: &char| ('0'..='7').contains(c)); 47 | let d_based = tuple((one_of("Dd"), zero_to_7, parse_n_hex_digits(2))); 48 | let parse_u32 = map_res(alt((recognize(non_d_based), recognize(d_based))), |hex| { 49 | u32::from_str_radix(hex, 16) 50 | }); 51 | context("non surrogate", map_opt(parse_u32, char::from_u32))(input) 52 | } 53 | 54 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 55 | fn parse_low_surrogate(input: &str) -> PResult { 56 | context( 57 | "low surrogate", 58 | map_res( 59 | recognize(tuple(( 60 | one_of("Dd"), 61 | one_of("CDEFcdef"), 62 | parse_n_hex_digits(2), 63 | ))), 64 | |hex| u16::from_str_radix(hex, 16), 65 | ), 66 | )(input) 67 | } 68 | 69 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 70 | fn parse_high_surrogate(input: &str) -> PResult { 71 | context( 72 | "high surrogate", 73 | map_res( 74 | recognize(tuple((char('D'), one_of("89AB"), parse_n_hex_digits(2)))), 75 | |hex| u16::from_str_radix(hex, 16), 76 | ), 77 | )(input) 78 | } 79 | 80 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 81 | fn parse_surrogate(input: &str) -> PResult { 82 | context( 83 | "surrogate pair", 84 | map_res( 85 | separated_pair(parse_high_surrogate, tag("\\u"), parse_low_surrogate), 86 | |(h, l)| String::from_utf16(&[h, l]), 87 | ), 88 | )(input) 89 | } 90 | 91 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 92 | fn parse_hex_char(input: &str) -> PResult { 93 | alt((map(parse_non_surrogate, String::from), parse_surrogate))(input) 94 | } 95 | 96 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 97 | fn parse_unicode_sequence(input: &str) -> PResult { 98 | context("unicode sequence", preceded(char('u'), parse_hex_char))(input) 99 | } 100 | 101 | fn parse_escaped_quote(quoted_with: Quotes) -> impl Fn(&str) -> PResult { 102 | move |input: &str| match quoted_with { 103 | Quotes::Single => value('\u{0027}', char('\''))(input), 104 | Quotes::Double => value('\u{0022}', char('"'))(input), 105 | } 106 | } 107 | 108 | fn parse_escaped_char(quoted_with: Quotes) -> impl Fn(&str) -> PResult { 109 | move |input: &str| { 110 | context( 111 | "escaped character", 112 | preceded( 113 | char('\\'), 114 | alt(( 115 | map( 116 | alt(( 117 | value('\u{0008}', char('b')), 118 | value('\u{0009}', char('t')), 119 | value('\u{000A}', char('n')), 120 | value('\u{000C}', char('f')), 121 | value('\u{000D}', char('r')), 122 | value('\u{002F}', char('/')), 123 | value('\u{005C}', char('\\')), 124 | parse_escaped_quote(quoted_with), 125 | )), 126 | String::from, 127 | ), 128 | parse_unicode_sequence, 129 | )), 130 | ), 131 | )(input) 132 | } 133 | } 134 | 135 | fn is_valid_unescaped_char(chr: char, quoted_with: Quotes) -> bool { 136 | let invalid_quote_char = match quoted_with { 137 | Quotes::Single => '\'', 138 | Quotes::Double => '"', 139 | }; 140 | if chr == invalid_quote_char { 141 | return false; 142 | } 143 | match chr { 144 | '\u{20}'..='\u{5B}' // Omit control characters 145 | | '\u{5D}'..='\u{10FFFF}' => true, // Omit \ 146 | _ => false, 147 | } 148 | } 149 | 150 | fn parse_unescaped(quoted_with: Quotes) -> impl Fn(&str) -> PResult<&str> { 151 | move |input: &str| { 152 | context( 153 | "unescaped character", 154 | verify( 155 | take_while(|chr| is_valid_unescaped_char(chr, quoted_with)), 156 | |s: &str| !s.is_empty(), 157 | ), 158 | )(input) 159 | } 160 | } 161 | 162 | fn parse_fragment(quoted_with: Quotes) -> impl Fn(&str) -> PResult { 163 | move |input: &str| { 164 | alt(( 165 | map(parse_unescaped(quoted_with), String::from), 166 | parse_escaped_char(quoted_with), 167 | ))(input) 168 | } 169 | } 170 | 171 | fn parse_internal(quoted_with: Quotes) -> impl Fn(&str) -> PResult { 172 | move |input: &str| { 173 | fold_many0( 174 | parse_fragment(quoted_with), 175 | String::new, 176 | |mut string, fragment| { 177 | string.push_str(fragment.as_str()); 178 | string 179 | }, 180 | )(input) 181 | } 182 | } 183 | 184 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 185 | fn parse_single_quoted(input: &str) -> PResult { 186 | context( 187 | "single quoted", 188 | delimited( 189 | char('\''), 190 | parse_internal(Quotes::Single), 191 | cut_with(char('\''), |_| StringError::ExpectedEndQuote), 192 | ), 193 | )(input) 194 | } 195 | 196 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 197 | fn parse_double_quoted(input: &str) -> PResult { 198 | context( 199 | "double quoted", 200 | delimited(char('"'), parse_internal(Quotes::Double), cut(char('"'))), 201 | )(input) 202 | } 203 | 204 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 205 | pub(crate) fn parse_string_literal(input: &str) -> PResult { 206 | context( 207 | "string literal", 208 | alt((parse_single_quoted, parse_double_quoted)), 209 | )(input) 210 | } 211 | 212 | #[derive(Debug, thiserror::Error)] 213 | pub(crate) enum StringError { 214 | #[error("expected an ending quote")] 215 | ExpectedEndQuote, 216 | } 217 | 218 | #[cfg(test)] 219 | mod tests { 220 | use crate::parser::primitive::string::{parse_escaped_char, Quotes}; 221 | 222 | use super::parse_string_literal; 223 | 224 | #[test] 225 | fn valid_double_quoted_selectors() { 226 | assert_eq!( 227 | parse_string_literal("\"test\""), 228 | Ok(("", String::from("test"))) 229 | ); 230 | assert_eq!( 231 | parse_string_literal("\"test\\n\""), 232 | Ok(("", String::from("test\n"))) 233 | ); 234 | assert_eq!( 235 | parse_string_literal("\"test\\ntest\""), 236 | Ok(("", String::from("test\ntest"))) 237 | ); 238 | assert_eq!( 239 | parse_string_literal("\"test\\\"\""), 240 | Ok(("", String::from("test\""))) 241 | ); 242 | assert_eq!( 243 | parse_string_literal("\"tes't\""), 244 | Ok(("", String::from("tes't"))) 245 | ); 246 | } 247 | 248 | #[test] 249 | fn valid_single_quoted_selectors() { 250 | assert_eq!( 251 | parse_string_literal("'test'"), 252 | Ok(("", String::from("test"))) 253 | ); 254 | assert_eq!( 255 | parse_string_literal(r#"'te"st'"#), 256 | Ok(("", String::from("te\"st"))) 257 | ); 258 | assert_eq!( 259 | parse_string_literal(r"'te\'st'"), 260 | Ok(("", String::from("te'st"))) 261 | ); 262 | } 263 | 264 | #[test] 265 | fn invalid_unicode() { 266 | { 267 | for c in '\u{00}'..'\u{20}' { 268 | let input = format!("{c}"); 269 | let result = parse_escaped_char(Quotes::Double)(&input); 270 | assert!(result.is_err()); 271 | } 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /serde_json_path/src/parser/segment.rs: -------------------------------------------------------------------------------- 1 | use nom::bytes::complete::tag; 2 | use nom::character::complete::char; 3 | use nom::error::context; 4 | use nom::sequence::terminated; 5 | use nom::{ 6 | branch::alt, 7 | bytes::complete::take_while1, 8 | character::complete::{alpha1, digit1, multispace0}, 9 | combinator::{map, recognize}, 10 | multi::{fold_many0, separated_list1}, 11 | sequence::{delimited, pair, preceded}, 12 | }; 13 | use serde_json_path_core::spec::segment::{QuerySegment, QuerySegmentKind, Segment}; 14 | use serde_json_path_core::spec::selector::Selector; 15 | 16 | use super::selector::{parse_selector, parse_wildcard_selector}; 17 | use super::utils::cut_with; 18 | use super::PResult; 19 | 20 | // The specification requires that a non-ASCII character is in the range 21 | // %x80-10FFFF. In Rust, the `char` type can not hold characters higher 22 | // than %x10FFFF, so we only need to check the lower bound in this function. 23 | // 24 | // See: https://doc.rust-lang.org/std/primitive.char.html#validity 25 | fn is_non_ascii_unicode(chr: char) -> bool { 26 | chr >= '\u{0080}' 27 | } 28 | 29 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 30 | fn parse_non_ascii_unicode(input: &str) -> PResult<&str> { 31 | take_while1(is_non_ascii_unicode)(input) 32 | } 33 | 34 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 35 | fn parse_name_first(input: &str) -> PResult<&str> { 36 | alt((alpha1, recognize(char('_')), parse_non_ascii_unicode))(input) 37 | } 38 | 39 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 40 | fn parse_name_char(input: &str) -> PResult<&str> { 41 | alt((digit1, parse_name_first))(input) 42 | } 43 | 44 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 45 | pub(crate) fn parse_dot_member_name(input: &str) -> PResult { 46 | map( 47 | recognize(pair( 48 | cut_with(parse_name_first, |_| { 49 | SegmentError::InvalidFirstNameCharacter 50 | }), 51 | fold_many0(parse_name_char, String::new, |mut s, item| { 52 | s.push_str(item); 53 | s 54 | }), 55 | )), 56 | |s| s.to_owned(), 57 | )(input) 58 | } 59 | 60 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 61 | fn parse_dot_member_name_shorthand(input: &str) -> PResult { 62 | map( 63 | preceded(char('.'), context("dot member name", parse_dot_member_name)), 64 | Segment::DotName, 65 | )(input) 66 | } 67 | 68 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 69 | fn parse_multi_selector(input: &str) -> PResult> { 70 | separated_list1( 71 | delimited(multispace0, char(','), multispace0), 72 | parse_selector, 73 | )(input) 74 | } 75 | 76 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 77 | fn parse_child_long_hand(input: &str) -> PResult { 78 | context( 79 | "long-hand segment", 80 | preceded( 81 | pair(char('['), multispace0), 82 | terminated( 83 | map(parse_multi_selector, Segment::LongHand), 84 | pair( 85 | multispace0, 86 | cut_with(char(']'), |_| SegmentError::ExpectedClosingBrace), 87 | ), 88 | ), 89 | ), 90 | )(input) 91 | } 92 | 93 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 94 | fn parse_dot_wildcard_shorthand(input: &str) -> PResult { 95 | map(preceded(char('.'), parse_wildcard_selector), |_| { 96 | Segment::Wildcard 97 | })(input) 98 | } 99 | 100 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 101 | fn parse_child_segment(input: &str) -> PResult { 102 | preceded( 103 | multispace0, 104 | alt(( 105 | parse_dot_wildcard_shorthand, 106 | parse_dot_member_name_shorthand, 107 | parse_child_long_hand, 108 | )), 109 | )(input) 110 | } 111 | 112 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 113 | fn parse_descendant_segment(input: &str) -> PResult { 114 | preceded( 115 | tag(".."), 116 | alt(( 117 | map(parse_wildcard_selector, |_| Segment::Wildcard), 118 | parse_child_long_hand, 119 | map(parse_dot_member_name, Segment::DotName), 120 | )), 121 | )(input) 122 | } 123 | 124 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 125 | pub(crate) fn parse_segment(input: &str) -> PResult { 126 | alt(( 127 | map(parse_descendant_segment, |inner| QuerySegment { 128 | kind: QuerySegmentKind::Descendant, 129 | segment: inner, 130 | }), 131 | map(parse_child_segment, |inner| QuerySegment { 132 | kind: QuerySegmentKind::Child, 133 | segment: inner, 134 | }), 135 | ))(input) 136 | } 137 | 138 | #[doc(hidden)] 139 | #[derive(Debug, thiserror::Error)] 140 | pub(crate) enum SegmentError { 141 | #[error("must start with lowercase alpha or '_'")] 142 | InvalidFirstNameCharacter, 143 | #[error("expected closing ']'")] 144 | ExpectedClosingBrace, 145 | } 146 | 147 | #[cfg(test)] 148 | mod tests { 149 | #[cfg(feature = "trace")] 150 | use test_log::test; 151 | 152 | use nom::combinator::all_consuming; 153 | use serde_json_path_core::spec::{ 154 | integer::Integer, 155 | selector::{index::Index, name::Name, slice::Slice, Selector}, 156 | }; 157 | 158 | use super::{ 159 | parse_child_long_hand, parse_child_segment, parse_descendant_segment, 160 | parse_dot_member_name_shorthand, Segment, 161 | }; 162 | 163 | #[test] 164 | fn dot_member_names() { 165 | assert!(matches!( 166 | parse_dot_member_name_shorthand(".name"), 167 | Ok(("", Segment::DotName(s))) if s == "name", 168 | )); 169 | assert!(matches!( 170 | parse_dot_member_name_shorthand(".foo_bar"), 171 | Ok(("", Segment::DotName(s))) if s == "foo_bar", 172 | )); 173 | assert!(parse_dot_member_name_shorthand(". space").is_err()); 174 | assert!(all_consuming(parse_dot_member_name_shorthand)(".no-dash").is_err()); 175 | assert!(parse_dot_member_name_shorthand(".1no_num_1st").is_err()); 176 | } 177 | 178 | #[test] 179 | fn child_long_hand() { 180 | { 181 | let (_, sk) = parse_child_long_hand(r#"["name"]"#).unwrap(); 182 | let s = sk.as_long_hand().unwrap(); 183 | assert_eq!(s[0], Selector::Name(Name::from("name"))); 184 | } 185 | { 186 | let (_, sk) = parse_child_long_hand(r#"['name']"#).unwrap(); 187 | let s = sk.as_long_hand().unwrap(); 188 | assert_eq!(s[0], Selector::Name(Name::from("name"))); 189 | } 190 | { 191 | let (_, sk) = parse_child_long_hand(r#"["name","test"]"#).unwrap(); 192 | let s = sk.as_long_hand().unwrap(); 193 | assert_eq!(s[0], Selector::Name(Name::from("name"))); 194 | assert_eq!(s[1], Selector::Name(Name::from("test"))); 195 | } 196 | { 197 | let (_, sk) = parse_child_long_hand(r#"['name',10,0:3]"#).unwrap(); 198 | let s = sk.as_long_hand().unwrap(); 199 | assert_eq!(s[0], Selector::Name(Name::from("name"))); 200 | assert_eq!( 201 | s[1], 202 | Selector::Index(Index(Integer::from_i64_unchecked(10))) 203 | ); 204 | assert_eq!( 205 | s[2], 206 | Selector::ArraySlice(Slice::new().with_start(0).with_end(3)) 207 | ); 208 | } 209 | { 210 | let (_, sk) = parse_child_long_hand(r#"[::,*]"#).unwrap(); 211 | let s = sk.as_long_hand().unwrap(); 212 | assert_eq!(s[0], Selector::ArraySlice(Slice::new())); 213 | assert_eq!(s[1], Selector::Wildcard); 214 | } 215 | { 216 | let err = parse_child_long_hand("[010]").unwrap_err(); 217 | match err { 218 | nom::Err::Error(e) | nom::Err::Failure(e) => println!("{e:#?}"), 219 | nom::Err::Incomplete(_) => panic!("wrong error kind: {err:?}"), 220 | } 221 | } 222 | } 223 | 224 | #[test] 225 | fn child_segment() { 226 | { 227 | let (_, sk) = parse_child_segment(".name").unwrap(); 228 | assert_eq!(sk.as_dot_name(), Some("name")); 229 | } 230 | { 231 | let (_, sk) = parse_child_segment(".*").unwrap(); 232 | assert!(matches!(sk, Segment::Wildcard)); 233 | } 234 | { 235 | let (_, sk) = parse_child_segment("[*]").unwrap(); 236 | let s = sk.as_long_hand().unwrap(); 237 | assert_eq!(s[0], Selector::Wildcard); 238 | } 239 | } 240 | 241 | #[test] 242 | fn descendant_segment() { 243 | { 244 | let (_, sk) = parse_descendant_segment("..['name']").unwrap(); 245 | let s = sk.as_long_hand().unwrap(); 246 | assert_eq!(s[0], Selector::Name(Name::from("name"))); 247 | } 248 | { 249 | let (_, sk) = parse_descendant_segment("..name").unwrap(); 250 | assert_eq!(sk.as_dot_name().unwrap(), "name"); 251 | } 252 | { 253 | let (_, sk) = parse_descendant_segment("..*").unwrap(); 254 | assert!(matches!(sk, Segment::Wildcard)); 255 | } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /serde_json_path/src/parser/selector/filter.rs: -------------------------------------------------------------------------------- 1 | use nom::character::complete::{char, multispace0}; 2 | use nom::combinator::{cut, map, map_res}; 3 | use nom::multi::separated_list1; 4 | use nom::sequence::{delimited, pair, preceded, separated_pair, tuple}; 5 | use nom::{branch::alt, bytes::complete::tag, combinator::value}; 6 | use serde_json_path_core::spec::functions::{ 7 | FunctionArgType, FunctionExpr, FunctionValidationError, Validated, 8 | }; 9 | use serde_json_path_core::spec::selector::filter::{ 10 | BasicExpr, Comparable, ComparisonExpr, ComparisonOperator, ExistExpr, Filter, Literal, 11 | LogicalAndExpr, LogicalOrExpr, SingularQuery, 12 | }; 13 | 14 | use super::function::parse_function_expr; 15 | use crate::parser::primitive::number::parse_number; 16 | use crate::parser::primitive::string::parse_string_literal; 17 | use crate::parser::primitive::{parse_bool, parse_null}; 18 | use crate::parser::utils::uncut; 19 | use crate::parser::{parse_query, PResult}; 20 | 21 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 22 | pub(crate) fn parse_filter(input: &str) -> PResult { 23 | map( 24 | preceded(pair(char('?'), multispace0), parse_logical_or_expr), 25 | Filter, 26 | )(input) 27 | } 28 | 29 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 30 | fn parse_logical_and(input: &str) -> PResult { 31 | map( 32 | separated_list1( 33 | tuple((multispace0, tag("&&"), multispace0)), 34 | parse_basic_expr, 35 | ), 36 | LogicalAndExpr, 37 | )(input) 38 | } 39 | 40 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 41 | pub(crate) fn parse_logical_or_expr(input: &str) -> PResult { 42 | map( 43 | separated_list1( 44 | tuple((multispace0, tag("||"), multispace0)), 45 | parse_logical_and, 46 | ), 47 | LogicalOrExpr, 48 | )(input) 49 | } 50 | 51 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 52 | fn parse_exist_expr_inner(input: &str) -> PResult { 53 | map(parse_query, ExistExpr)(input) 54 | } 55 | 56 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 57 | fn parse_exist_expr(input: &str) -> PResult { 58 | map(parse_exist_expr_inner, BasicExpr::Exist)(input) 59 | } 60 | 61 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 62 | fn parse_not_exist_expr(input: &str) -> PResult { 63 | map( 64 | preceded(pair(char('!'), multispace0), parse_exist_expr_inner), 65 | BasicExpr::NotExist, 66 | )(input) 67 | } 68 | 69 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 70 | fn parse_func_expr_inner(input: &str) -> PResult> { 71 | cut(map_res(parse_function_expr, |fe| match fe.return_type { 72 | FunctionArgType::Logical | FunctionArgType::Nodelist => Ok(fe), 73 | _ => Err(FunctionValidationError::IncorrectFunctionReturnType), 74 | }))(input) 75 | } 76 | 77 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 78 | fn parse_func_expr(input: &str) -> PResult { 79 | map(parse_func_expr_inner, BasicExpr::FuncExpr)(input) 80 | } 81 | 82 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 83 | fn parse_not_func_expr(input: &str) -> PResult { 84 | map( 85 | preceded(pair(char('!'), multispace0), parse_func_expr_inner), 86 | BasicExpr::NotFuncExpr, 87 | )(input) 88 | } 89 | 90 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 91 | fn parse_paren_expr_inner(input: &str) -> PResult { 92 | delimited( 93 | pair(char('('), multispace0), 94 | parse_logical_or_expr, 95 | pair(multispace0, char(')')), 96 | )(input) 97 | } 98 | 99 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 100 | fn parse_paren_expr(input: &str) -> PResult { 101 | map(parse_paren_expr_inner, BasicExpr::Paren)(input) 102 | } 103 | 104 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 105 | fn parse_not_parent_expr(input: &str) -> PResult { 106 | map( 107 | preceded(pair(char('!'), multispace0), parse_paren_expr_inner), 108 | BasicExpr::NotParen, 109 | )(input) 110 | } 111 | 112 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 113 | fn parse_basic_expr(input: &str) -> PResult { 114 | alt(( 115 | parse_not_parent_expr, 116 | parse_paren_expr, 117 | map(parse_comp_expr, BasicExpr::Relation), 118 | parse_not_exist_expr, 119 | parse_exist_expr, 120 | parse_not_func_expr, 121 | parse_func_expr, 122 | ))(input) 123 | } 124 | 125 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 126 | fn parse_comp_expr(input: &str) -> PResult { 127 | map( 128 | separated_pair( 129 | parse_comparable, 130 | multispace0, 131 | separated_pair(parse_comparison_operator, multispace0, parse_comparable), 132 | ), 133 | |(left, (op, right))| ComparisonExpr { left, op, right }, 134 | )(input) 135 | } 136 | 137 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 138 | fn parse_comparison_operator(input: &str) -> PResult { 139 | alt(( 140 | value(ComparisonOperator::EqualTo, tag("==")), 141 | value(ComparisonOperator::NotEqualTo, tag("!=")), 142 | value(ComparisonOperator::LessThanEqualTo, tag("<=")), 143 | value(ComparisonOperator::GreaterThanEqualTo, tag(">=")), 144 | value(ComparisonOperator::LessThan, char('<')), 145 | value(ComparisonOperator::GreaterThan, char('>')), 146 | ))(input) 147 | } 148 | 149 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 150 | pub(crate) fn parse_literal(input: &str) -> PResult { 151 | alt(( 152 | map(parse_string_literal, Literal::String), 153 | map(parse_number, Literal::Number), 154 | map(parse_bool, Literal::Bool), 155 | value(Literal::Null, parse_null), 156 | ))(input) 157 | } 158 | 159 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 160 | fn parse_literal_comparable(input: &str) -> PResult { 161 | map(parse_literal, Comparable::Literal)(input) 162 | } 163 | 164 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 165 | pub(crate) fn parse_singular_path(input: &str) -> PResult { 166 | map_res(parse_query, |q| q.try_into())(input) 167 | } 168 | 169 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 170 | fn parse_singular_path_comparable(input: &str) -> PResult { 171 | map(parse_singular_path, Comparable::SingularQuery)(input) 172 | } 173 | 174 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 175 | fn parse_function_expr_comparable(input: &str) -> PResult { 176 | map_res(parse_function_expr, |fe| { 177 | match fe.return_type { 178 | FunctionArgType::Value => Ok(fe), 179 | _ => Err(FunctionValidationError::IncorrectFunctionReturnType), 180 | } 181 | .map(Comparable::FunctionExpr) 182 | })(input) 183 | } 184 | 185 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 186 | pub(crate) fn parse_comparable(input: &str) -> PResult { 187 | uncut(alt(( 188 | parse_literal_comparable, 189 | parse_singular_path_comparable, 190 | parse_function_expr_comparable, 191 | )))(input) 192 | } 193 | 194 | #[cfg(test)] 195 | mod tests { 196 | #[cfg(feature = "trace")] 197 | use test_log::test; 198 | 199 | use serde_json::Number; 200 | use serde_json_path_core::spec::selector::filter::{Comparable, Literal, SingularQuerySegment}; 201 | 202 | use crate::parser::selector::{ 203 | filter::{parse_literal, ComparisonOperator}, 204 | Index, Name, 205 | }; 206 | 207 | use super::{parse_basic_expr, parse_comp_expr, parse_comparable}; 208 | 209 | #[test] 210 | fn literals() { 211 | { 212 | let (_, lit) = parse_literal("null").unwrap(); 213 | assert!(matches!(lit, Literal::Null)); 214 | } 215 | { 216 | let (_, lit) = parse_literal("true").unwrap(); 217 | assert!(matches!(lit, Literal::Bool(true))); 218 | } 219 | { 220 | let (_, lit) = parse_literal("false").unwrap(); 221 | assert!(matches!(lit, Literal::Bool(false))); 222 | } 223 | { 224 | let (_, lit) = parse_literal("\"test\"").unwrap(); 225 | assert!(matches!(lit, Literal::String(s) if s == "test")); 226 | } 227 | { 228 | let (_, lit) = parse_literal("'test'").unwrap(); 229 | assert!(matches!(lit, Literal::String(s) if s == "test")); 230 | } 231 | { 232 | let (_, lit) = parse_literal("123").unwrap(); 233 | assert!(matches!(lit, Literal::Number(n) if n == Number::from(123))); 234 | } 235 | } 236 | 237 | #[test] 238 | fn comp_expr() { 239 | let (_, cxp) = parse_comp_expr("true != false").unwrap(); 240 | assert!(matches!(cxp.left, Comparable::Literal(Literal::Bool(true)))); 241 | assert!(matches!(cxp.op, ComparisonOperator::NotEqualTo)); 242 | assert!(matches!( 243 | cxp.right, 244 | Comparable::Literal(Literal::Bool(false)) 245 | )); 246 | } 247 | 248 | #[test] 249 | fn basic_expr() { 250 | let (_, bxp) = parse_basic_expr("true == true").unwrap(); 251 | let cx = bxp.as_relation().unwrap(); 252 | assert!(matches!(cx.left, Comparable::Literal(Literal::Bool(true)))); 253 | assert!(matches!(cx.right, Comparable::Literal(Literal::Bool(true)))); 254 | assert!(matches!(cx.op, ComparisonOperator::EqualTo)); 255 | } 256 | 257 | #[test] 258 | fn singular_path_comparables() { 259 | { 260 | let (_, cmp) = parse_comparable("@.name").unwrap(); 261 | let sp = &cmp.as_singular_path().unwrap().segments; 262 | assert!(matches!(&sp[0], SingularQuerySegment::Name(Name(s)) if s == "name")); 263 | } 264 | { 265 | let (_, cmp) = parse_comparable("$.data[0].id").unwrap(); 266 | let sp = &cmp.as_singular_path().unwrap().segments; 267 | assert!(matches!(&sp[0], SingularQuerySegment::Name(Name(s)) if s == "data")); 268 | assert!(matches!(&sp[1], SingularQuerySegment::Index(Index(i)) if i == &0)); 269 | assert!(matches!(&sp[2], SingularQuerySegment::Name(Name(s)) if s == "id")); 270 | } 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /serde_json_path/src/parser/selector/function/mod.rs: -------------------------------------------------------------------------------- 1 | use nom::character::complete::char; 2 | use nom::combinator::{cut, map_res}; 3 | use nom::multi::separated_list0; 4 | use nom::sequence::{preceded, terminated}; 5 | use nom::{ 6 | branch::alt, 7 | character::complete::{multispace0, satisfy}, 8 | combinator::map, 9 | multi::fold_many1, 10 | sequence::{delimited, pair}, 11 | }; 12 | use serde_json_path_core::spec::functions::{ 13 | Function, FunctionExpr, FunctionExprArg, FunctionValidationError, Validated, 14 | }; 15 | 16 | pub(crate) mod registry; 17 | 18 | use crate::parser::{parse_query, PResult}; 19 | 20 | use self::registry::REGISTRY; 21 | 22 | use super::filter::{parse_literal, parse_logical_or_expr, parse_singular_path}; 23 | 24 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 25 | fn parse_function_name_first(input: &str) -> PResult { 26 | satisfy(|c| c.is_ascii_lowercase())(input) 27 | } 28 | 29 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 30 | fn parse_function_name_char(input: &str) -> PResult { 31 | alt(( 32 | parse_function_name_first, 33 | char('_'), 34 | satisfy(|c| c.is_ascii_digit()), 35 | ))(input) 36 | } 37 | 38 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 39 | fn parse_function_name(input: &str) -> PResult { 40 | map( 41 | pair( 42 | parse_function_name_first, 43 | fold_many1( 44 | parse_function_name_char, 45 | String::new, 46 | |mut string, fragment| { 47 | string.push(fragment); 48 | string 49 | }, 50 | ), 51 | ), 52 | |(first, rest)| format!("{first}{rest}"), 53 | )(input) 54 | } 55 | 56 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 57 | fn parse_function_argument(input: &str) -> PResult { 58 | alt(( 59 | map(parse_literal, FunctionExprArg::Literal), 60 | map(parse_singular_path, FunctionExprArg::SingularQuery), 61 | map(parse_query, FunctionExprArg::FilterQuery), 62 | map(parse_function_expr, FunctionExprArg::FunctionExpr), 63 | map(parse_logical_or_expr, FunctionExprArg::LogicalExpr), 64 | ))(input) 65 | } 66 | 67 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 68 | pub(crate) fn parse_function_expr(input: &str) -> PResult> { 69 | cut(map_res( 70 | pair( 71 | parse_function_name, 72 | delimited( 73 | terminated(char('('), multispace0), 74 | separated_list0( 75 | delimited(multispace0, char(','), multispace0), 76 | parse_function_argument, 77 | ), 78 | preceded(multispace0, char(')')), 79 | ), 80 | ), 81 | |(name, args)| { 82 | #[cfg(feature = "functions")] 83 | for f in inventory::iter:: { 84 | if f.name == name { 85 | (f.validator)(args.as_slice())?; 86 | return Ok(FunctionExpr { 87 | name, 88 | args, 89 | return_type: f.result_type, 90 | validated: Validated { 91 | evaluator: f.evaluator, 92 | }, 93 | }); 94 | } 95 | } 96 | if let Some(f) = REGISTRY.get(name.as_str()) { 97 | (f.validator)(args.as_slice())?; 98 | return Ok(FunctionExpr { 99 | name, 100 | args, 101 | return_type: f.result_type, 102 | validated: Validated { 103 | evaluator: f.evaluator, 104 | }, 105 | }); 106 | } 107 | Err(FunctionValidationError::Undefined { name }) 108 | }, 109 | ))(input) 110 | } 111 | -------------------------------------------------------------------------------- /serde_json_path/src/parser/selector/function/registry.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, sync::LazyLock}; 2 | 3 | use serde_json::Value; 4 | use serde_json_path_core::spec::functions::{Function, LogicalType, NodesType, ValueType}; 5 | 6 | /// The main registry of functions for use in JSONPath queries 7 | /// 8 | /// These come directly from the JSONPath specification, which includes a registry of standardized 9 | /// functions. 10 | /// 11 | /// # Note 12 | /// 13 | /// There is a function in `serde_json_path_core/src/spec/functions.rs` that gives 14 | /// the return type for each function registered here. When adding new functions to 15 | /// the register, i.e., when new functions are standardized, the function there needs 16 | /// to be updated too. 17 | pub(crate) static REGISTRY: LazyLock> = 18 | LazyLock::new(|| { 19 | let mut m = HashMap::new(); 20 | m.insert("length", &LENGTH_FUNC); 21 | m.insert("count", &COUNT_FUNC); 22 | #[cfg(feature = "regex")] 23 | { 24 | m.insert("match", &MATCH_FUNC); 25 | m.insert("search", &SEARCH_FUNC); 26 | } 27 | m.insert("value", &VALUE_FUNC); 28 | m 29 | }); 30 | 31 | fn value_length(value: &Value) -> Option { 32 | match value { 33 | Value::String(s) => Some(s.chars().count()), 34 | Value::Array(a) => Some(a.len()), 35 | Value::Object(o) => Some(o.len()), 36 | _ => None, 37 | } 38 | } 39 | 40 | #[serde_json_path_macros::register(target = LENGTH_FUNC)] 41 | fn length(value: ValueType) -> ValueType { 42 | match value { 43 | ValueType::Value(v) => value_length(&v), 44 | ValueType::Node(v) => value_length(v), 45 | ValueType::Nothing => None, 46 | } 47 | .map_or(ValueType::Nothing, |l| ValueType::Value(l.into())) 48 | } 49 | 50 | #[serde_json_path_macros::register(target = COUNT_FUNC)] 51 | fn count(nodes: NodesType) -> ValueType { 52 | nodes.len().into() 53 | } 54 | 55 | #[cfg(feature = "regex")] 56 | #[serde_json_path_macros::register(name = "match", target = MATCH_FUNC)] 57 | fn match_func(value: ValueType, rgx: ValueType) -> LogicalType { 58 | match (value.as_value(), rgx.as_value()) { 59 | (Some(Value::String(s)), Some(Value::String(r))) => { 60 | regex::Regex::new(format!("(?R)^({r})$").as_str()) 61 | .map(|r| r.is_match(s)) 62 | .map(Into::into) 63 | .unwrap_or_default() 64 | } 65 | _ => LogicalType::False, 66 | } 67 | } 68 | 69 | #[cfg(feature = "regex")] 70 | #[serde_json_path_macros::register(target = SEARCH_FUNC)] 71 | fn search(value: ValueType, rgx: ValueType) -> LogicalType { 72 | match (value.as_value(), rgx.as_value()) { 73 | (Some(Value::String(s)), Some(Value::String(r))) => { 74 | regex::Regex::new(format!("(?R)({r})").as_str()) 75 | .map(|r| r.is_match(s)) 76 | .map(Into::into) 77 | .unwrap_or_default() 78 | } 79 | _ => LogicalType::False, 80 | } 81 | } 82 | 83 | #[serde_json_path_macros::register(target = VALUE_FUNC)] 84 | fn value(nodes: NodesType) -> ValueType { 85 | if nodes.len() > 1 { 86 | ValueType::Nothing 87 | } else { 88 | match nodes.first() { 89 | Some(v) => ValueType::Node(v), 90 | None => ValueType::Nothing, 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /serde_json_path/src/parser/selector/mod.rs: -------------------------------------------------------------------------------- 1 | use nom::branch::alt; 2 | use nom::character::complete::char; 3 | use nom::combinator::map; 4 | use nom::error::context; 5 | use serde_json_path_core::spec::selector::index::Index; 6 | use serde_json_path_core::spec::selector::name::Name; 7 | use serde_json_path_core::spec::selector::Selector; 8 | 9 | use self::filter::parse_filter; 10 | use self::slice::parse_array_slice; 11 | 12 | use super::primitive::int::parse_int; 13 | use super::primitive::string::parse_string_literal; 14 | use super::PResult; 15 | 16 | pub(crate) mod filter; 17 | pub(crate) mod function; 18 | pub(crate) mod slice; 19 | 20 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 21 | pub(crate) fn parse_wildcard_selector(input: &str) -> PResult { 22 | map(char('*'), |_| Selector::Wildcard)(input) 23 | } 24 | 25 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 26 | pub(crate) fn parse_name(input: &str) -> PResult { 27 | map(parse_string_literal, Name)(input) 28 | } 29 | 30 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 31 | fn parse_name_selector(input: &str) -> PResult { 32 | map(parse_name, Selector::Name)(input) 33 | } 34 | 35 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 36 | fn parse_index(input: &str) -> PResult { 37 | map(parse_int, Index)(input) 38 | } 39 | 40 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 41 | fn parse_index_selector(input: &str) -> PResult { 42 | map(parse_index, Selector::Index)(input) 43 | } 44 | 45 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 46 | fn parse_array_slice_selector(input: &str) -> PResult { 47 | map(parse_array_slice, Selector::ArraySlice)(input) 48 | } 49 | 50 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 51 | fn parse_filter_selector(input: &str) -> PResult { 52 | map(parse_filter, Selector::Filter)(input) 53 | } 54 | 55 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 56 | pub(crate) fn parse_selector(input: &str) -> PResult { 57 | context( 58 | "selector", 59 | alt(( 60 | parse_wildcard_selector, 61 | parse_name_selector, 62 | parse_array_slice_selector, 63 | parse_index_selector, 64 | parse_filter_selector, 65 | )), 66 | )(input) 67 | } 68 | 69 | #[cfg(test)] 70 | mod tests { 71 | use serde_json_path_core::spec::{ 72 | integer::Integer, 73 | selector::{name::Name, slice::Slice}, 74 | }; 75 | 76 | use super::{parse_selector, parse_wildcard_selector, Index, Selector}; 77 | 78 | #[test] 79 | fn wildcard() { 80 | assert!(matches!( 81 | parse_wildcard_selector("*"), 82 | Ok(("", Selector::Wildcard)) 83 | )); 84 | } 85 | 86 | #[test] 87 | fn all_selectors() { 88 | { 89 | let (_, s) = parse_selector("0").unwrap(); 90 | assert_eq!(s, Selector::Index(Index(Integer::from_i64_unchecked(0)))); 91 | } 92 | { 93 | let (_, s) = parse_selector("10").unwrap(); 94 | assert_eq!(s, Selector::Index(Index(Integer::from_i64_unchecked(10)))); 95 | } 96 | { 97 | let (_, s) = parse_selector("'name'").unwrap(); 98 | assert_eq!(s, Selector::Name(Name(String::from("name")))); 99 | } 100 | { 101 | let (_, s) = parse_selector("\"name\"").unwrap(); 102 | assert_eq!(s, Selector::Name(Name(String::from("name")))); 103 | } 104 | { 105 | let (_, s) = parse_selector("0:3").unwrap(); 106 | assert_eq!( 107 | s, 108 | Selector::ArraySlice(Slice::new().with_start(0).with_end(3)) 109 | ); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /serde_json_path/src/parser/selector/slice.rs: -------------------------------------------------------------------------------- 1 | use nom::{ 2 | branch::alt, 3 | character::complete::{char, multispace0}, 4 | combinator::{map, opt}, 5 | sequence::{preceded, separated_pair, terminated}, 6 | }; 7 | use serde_json_path_core::spec::{integer::Integer, selector::slice::Slice}; 8 | 9 | use crate::parser::{primitive::int::parse_int, PResult}; 10 | 11 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 12 | fn parse_int_space_after(input: &str) -> PResult { 13 | terminated(parse_int, multispace0)(input) 14 | } 15 | 16 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 17 | fn parse_int_space_before(input: &str) -> PResult { 18 | preceded(multispace0, parse_int)(input) 19 | } 20 | 21 | #[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))] 22 | pub(crate) fn parse_array_slice(input: &str) -> PResult { 23 | map( 24 | separated_pair( 25 | opt(parse_int_space_after), 26 | char(':'), 27 | preceded( 28 | multispace0, 29 | alt(( 30 | separated_pair( 31 | opt(parse_int_space_after), 32 | char(':'), 33 | opt(parse_int_space_before), 34 | ), 35 | map(opt(parse_int_space_after), |i| (i, None)), 36 | )), 37 | ), 38 | ), 39 | |(start, (end, step))| Slice { start, end, step }, 40 | )(input) 41 | } 42 | 43 | #[cfg(test)] 44 | mod tests { 45 | use crate::parser::selector::slice::Slice; 46 | 47 | use super::parse_array_slice; 48 | 49 | #[test] 50 | fn valid_forward() { 51 | assert_eq!( 52 | parse_array_slice("1:5:1"), 53 | Ok(("", Slice::new().with_start(1).with_end(5).with_step(1))) 54 | ); 55 | assert_eq!( 56 | parse_array_slice("1:10:3"), 57 | Ok(("", Slice::new().with_start(1).with_end(10).with_step(3))) 58 | ); 59 | assert_eq!( 60 | parse_array_slice("1:5:-1"), 61 | Ok(("", Slice::new().with_start(1).with_end(5).with_step(-1))) 62 | ); 63 | assert_eq!( 64 | parse_array_slice(":5:1"), 65 | Ok(("", Slice::new().with_end(5).with_step(1))) 66 | ); 67 | assert_eq!( 68 | parse_array_slice("1::1"), 69 | Ok(("", Slice::new().with_start(1).with_step(1))) 70 | ); 71 | assert_eq!( 72 | parse_array_slice("1:5"), 73 | Ok(("", Slice::new().with_start(1).with_end(5))) 74 | ); 75 | assert_eq!(parse_array_slice("::"), Ok(("", Slice::new()))); 76 | } 77 | 78 | #[test] 79 | fn optional_whitespace() { 80 | assert_eq!( 81 | parse_array_slice("1 :5:1"), 82 | Ok(("", Slice::new().with_start(1).with_end(5).with_step(1))) 83 | ); 84 | assert_eq!( 85 | parse_array_slice("1: 5 :1"), 86 | Ok(("", Slice::new().with_start(1).with_end(5).with_step(1))) 87 | ); 88 | assert_eq!( 89 | parse_array_slice("1: :1"), 90 | Ok(("", Slice::new().with_start(1).with_step(1))) 91 | ); 92 | assert_eq!( 93 | parse_array_slice("1:5\n:1"), 94 | Ok(("", Slice::new().with_start(1).with_end(5).with_step(1))) 95 | ); 96 | assert_eq!( 97 | parse_array_slice("1 : 5 :1"), 98 | Ok(("", Slice::new().with_start(1).with_end(5).with_step(1))) 99 | ); 100 | assert_eq!( 101 | parse_array_slice("1:5: 1"), 102 | Ok(("", Slice::new().with_start(1).with_end(5).with_step(1))) 103 | ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /serde_json_path/src/parser/utils.rs: -------------------------------------------------------------------------------- 1 | use nom::{error::ParseError, IResult, Parser}; 2 | 3 | use super::FromInternalError; 4 | 5 | /// Prevent upstream poisoning by [`nom::combinator::cut`] 6 | /// 7 | /// It is common when using [`nom::branch::alt`] to terminate a branch, and the entire `alt` tree, 8 | /// using the `cut` combinator. This has the unfortunate effect of "poisoning" upstream parsers by 9 | /// propagating a nom `Failure` up the parser chain. Any upstream usage of, e.g., `alt`, would then 10 | /// also terminate, and not attempt other branches. 11 | /// 12 | /// Using `uncut`, you can wrap `alt` to prevent this poisoning. 13 | /// 14 | /// # Example 15 | /// 16 | /// ```ignore 17 | /// fn unpoisoned_alt(input: &str) -> IResult<&str, &str> { 18 | /// uncut( 19 | /// alt(( 20 | /// pair(tag("foo"), cut(tag("bar"))), 21 | /// tag("baz"), 22 | /// )) 23 | /// )(input) 24 | /// } 25 | /// ``` 26 | pub(crate) fn uncut, F: Parser>( 27 | mut parser: F, 28 | ) -> impl FnMut(I) -> IResult { 29 | move |input: I| match parser.parse(input) { 30 | Err(nom::Err::Failure(e)) => Err(nom::Err::Error(e)), 31 | rest => rest, 32 | } 33 | } 34 | 35 | #[allow(dead_code)] 36 | /// Map a parser error into another error 37 | pub(crate) fn map_err( 38 | mut parser: F, 39 | mut f: G, 40 | ) -> impl FnMut(I) -> IResult 41 | where 42 | F: nom::Parser, 43 | G: FnMut(E1) -> E2, 44 | E1: FromInternalError, 45 | { 46 | move |input: I| { 47 | let i = input.clone(); 48 | match parser.parse(i) { 49 | Ok((remainder, value)) => Ok((remainder, value)), 50 | Err(nom::Err::Error(e)) => Err(nom::Err::Error(E1::from_internal_error(input, f(e)))), 51 | Err(nom::Err::Failure(e)) => { 52 | Err(nom::Err::Failure(E1::from_internal_error(input, f(e)))) 53 | } 54 | Err(nom::Err::Incomplete(_)) => unreachable!(), 55 | } 56 | } 57 | } 58 | 59 | /// Use the `cut` parser with a custom error wrapper 60 | pub(crate) fn cut_with( 61 | mut parser: F, 62 | mut f: G, 63 | ) -> impl FnMut(I) -> IResult 64 | where 65 | I: Clone, 66 | F: nom::Parser, 67 | G: FnMut(E1) -> E2, 68 | E1: FromInternalError, 69 | { 70 | move |input: I| { 71 | let i = input.clone(); 72 | match parser.parse(i) { 73 | Ok((remainder, value)) => Ok((remainder, value)), 74 | Err(nom::Err::Error(e) | nom::Err::Failure(e)) => { 75 | Err(nom::Err::Failure(E1::from_internal_error(input, f(e)))) 76 | } 77 | Err(nom::Err::Incomplete(_)) => unreachable!(), 78 | } 79 | } 80 | } 81 | 82 | #[allow(dead_code)] 83 | /// Fail with an internal error 84 | pub(crate) fn fail_with(mut f: G) -> impl FnMut(I) -> IResult 85 | where 86 | E1: FromInternalError, 87 | G: FnMut() -> E2, 88 | { 89 | move |input: I| Err(nom::Err::Failure(E1::from_internal_error(input, f()))) 90 | } 91 | -------------------------------------------------------------------------------- /serde_json_path/src/path.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use serde::{de::Visitor, Deserialize, Serialize}; 4 | use serde_json::Value; 5 | use serde_json_path_core::{ 6 | node::{LocatedNodeList, NodeList}, 7 | spec::query::{Query, Queryable}, 8 | }; 9 | 10 | use crate::{parser::parse_query_main, ParseError}; 11 | 12 | /// A parsed JSON Path query string 13 | /// 14 | /// This type represents a valid, parsed JSON Path query string. Please refer to the 15 | /// JSONPath standard ([RFC 9535][rfc]) for the details on what constitutes a valid JSON Path 16 | /// query. 17 | /// 18 | /// # Usage 19 | /// 20 | /// A `JsonPath` can be parsed directly from an `&str` using the [`parse`][JsonPath::parse] method: 21 | /// ```rust 22 | /// # use serde_json_path::JsonPath; 23 | /// # fn main() { 24 | /// let path = JsonPath::parse("$.foo.*").expect("valid JSON Path"); 25 | /// # } 26 | /// ``` 27 | /// It can then be used to query [`serde_json::Value`]'s with the [`query`][JsonPath::query] method: 28 | /// ```rust 29 | /// # use serde_json::json; 30 | /// # use serde_json_path::JsonPath; 31 | /// # fn main() { 32 | /// # let path = JsonPath::parse("$.foo.*").expect("valid JSON Path"); 33 | /// let value = json!({"foo": [1, 2, 3, 4]}); 34 | /// let nodes = path.query(&value); 35 | /// assert_eq!(nodes.all(), vec![1, 2, 3, 4]); 36 | /// # } 37 | /// ``` 38 | /// 39 | /// [rfc]: https://www.rfc-editor.org/rfc/rfc9535.html 40 | #[derive(Debug, PartialEq, Eq, Clone, Default)] 41 | pub struct JsonPath(Query); 42 | 43 | impl JsonPath { 44 | /// Create a [`JsonPath`] by parsing a valid JSON Path query string 45 | /// 46 | /// # Example 47 | /// ```rust 48 | /// # use serde_json_path::JsonPath; 49 | /// # fn main() { 50 | /// let path = JsonPath::parse("$.foo[1:10:2].baz").expect("valid JSON Path"); 51 | /// # } 52 | /// ``` 53 | pub fn parse(path_str: &str) -> Result { 54 | let (_, path) = parse_query_main(path_str).map_err(|err| match err { 55 | nom::Err::Error(e) | nom::Err::Failure(e) => (path_str, e), 56 | nom::Err::Incomplete(_) => unreachable!("we do not use streaming parsers"), 57 | })?; 58 | Ok(Self(path)) 59 | } 60 | 61 | /// Query a [`serde_json::Value`] using this [`JsonPath`] 62 | /// 63 | /// # Example 64 | /// ```rust 65 | /// # use serde_json::json; 66 | /// # use serde_json_path::JsonPath; 67 | /// # fn main() -> Result<(), serde_json_path::ParseError> { 68 | /// let value = json!({"foo": [1, 2, 3, 4]}); 69 | /// let path = JsonPath::parse("$.foo[::2]")?; 70 | /// let nodes = path.query(&value); 71 | /// assert_eq!(nodes.all(), vec![1, 3]); 72 | /// # Ok(()) 73 | /// # } 74 | /// ``` 75 | pub fn query<'b>(&self, value: &'b Value) -> NodeList<'b> { 76 | self.0.query(value, value).into() 77 | } 78 | 79 | /// Query a [`serde_json::Value`] using this [`JsonPath`] to produce a [`LocatedNodeList`] 80 | /// 81 | /// # Example 82 | /// ```rust 83 | /// # use serde_json::{json, Value}; 84 | /// # use serde_json_path::{JsonPath,NormalizedPath}; 85 | /// # fn main() -> Result<(), serde_json_path::ParseError> { 86 | /// let value = json!({"foo": {"bar": 1, "baz": 2}}); 87 | /// let path = JsonPath::parse("$.foo.*")?; 88 | /// let query = path.query_located(&value); 89 | /// let nodes: Vec<&Value> = query.nodes().collect(); 90 | /// assert_eq!(nodes, vec![1, 2]); 91 | /// let locs: Vec = query 92 | /// .locations() 93 | /// .map(|loc| loc.to_string()) 94 | /// .collect(); 95 | /// assert_eq!(locs, ["$['foo']['bar']", "$['foo']['baz']"]); 96 | /// # Ok(()) 97 | /// # } 98 | /// ``` 99 | pub fn query_located<'b>(&self, value: &'b Value) -> LocatedNodeList<'b> { 100 | self.0 101 | .query_located(value, value, Default::default()) 102 | .into() 103 | } 104 | } 105 | 106 | impl FromStr for JsonPath { 107 | type Err = ParseError; 108 | 109 | fn from_str(s: &str) -> Result { 110 | JsonPath::parse(s) 111 | } 112 | } 113 | 114 | impl std::fmt::Display for JsonPath { 115 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 116 | write!(f, "{path}", path = self.0) 117 | } 118 | } 119 | 120 | impl Serialize for JsonPath { 121 | fn serialize(&self, serializer: S) -> Result 122 | where 123 | S: serde::Serializer, 124 | { 125 | serializer.collect_str(self) 126 | } 127 | } 128 | 129 | impl<'de> Deserialize<'de> for JsonPath { 130 | fn deserialize(deserializer: D) -> Result 131 | where 132 | D: serde::Deserializer<'de>, 133 | { 134 | struct JsonPathVisitor; 135 | 136 | impl Visitor<'_> for JsonPathVisitor { 137 | type Value = JsonPath; 138 | 139 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 140 | write!(formatter, "a string representing a JSON Path query") 141 | } 142 | 143 | fn visit_str(self, v: &str) -> Result 144 | where 145 | E: serde::de::Error, 146 | { 147 | JsonPath::parse(v).map_err(serde::de::Error::custom) 148 | } 149 | } 150 | 151 | deserializer.deserialize_str(JsonPathVisitor) 152 | } 153 | } 154 | 155 | #[cfg(test)] 156 | mod tests { 157 | use serde_json::{from_value, json, to_value}; 158 | 159 | use crate::JsonPath; 160 | 161 | #[test] 162 | fn test_send() { 163 | fn assert_send() {} 164 | assert_send::(); 165 | } 166 | 167 | #[test] 168 | fn test_sync() { 169 | fn assert_sync() {} 170 | assert_sync::(); 171 | } 172 | 173 | #[test] 174 | fn serde_round_trip() { 175 | let j1 = json!("$.foo['bar'][1:10][?@.baz > 10 && @.foo.bar < 20]"); 176 | let p1 = from_value::(j1).expect("deserializes"); 177 | let p2 = to_value(&p1) 178 | .and_then(from_value::) 179 | .expect("round trip"); 180 | assert_eq!(p1, p2); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /serde_json_path/tests/compliance.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | 3 | use serde::Deserialize; 4 | use serde_json::Value; 5 | use serde_json_path::JsonPath; 6 | #[cfg(feature = "trace")] 7 | use test_log::test; 8 | 9 | #[derive(Deserialize)] 10 | struct TestSuite { 11 | tests: Vec, 12 | } 13 | 14 | #[derive(Deserialize)] 15 | struct TestCase { 16 | name: String, 17 | selector: String, 18 | #[serde(default)] 19 | document: Value, 20 | #[serde(flatten)] 21 | result: TestResult, 22 | } 23 | 24 | #[derive(Deserialize)] 25 | #[serde(untagged)] 26 | enum TestResult { 27 | Deterministic { result: Vec }, 28 | NonDeterministic { results: Vec> }, 29 | InvalidSelector { invalid_selector: bool }, 30 | } 31 | 32 | impl TestResult { 33 | fn verify(&self, name: &str, actual: Vec<&Value>) { 34 | match self { 35 | TestResult::Deterministic { result } => assert_eq!( 36 | result.iter().collect::>(), 37 | actual, 38 | "{name}: incorrect result, expected {result:?}, got {actual:?}" 39 | ), 40 | TestResult::NonDeterministic { results } => { 41 | assert!(results 42 | .iter() 43 | .any(|r| r.iter().collect::>().eq(&actual))) 44 | } 45 | TestResult::InvalidSelector { .. } => unreachable!(), 46 | } 47 | } 48 | 49 | fn is_invalid_selector(&self) -> bool { 50 | matches!(self, Self::InvalidSelector { invalid_selector } if *invalid_selector) 51 | } 52 | } 53 | 54 | #[test] 55 | fn compliance_test_suite() { 56 | let cts_json_str = fs::read_to_string("../jsonpath-compliance-test-suite/cts.json") 57 | .expect("read cts.json file"); 58 | 59 | let test_cases: TestSuite = 60 | serde_json::from_str(cts_json_str.as_str()).expect("parse cts_json_str"); 61 | 62 | for ( 63 | i, 64 | TestCase { 65 | name, 66 | selector, 67 | document, 68 | result, 69 | }, 70 | ) in test_cases.tests.iter().enumerate() 71 | { 72 | println!("Test ({i}): {name}"); 73 | let path = JsonPath::parse(selector); 74 | if result.is_invalid_selector() { 75 | assert!( 76 | path.is_err(), 77 | "{name}: parsing {selector:?} should have failed", 78 | ); 79 | } else { 80 | let path = path.expect("valid JSON Path string"); 81 | { 82 | // Query using JsonPath::query 83 | let actual = path.query(document).all(); 84 | result.verify(name, actual); 85 | } 86 | { 87 | // Query using JsonPath::query_located 88 | let q = path.query_located(document); 89 | let actual = q.nodes().collect::>(); 90 | result.verify(name, actual); 91 | } 92 | } 93 | } 94 | } 95 | 96 | const TEST_CASE_N: usize = 10; 97 | 98 | #[test] 99 | #[ignore = "this is only for testing individual CTS test cases as needed"] 100 | fn compliance_single() { 101 | let cts_json_str = fs::read_to_string("../jsonpath-compliance-test-suite/cts.json") 102 | .expect("read cts.json file"); 103 | 104 | let test_cases: TestSuite = 105 | serde_json::from_str(cts_json_str.as_str()).expect("parse cts_json_str"); 106 | 107 | let TestCase { 108 | name, 109 | selector, 110 | document, 111 | result, 112 | } = &test_cases.tests[TEST_CASE_N]; 113 | println!("Test Case: {name}"); 114 | let path = JsonPath::parse(selector); 115 | if result.is_invalid_selector() { 116 | println!("...this test should fail"); 117 | assert!( 118 | path.is_err(), 119 | "{name}: parsing {selector:?} should have failed", 120 | ); 121 | } else { 122 | let path = path.expect("valid JSON Path string"); 123 | { 124 | // Query using JsonPath::query 125 | let actual = path.query(document).all(); 126 | result.verify(name, actual); 127 | } 128 | { 129 | // Query using JsonPath::query_located 130 | let q = path.query_located(document); 131 | let actual = q.nodes().collect::>(); 132 | result.verify(name, actual); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /serde_json_path/tests/functions.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | 3 | use serde_json::{json, Value}; 4 | use serde_json_path::{JsonPath, JsonPathExt}; 5 | use serde_json_path_core::spec::functions::{NodesType, ValueType}; 6 | #[cfg(feature = "trace")] 7 | use test_log::test; 8 | 9 | #[test] 10 | fn test_length() { 11 | let value = json!([ 12 | "a short string", 13 | "a slightly longer string", 14 | "a string that is even longer!", 15 | ]); 16 | let query = JsonPath::parse("$[?length(@) > 14]").unwrap(); 17 | let nodes = query.query(&value); 18 | println!("{nodes:#?}"); 19 | assert_eq!(nodes.len(), 2); 20 | } 21 | 22 | #[test] 23 | fn test_length_invalid_args() { 24 | let error = JsonPath::parse("$[? length(@.foo.*)]").unwrap_err(); 25 | println!("{error:#?}"); 26 | } 27 | 28 | #[test] 29 | fn test_length_incorrect_use() { 30 | let error = JsonPath::parse("$[?length(@.author)]").unwrap_err(); 31 | assert!( 32 | error 33 | .to_string() 34 | .contains("in long-hand segment, function with incorrect return type used"), 35 | "error did not contain the expected message" 36 | ); 37 | } 38 | 39 | #[test] 40 | fn test_count() { 41 | let value = json!([ 42 | {"foo": [1]}, 43 | {"foo": [1, 2]}, 44 | ]); 45 | let path = JsonPath::parse("$[?count(@.foo.*) > 1]").unwrap(); 46 | let q = path.query(&value); 47 | assert_eq!(1, q.len()); 48 | } 49 | 50 | #[test] 51 | fn test_count_singular_query() { 52 | let value = json!([ 53 | {"foo": "bar"}, 54 | {"foo": "baz"}, 55 | ]); 56 | let path = JsonPath::parse("$[? count(@.foo) == 1]").unwrap(); 57 | let q = path.query(&value); 58 | println!("{q:#?}"); 59 | assert_eq!(q.len(), 2); 60 | } 61 | 62 | fn simpsons_characters() -> Value { 63 | json!([ 64 | { "name": "Homer Simpson" }, 65 | { "name": "Marge Simpson" }, 66 | { "name": "Bart Simpson" }, 67 | { "name": "Lisa Simpson" }, 68 | { "name": "Maggie Simpson" }, 69 | { "name": "Ned Flanders" }, 70 | { "name": "Rod Flanders" }, 71 | { "name": "Todd Flanders" }, 72 | ]) 73 | } 74 | 75 | #[test] 76 | fn test_match() { 77 | let value = simpsons_characters(); 78 | let path = JsonPath::parse("$[? match(@.name, 'M[A-Za-z ]*Simpson')].name").unwrap(); 79 | let nodes = path.query(&value); 80 | println!("{nodes:#?}"); 81 | assert_eq!(2, nodes.len()); 82 | assert_eq!("Marge Simpson", nodes.first().unwrap().as_str().unwrap(),); 83 | } 84 | 85 | #[test] 86 | fn test_search() { 87 | let value = simpsons_characters(); 88 | let path = JsonPath::parse("$[? search(@.name, 'Flanders')]").unwrap(); 89 | let nodes = path.query(&value); 90 | println!("{nodes:#?}"); 91 | assert_eq!(3, nodes.len()); 92 | } 93 | 94 | fn get_some_books() -> Value { 95 | json!([ 96 | { 97 | "books": [ 98 | { 99 | "author": "Alexandre Dumas", 100 | "title": "The Three Musketeers", 101 | "price": 14.99 102 | }, 103 | { 104 | "author": "Leo Tolstoy", 105 | "title": "War and Peace", 106 | "price": 19.99 107 | } 108 | ] 109 | }, 110 | { 111 | "books": [ 112 | { 113 | "author": "Charles Dickens", 114 | "title": "Great Expectations", 115 | "price": 13.99 116 | }, 117 | { 118 | "author": "Fyodor Dostoevsky", 119 | "title": "The Brothers Karamazov", 120 | "price": 12.99 121 | } 122 | ] 123 | } 124 | ]) 125 | } 126 | 127 | #[serde_json_path::function] 128 | fn first(nodes: NodesType) -> ValueType { 129 | match nodes.first() { 130 | Some(v) => ValueType::Node(v), 131 | None => ValueType::Nothing, 132 | } 133 | } 134 | 135 | #[serde_json_path::function] 136 | fn sort<'a>(nodes: NodesType<'a>, on: ValueType<'a>) -> NodesType<'a> { 137 | if let Some(Ok(path)) = on.as_value().and_then(Value::as_str).map(JsonPath::parse) { 138 | let mut nl = nodes.all(); 139 | nl.sort_by(|a, b| { 140 | if let (Some(a), Some(b)) = ( 141 | a.json_path(&path).exactly_one().ok(), 142 | b.json_path(&path).exactly_one().ok(), 143 | ) { 144 | match (a, b) { 145 | (Value::Bool(b1), Value::Bool(b2)) => b1.cmp(b2), 146 | (Value::Number(n1), Value::Number(n2)) => { 147 | if let (Some(f1), Some(f2)) = (n1.as_f64(), n2.as_f64()) { 148 | f1.partial_cmp(&f2).unwrap_or(Ordering::Equal) 149 | } else if let (Some(u1), Some(u2)) = (n1.as_u64(), n2.as_u64()) { 150 | u1.cmp(&u2) 151 | } else if let (Some(i1), Some(i2)) = (n1.as_i64(), n2.as_i64()) { 152 | i1.cmp(&i2) 153 | } else { 154 | Ordering::Equal 155 | } 156 | } 157 | (Value::String(s1), Value::String(s2)) => s1.cmp(s2), 158 | _ => Ordering::Equal, 159 | } 160 | } else { 161 | Ordering::Equal 162 | } 163 | }); 164 | nl.into() 165 | } else { 166 | nodes 167 | } 168 | } 169 | 170 | #[serde_json_path::function] 171 | fn get<'a>(node: ValueType<'a>, path: ValueType<'a>) -> ValueType<'a> { 172 | if let Some(Ok(path)) = path.as_value().and_then(Value::as_str).map(JsonPath::parse) { 173 | match node { 174 | ValueType::Node(v) => v 175 | .json_path(&path) 176 | .exactly_one() 177 | .map(ValueType::Node) 178 | .unwrap_or_default(), 179 | // This is awkward, because we can't return a reference to a value owned by `node` 180 | // This means that `get` can only be used when dealing with nodes within the JSON object 181 | // being queried. 182 | _ => ValueType::Nothing, 183 | } 184 | } else { 185 | ValueType::Nothing 186 | } 187 | } 188 | 189 | #[test] 190 | fn custom_first_function() { 191 | let value = get_some_books(); 192 | let path = JsonPath::parse("$[? first(@.books.*.author) == 'Alexandre Dumas']").unwrap(); 193 | let node = path.query(&value).exactly_one().unwrap(); 194 | println!("{node:#?}"); 195 | assert_eq!( 196 | "War and Peace", 197 | node.pointer("/books/1/title").unwrap().as_str().unwrap(), 198 | ); 199 | } 200 | 201 | #[test] 202 | fn function_as_argument() { 203 | let value = get_some_books(); 204 | let path = JsonPath::parse("$[? length(first(@.books.*.title)) == 18 ]").unwrap(); 205 | let node = path.query(&value).exactly_one().unwrap(); 206 | println!("{node:#?}"); 207 | assert_eq!( 208 | "The Brothers Karamazov", 209 | node.pointer("/books/1/title").unwrap().as_str().unwrap(), 210 | ) 211 | } 212 | 213 | #[test] 214 | fn combine_custom_functions() { 215 | let value = get_some_books(); 216 | let path = JsonPath::parse( 217 | // `sort` the books in each node by price, take the `first` one, and check for specific 218 | // title by using `get`: 219 | "$[? get(first(sort(@.books.*, '$.price')), '$.title') == 'The Brothers Karamazov']", 220 | ) 221 | .unwrap(); 222 | let node = path.query(&value).exactly_one().unwrap(); 223 | println!("{node:#?}"); 224 | assert_eq!( 225 | "The Brothers Karamazov", 226 | node.pointer("/books/1/title").unwrap().as_str().unwrap(), 227 | ); 228 | } 229 | -------------------------------------------------------------------------------- /serde_json_path/tests/regressions.rs: -------------------------------------------------------------------------------- 1 | use serde_json::json; 2 | use serde_json_path::JsonPath; 3 | #[cfg(feature = "trace")] 4 | use test_log::test; 5 | 6 | // This test is meant for issue #49, which can be found here: 7 | // https://github.com/hiltontj/serde_json_path/issues/49 8 | #[test] 9 | fn issue_49() { 10 | let value = json!({"a": 1, "b": 2}); 11 | let path = JsonPath::parse("$[?(@.a == 2)]").expect("parses JSONPath"); 12 | assert!(path.query(&value).is_empty()); 13 | } 14 | 15 | // This test is meant for issue #60, which can be found here: 16 | // https://github.com/hiltontj/serde_json_path/issues/60 17 | #[test] 18 | fn issue_60() { 19 | let value = json!([{"foo": "bar"}, {"foo": "biz"}]); 20 | let path = JsonPath::parse("$[? match(@.foo, '|')]").expect("parses JSONPath"); 21 | assert!(path.query(&value).is_empty()); 22 | } 23 | -------------------------------------------------------------------------------- /serde_json_path/tests/serde.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use serde_json::{from_value, json}; 3 | use serde_json_path::JsonPath; 4 | 5 | #[derive(Deserialize)] 6 | struct Config { 7 | pub path: JsonPath, 8 | } 9 | 10 | #[test] 11 | fn can_deserialize_json_path() { 12 | let config_json = json!({ "path": "$.foo.*" }); 13 | let config = from_value::(config_json).expect("deserializes"); 14 | let value = json!({"foo": [1, 2, 3]}); 15 | let nodes = config.path.query(&value).all(); 16 | assert_eq!(nodes, vec![1, 2, 3]); 17 | } 18 | -------------------------------------------------------------------------------- /serde_json_path/tests/spec_examples.rs: -------------------------------------------------------------------------------- 1 | use serde_json::{json, Value}; 2 | use serde_json_path::JsonPath; 3 | #[cfg(feature = "trace")] 4 | use test_log::test; 5 | 6 | fn spec_example_json() -> Value { 7 | json!({ 8 | "store": { 9 | "book": [ 10 | { 11 | "category": "reference", 12 | "author": "Nigel Rees", 13 | "title": "Sayings of the Century", 14 | "price": 8.95 15 | }, 16 | { 17 | "category": "fiction", 18 | "author": "Evelyn Waugh", 19 | "title": "Sword of Honour", 20 | "price": 12.99 21 | }, 22 | { 23 | "category": "fiction", 24 | "author": "Herman Melville", 25 | "title": "Moby Dick", 26 | "isbn": "0-553-21311-3", 27 | "price": 8.99 28 | }, 29 | { 30 | "category": "fiction", 31 | "author": "J. R. R. Tolkien", 32 | "title": "The Lord of the Rings", 33 | "isbn": "0-395-19395-8", 34 | "price": 22.99 35 | } 36 | ], 37 | "bicycle": { 38 | "color": "red", 39 | "price": 399 40 | } 41 | } 42 | }) 43 | } 44 | 45 | #[test] 46 | fn spec_example_1() { 47 | let value = spec_example_json(); 48 | let path = JsonPath::parse("$.store.book[*].author").unwrap(); 49 | let nodes = path.query(&value).all(); 50 | assert_eq!( 51 | nodes, 52 | vec![ 53 | "Nigel Rees", 54 | "Evelyn Waugh", 55 | "Herman Melville", 56 | "J. R. R. Tolkien" 57 | ] 58 | ); 59 | } 60 | 61 | #[test] 62 | fn spec_example_2() { 63 | let value = spec_example_json(); 64 | let path = JsonPath::parse("$..author").unwrap(); 65 | let nodes = path.query(&value).all(); 66 | assert_eq!( 67 | nodes, 68 | vec![ 69 | "Nigel Rees", 70 | "Evelyn Waugh", 71 | "Herman Melville", 72 | "J. R. R. Tolkien" 73 | ] 74 | ); 75 | } 76 | 77 | #[test] 78 | fn spec_example_3() { 79 | let value = spec_example_json(); 80 | let path = JsonPath::parse("$.store.*").unwrap(); 81 | let nodes = path.query(&value).all(); 82 | assert_eq!(nodes.len(), 2); 83 | assert!(nodes 84 | .iter() 85 | .any(|&node| node == value.pointer("/store/book").unwrap())); 86 | } 87 | 88 | #[test] 89 | fn spec_example_4() { 90 | let value = spec_example_json(); 91 | let path = JsonPath::parse("$.store..price").unwrap(); 92 | let nodes = path.query(&value).all(); 93 | assert_eq!(nodes, vec![399., 8.95, 12.99, 8.99, 22.99]); 94 | } 95 | 96 | #[test] 97 | fn spec_example_5() { 98 | let value = spec_example_json(); 99 | let path = JsonPath::parse("$..book[2]").unwrap(); 100 | let node = path.query(&value).at_most_one().unwrap(); 101 | assert!(node.is_some()); 102 | assert_eq!(node, value.pointer("/store/book/2")); 103 | } 104 | 105 | #[test] 106 | fn spec_example_6() { 107 | let value = spec_example_json(); 108 | let path = JsonPath::parse("$..book[-1]").unwrap(); 109 | let node = path.query(&value).at_most_one().unwrap(); 110 | assert!(node.is_some()); 111 | assert_eq!(node, value.pointer("/store/book/3")); 112 | } 113 | 114 | #[test] 115 | fn spec_example_7() { 116 | let value = spec_example_json(); 117 | { 118 | let path = JsonPath::parse("$..book[0,1]").unwrap(); 119 | let nodes = path.query(&value).all(); 120 | assert_eq!(nodes.len(), 2); 121 | } 122 | { 123 | let path = JsonPath::parse("$..book[:2]").unwrap(); 124 | let nodes = path.query(&value).all(); 125 | assert_eq!(nodes.len(), 2); 126 | } 127 | } 128 | 129 | #[test] 130 | fn spec_example_8() { 131 | let value = spec_example_json(); 132 | let path = JsonPath::parse("$..book[?(@.isbn)]").unwrap(); 133 | let nodes = path.query(&value); 134 | assert_eq!(nodes.len(), 2); 135 | } 136 | 137 | #[test] 138 | fn spec_example_9() { 139 | let value = spec_example_json(); 140 | let path = JsonPath::parse("$..book[?(@.price<10)]").unwrap(); 141 | let nodes = path.query(&value); 142 | assert_eq!(nodes.len(), 2); 143 | } 144 | 145 | #[test] 146 | fn spec_example_10() { 147 | let value = spec_example_json(); 148 | let path = JsonPath::parse("$..*").unwrap(); 149 | let nodes = path.query(&value); 150 | assert_eq!(nodes.len(), 27); 151 | } 152 | -------------------------------------------------------------------------------- /serde_json_path_core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | # Unreleased 9 | 10 | # 0.2.1 (3 November 2024) 11 | 12 | - **internal**: update `serde_json` to the latest version ([#107]) 13 | - **breaking**: ensure integers used as indices are within the [valid range for I-JSON][i-json-range] ([#98]) 14 | - **internal**: remove use of `once_cell` and use specific versions for crate dependencies ([#105]) 15 | 16 | [#98]: https://github.com/hiltontj/serde_json_path/pull/98 17 | [i-json-range]: https://www.rfc-editor.org/rfc/rfc9535.html#section-2.1-4.1 18 | [#105]: https://github.com/hiltontj/serde_json_path/pull/105 19 | [#107]: https://github.com/hiltontj/serde_json_path/pull/107 20 | 21 | # 0.1.6 (3 March 2024) 22 | 23 | - **testing**: support tests for non-determinism in compliance test suite ([#85]) 24 | - **fixed**: bug preventing registered functions from being used as arguments to other functions ([#84]) 25 | 26 | [#85]: https://github.com/hiltontj/serde_json_path/pull/85 27 | [#84]: https://github.com/hiltontj/serde_json_path/pull/84 28 | 29 | # 0.1.5 (23 February 2024) 30 | 31 | - **docs**: update links to refer to RFC 9535 ([#81]) 32 | 33 | [#81]: https://github.com/hiltontj/serde_json_path/pull/81 34 | 35 | # 0.1.4 (2 February 2024) 36 | 37 | ## Added: `NormalizedPath` and `PathElement` types ([#78]) 38 | 39 | The `NormalizedPath` struct represents the location of a node within a JSON object. Its representation is like so: 40 | 41 | ```rust 42 | pub struct NormalizedPath<'a>(Vec); 43 | 44 | pub enum PathElement<'a> { 45 | Name(&'a str), 46 | Index(usize), 47 | } 48 | ``` 49 | 50 | 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. 51 | 52 | [pointer]: https://docs.rs/serde_json/latest/serde_json/enum.Value.html#method.pointer 53 | [pointer-mut]: https://docs.rs/serde_json/latest/serde_json/enum.Value.html#method.pointer_mut 54 | 55 | 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. 56 | 57 | ## Added: `LocatedNodeList` and `LocatedNode` types ([#78]) 58 | 59 | 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: 60 | 61 | ```rust 62 | use serde_json::{json, Value}; 63 | use serde_json_path::JsonPath; 64 | let value = json!({"foo": {"bar": 1, "baz": 2}}); 65 | let path = JsonPath::parse("$.foo.*")?; 66 | let query = path.query_located(&value); 67 | let nodes: Vec<&Value> = query.nodes().collect(); 68 | assert_eq!(nodes, vec![1, 2]); 69 | let locs: Vec = query 70 | .locations() 71 | .map(|loc| loc.to_string()) 72 | .collect(); 73 | assert_eq!(locs, ["$['foo']['bar']", "$['foo']['baz']"]); 74 | ``` 75 | 76 | The location/node pairs are represented by the `LocatedNode` type. 77 | 78 | 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. 79 | 80 | [#78]: https://github.com/hiltontj/serde_json_path/pull/78 81 | 82 | ## Other Changes 83 | 84 | - **internal**: address new clippy lints in Rust 1.75 ([#75]) 85 | - **internal**: address new clippy lints in Rust 1.74 and update some tracing instrumentation ([#70]) 86 | - **internal**: code clean-up ([#72]) 87 | 88 | [#70]: https://github.com/hiltontj/serde_json_path/pull/70 89 | [#72]: https://github.com/hiltontj/serde_json_path/pull/72 90 | [#75]: https://github.com/hiltontj/serde_json_path/pull/75 91 | 92 | # 0.1.3 (9 November 2023) 93 | 94 | - **added**: `is_empty`, `is_more_than_one`, and `as_more_than_one` methods to `ExactlyOneError` ([#65]) 95 | - **fixed**: ensure that the check `== -0` in filters works as expected ([#67]) 96 | 97 | [#65]: https://github.com/hiltontj/serde_json_path/pull/65 98 | [#67]: https://github.com/hiltontj/serde_json_path/pull/67 99 | 100 | # 0.1.2 (17 September 2023) 101 | 102 | - **documentation**: Improvements to documentation ([#56]) 103 | 104 | [#56]: https://github.com/hiltontj/serde_json_path/pull/56 105 | 106 | # 0.1.1 (13 July 2023) 107 | 108 | * **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]) 109 | 110 | [#50]: https://github.com/hiltontj/serde_json_path/pull/50 111 | 112 | # 0.1.0 (2 April 2023) 113 | 114 | Initial Release 115 | 116 | -------------------------------------------------------------------------------- /serde_json_path_core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serde_json_path_core" 3 | version = "0.2.2" 4 | edition = "2021" 5 | license = "MIT" 6 | authors = ["Trevor Hilton "] 7 | description = "Core types for the serde_json_path crate" 8 | repository = "https://github.com/hiltontj/serde_json_path" 9 | readme = "README.md" 10 | keywords = ["json", "jsonpath", "json_path", "serde", "serde_json"] 11 | 12 | [lib] 13 | 14 | [features] 15 | default = ["functions"] 16 | trace = ["dep:tracing"] 17 | functions = [] 18 | 19 | [dependencies] 20 | # crates.io crates: 21 | inventory.workspace = true 22 | serde.workspace = true 23 | serde_json.workspace = true 24 | thiserror.workspace = true 25 | 26 | [dependencies.tracing] 27 | workspace = true 28 | optional = true 29 | 30 | 31 | [dev-dependencies] 32 | serde_json_path = { path = "../serde_json_path" } 33 | 34 | -------------------------------------------------------------------------------- /serde_json_path_core/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Trevor Hilton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /serde_json_path_core/README.md: -------------------------------------------------------------------------------- 1 | # serde_json_path_core 2 | 3 | Core types for the [`serde_json_path`][sjp] crate. 4 | 5 | [sjp]: https://crates.io/crates/serde_json_path 6 | -------------------------------------------------------------------------------- /serde_json_path_core/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Core types for [`serde_json_path`] 2 | //! 3 | //! [`serde_json_path`]: https://crates.io/crates/serde_json_path 4 | #![warn( 5 | clippy::all, 6 | clippy::dbg_macro, 7 | clippy::todo, 8 | clippy::empty_enum, 9 | clippy::enum_glob_use, 10 | clippy::mem_forget, 11 | clippy::unused_self, 12 | clippy::filter_map_next, 13 | clippy::needless_continue, 14 | clippy::needless_borrow, 15 | clippy::match_wildcard_for_single_variants, 16 | clippy::if_let_mutex, 17 | unexpected_cfgs, 18 | clippy::await_holding_lock, 19 | clippy::match_on_vec_items, 20 | clippy::imprecise_flops, 21 | clippy::suboptimal_flops, 22 | clippy::lossy_float_literal, 23 | clippy::rest_pat_in_fully_bound_structs, 24 | clippy::fn_params_excessive_bools, 25 | clippy::exit, 26 | clippy::inefficient_to_string, 27 | clippy::linkedlist, 28 | clippy::macro_use_imports, 29 | clippy::option_option, 30 | clippy::verbose_file_reads, 31 | clippy::unnested_or_patterns, 32 | clippy::str_to_string, 33 | rust_2018_idioms, 34 | future_incompatible, 35 | nonstandard_style, 36 | missing_debug_implementations, 37 | missing_docs 38 | )] 39 | #![deny(unreachable_pub)] 40 | #![allow(elided_lifetimes_in_paths, clippy::type_complexity)] 41 | #![forbid(unsafe_code)] 42 | 43 | pub mod node; 44 | pub mod path; 45 | pub mod spec; 46 | -------------------------------------------------------------------------------- /serde_json_path_core/src/path.rs: -------------------------------------------------------------------------------- 1 | //! Types for representing [Normalized Paths][norm-paths] from the JSONPath specification 2 | //! 3 | //! [norm-paths]: https://www.rfc-editor.org/rfc/rfc9535.html#name-normalized-paths 4 | use std::{ 5 | cmp::Ordering, 6 | fmt::Display, 7 | slice::{Iter, SliceIndex}, 8 | }; 9 | 10 | use serde::Serialize; 11 | 12 | // Documented in the serde_json_path crate, for linking purposes 13 | #[allow(missing_docs)] 14 | #[derive(Debug, Default, Eq, PartialEq, Clone, PartialOrd)] 15 | pub struct NormalizedPath<'a>(Vec>); 16 | 17 | impl<'a> NormalizedPath<'a> { 18 | pub(crate) fn push>>(&mut self, elem: T) { 19 | self.0.push(elem.into()) 20 | } 21 | 22 | pub(crate) fn clone_and_push>>(&self, elem: T) -> Self { 23 | let mut new_path = self.clone(); 24 | new_path.push(elem.into()); 25 | new_path 26 | } 27 | 28 | /// Get the [`NormalizedPath`] as a [JSON Pointer][json-pointer] string 29 | /// 30 | /// This can be used with the [`serde_json::Value::pointer`] or 31 | /// [`serde_json::Value::pointer_mut`] methods. 32 | /// 33 | /// # Example 34 | /// ```rust 35 | /// # use serde_json::json; 36 | /// # use serde_json_path::JsonPath; 37 | /// # fn main() -> Result<(), Box> { 38 | /// let mut value = json!({"foo": ["bar", "baz"]}); 39 | /// let path = JsonPath::parse("$.foo[? @ == 'bar']")?; 40 | /// let pointer= path 41 | /// .query_located(&value) 42 | /// .exactly_one()? 43 | /// .location() 44 | /// .to_json_pointer(); 45 | /// *value.pointer_mut(&pointer).unwrap() = "bop".into(); 46 | /// assert_eq!(value, json!({"foo": ["bop", "baz"]})); 47 | /// # Ok(()) 48 | /// # } 49 | /// ``` 50 | /// 51 | /// [json-pointer]: https://datatracker.ietf.org/doc/html/rfc6901 52 | pub fn to_json_pointer(&self) -> String { 53 | self.0 54 | .iter() 55 | .map(PathElement::to_json_pointer) 56 | .fold(String::from(""), |mut acc, s| { 57 | acc.push('/'); 58 | acc.push_str(&s); 59 | acc 60 | }) 61 | } 62 | 63 | /// Check if the [`NormalizedPath`] is empty 64 | /// 65 | /// An empty normalized path represents the location of the root node of the JSON object, 66 | /// i.e., `$`. 67 | pub fn is_empty(&self) -> bool { 68 | self.0.is_empty() 69 | } 70 | 71 | /// Get the length of the [`NormalizedPath`] 72 | pub fn len(&self) -> usize { 73 | self.0.len() 74 | } 75 | 76 | /// Get an iterator over the [`PathElement`]s of the [`NormalizedPath`] 77 | /// 78 | /// Note that [`NormalizedPath`] also implements [`IntoIterator`] 79 | /// 80 | /// # Example 81 | /// ```rust 82 | /// # use serde_json::json; 83 | /// # use serde_json_path::JsonPath; 84 | /// # fn main() -> Result<(), Box> { 85 | /// let mut value = json!({"foo": {"bar": 1, "baz": 2, "bop": 3}}); 86 | /// let path = JsonPath::parse("$.foo[? @ == 2]")?; 87 | /// let location = path.query_located(&value).exactly_one()?.to_location(); 88 | /// let elements: Vec = location 89 | /// .iter() 90 | /// .map(|ele| ele.to_string()) 91 | /// .collect(); 92 | /// assert_eq!(elements, ["foo", "baz"]); 93 | /// # Ok(()) 94 | /// # } 95 | /// ``` 96 | pub fn iter(&self) -> Iter<'_, PathElement<'a>> { 97 | self.0.iter() 98 | } 99 | 100 | /// Get the [`PathElement`] at `index`, or `None` if the index is out of bounds 101 | /// 102 | /// # Example 103 | /// ```rust 104 | /// # use serde_json::json; 105 | /// # use serde_json_path::JsonPath; 106 | /// # fn main() -> Result<(), Box> { 107 | /// let value = json!({"foo": {"bar": {"baz": "bop"}}}); 108 | /// let path = JsonPath::parse("$..baz")?; 109 | /// let location = path.query_located(&value).exactly_one()?.to_location(); 110 | /// assert_eq!(location.to_string(), "$['foo']['bar']['baz']"); 111 | /// assert!(location.get(0).is_some_and(|p| p == "foo")); 112 | /// assert!(location.get(1..).is_some_and(|p| p == ["bar", "baz"])); 113 | /// assert!(location.get(3).is_none()); 114 | /// # Ok(()) 115 | /// # } 116 | /// ``` 117 | pub fn get(&self, index: I) -> Option<&I::Output> 118 | where 119 | I: SliceIndex<[PathElement<'a>]>, 120 | { 121 | self.0.get(index) 122 | } 123 | 124 | /// Get the first [`PathElement`], or `None` if the path is empty 125 | /// 126 | /// # Example 127 | /// ```rust 128 | /// # use serde_json::json; 129 | /// # use serde_json_path::JsonPath; 130 | /// # fn main() -> Result<(), Box> { 131 | /// let value = json!(["foo", true, {"bar": false}, {"bar": true}]); 132 | /// let path = JsonPath::parse("$..[? @ == false]")?; 133 | /// let location = path.query_located(&value).exactly_one()?.to_location(); 134 | /// assert_eq!(location.to_string(), "$[2]['bar']"); 135 | /// assert!(location.first().is_some_and(|p| *p == 2)); 136 | /// # Ok(()) 137 | /// # } 138 | /// ``` 139 | pub fn first(&self) -> Option<&PathElement<'a>> { 140 | self.0.first() 141 | } 142 | 143 | /// Get the last [`PathElement`], or `None` if the path is empty 144 | /// 145 | /// # Example 146 | /// ```rust 147 | /// # use serde_json::json; 148 | /// # use serde_json_path::JsonPath; 149 | /// # fn main() -> Result<(), Box> { 150 | /// let value = json!({"foo": {"bar": [1, 2, 3]}}); 151 | /// let path = JsonPath::parse("$..[? @ == 2]")?; 152 | /// let location = path.query_located(&value).exactly_one()?.to_location(); 153 | /// assert_eq!(location.to_string(), "$['foo']['bar'][1]"); 154 | /// assert!(location.last().is_some_and(|p| *p == 1)); 155 | /// # Ok(()) 156 | /// # } 157 | /// ``` 158 | pub fn last(&self) -> Option<&PathElement<'a>> { 159 | self.0.last() 160 | } 161 | } 162 | 163 | impl<'a> IntoIterator for NormalizedPath<'a> { 164 | type Item = PathElement<'a>; 165 | 166 | type IntoIter = std::vec::IntoIter; 167 | 168 | fn into_iter(self) -> Self::IntoIter { 169 | self.0.into_iter() 170 | } 171 | } 172 | 173 | impl Display for NormalizedPath<'_> { 174 | /// Format the [`NormalizedPath`] as a JSONPath string using the canonical bracket notation 175 | /// as per the [JSONPath Specification][norm-paths] 176 | /// 177 | /// # Example 178 | /// ```rust 179 | /// # use serde_json::json; 180 | /// # use serde_json_path::JsonPath; 181 | /// # fn main() -> Result<(), Box> { 182 | /// let value = json!({"foo": ["bar", "baz"]}); 183 | /// let path = JsonPath::parse("$.foo[0]")?; 184 | /// let location = path.query_located(&value).exactly_one()?.to_location(); 185 | /// assert_eq!(location.to_string(), "$['foo'][0]"); 186 | /// # Ok(()) 187 | /// # } 188 | /// ``` 189 | /// 190 | /// [norm-paths]: https://www.rfc-editor.org/rfc/rfc9535.html#name-normalized-paths 191 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 192 | write!(f, "$")?; 193 | for elem in &self.0 { 194 | match elem { 195 | PathElement::Name(name) => write!(f, "['{name}']")?, 196 | PathElement::Index(index) => write!(f, "[{index}]")?, 197 | } 198 | } 199 | Ok(()) 200 | } 201 | } 202 | 203 | impl Serialize for NormalizedPath<'_> { 204 | fn serialize(&self, serializer: S) -> Result 205 | where 206 | S: serde::Serializer, 207 | { 208 | serializer.serialize_str(self.to_string().as_str()) 209 | } 210 | } 211 | 212 | /// An element within a [`NormalizedPath`] 213 | #[derive(Debug, Eq, PartialEq, Clone)] 214 | pub enum PathElement<'a> { 215 | /// A key within a JSON object 216 | Name(&'a str), 217 | /// An index of a JSON Array 218 | Index(usize), 219 | } 220 | 221 | impl PathElement<'_> { 222 | fn to_json_pointer(&self) -> String { 223 | match self { 224 | PathElement::Name(s) => s.replace('~', "~0").replace('/', "~1"), 225 | PathElement::Index(i) => i.to_string(), 226 | } 227 | } 228 | 229 | /// Get the underlying name if the [`PathElement`] is `Name`, or `None` otherwise 230 | pub fn as_name(&self) -> Option<&str> { 231 | match self { 232 | PathElement::Name(n) => Some(n), 233 | PathElement::Index(_) => None, 234 | } 235 | } 236 | 237 | /// Get the underlying index if the [`PathElement`] is `Index`, or `None` otherwise 238 | pub fn as_index(&self) -> Option { 239 | match self { 240 | PathElement::Name(_) => None, 241 | PathElement::Index(i) => Some(*i), 242 | } 243 | } 244 | 245 | /// Test if the [`PathElement`] is `Name` 246 | pub fn is_name(&self) -> bool { 247 | self.as_name().is_some() 248 | } 249 | 250 | /// Test if the [`PathElement`] is `Index` 251 | pub fn is_index(&self) -> bool { 252 | self.as_index().is_some() 253 | } 254 | } 255 | 256 | impl PartialOrd for PathElement<'_> { 257 | fn partial_cmp(&self, other: &Self) -> Option { 258 | match (self, other) { 259 | (PathElement::Name(a), PathElement::Name(b)) => a.partial_cmp(b), 260 | (PathElement::Index(a), PathElement::Index(b)) => a.partial_cmp(b), 261 | _ => None, 262 | } 263 | } 264 | } 265 | 266 | impl PartialEq for PathElement<'_> { 267 | fn eq(&self, other: &str) -> bool { 268 | match self { 269 | PathElement::Name(s) => s.eq(&other), 270 | PathElement::Index(_) => false, 271 | } 272 | } 273 | } 274 | 275 | impl PartialEq<&str> for PathElement<'_> { 276 | fn eq(&self, other: &&str) -> bool { 277 | match self { 278 | PathElement::Name(s) => s.eq(other), 279 | PathElement::Index(_) => false, 280 | } 281 | } 282 | } 283 | 284 | impl PartialEq for PathElement<'_> { 285 | fn eq(&self, other: &usize) -> bool { 286 | match self { 287 | PathElement::Name(_) => false, 288 | PathElement::Index(i) => i.eq(other), 289 | } 290 | } 291 | } 292 | 293 | impl Display for PathElement<'_> { 294 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 295 | match self { 296 | PathElement::Name(n) => { 297 | // https://www.rfc-editor.org/rfc/rfc9535#section-2.7 298 | for c in n.chars() { 299 | match c { 300 | '\u{0008}' => write!(f, r#"\b"#)?, // b BS backspace 301 | '\u{000C}' => write!(f, r#"\f"#)?, // f FF form feed 302 | '\u{000A}' => write!(f, r#"\n"#)?, // n LF line feed 303 | '\u{000D}' => write!(f, r#"\r"#)?, // r CR carriage return 304 | '\u{0009}' => write!(f, r#"\t"#)?, // t HT horizontal tab 305 | '\u{0027}' => write!(f, r#"\'"#)?, // ' apostrophe 306 | '\u{005C}' => write!(f, r#"\"#)?, // \ backslash (reverse solidus) 307 | ('\x00'..='\x07') | '\x0b' | '\x0e' | '\x0f' => { 308 | // "00"-"07", "0b", "0e"-"0f" 309 | write!(f, "\\u000{:x}", c as i32)? 310 | } 311 | _ => write!(f, "{c}")?, 312 | } 313 | } 314 | Ok(()) 315 | } 316 | PathElement::Index(i) => write!(f, "{i}"), 317 | } 318 | } 319 | } 320 | 321 | impl<'a> From<&'a String> for PathElement<'a> { 322 | fn from(s: &'a String) -> Self { 323 | Self::Name(s.as_str()) 324 | } 325 | } 326 | 327 | impl From for PathElement<'_> { 328 | fn from(index: usize) -> Self { 329 | Self::Index(index) 330 | } 331 | } 332 | 333 | impl Serialize for PathElement<'_> { 334 | fn serialize(&self, serializer: S) -> Result 335 | where 336 | S: serde::Serializer, 337 | { 338 | match self { 339 | PathElement::Name(s) => serializer.serialize_str(s), 340 | PathElement::Index(i) => serializer.serialize_u64(*i as u64), 341 | } 342 | } 343 | } 344 | 345 | #[cfg(test)] 346 | mod tests { 347 | use super::{NormalizedPath, PathElement}; 348 | 349 | #[test] 350 | fn normalized_path_to_json_pointer() { 351 | let np = NormalizedPath(vec![ 352 | PathElement::Name("foo"), 353 | PathElement::Index(42), 354 | PathElement::Name("bar"), 355 | ]); 356 | assert_eq!(np.to_json_pointer(), "/foo/42/bar"); 357 | } 358 | 359 | #[test] 360 | fn normalized_path_to_json_pointer_with_escapes() { 361 | let np = NormalizedPath(vec![ 362 | PathElement::Name("foo~bar"), 363 | PathElement::Index(42), 364 | PathElement::Name("baz/bop"), 365 | ]); 366 | assert_eq!(np.to_json_pointer(), "/foo~0bar/42/baz~1bop"); 367 | } 368 | 369 | #[test] 370 | fn normalized_element_fmt() { 371 | for (name, elem, exp) in [ 372 | ("simple name", PathElement::Name("foo"), "foo"), 373 | ("index", PathElement::Index(1), "1"), 374 | ("escape_apostrophes", PathElement::Name("'hi'"), r#"\'hi\'"#), 375 | ( 376 | "escapes", 377 | PathElement::Name(r#"'\b\f\n\r\t\\'"#), 378 | r#"\'\b\f\n\r\t\\\'"#, 379 | ), 380 | ( 381 | "escape_vertical_unicode", 382 | PathElement::Name("\u{000B}"), 383 | r#"\u000b"#, 384 | ), 385 | ( 386 | "escape_unicode_null", 387 | PathElement::Name("\u{0000}"), 388 | r#"\u0000"#, 389 | ), 390 | ( 391 | "escape_unicode_runes", 392 | PathElement::Name( 393 | "\u{0001}\u{0002}\u{0003}\u{0004}\u{0005}\u{0006}\u{0007}\u{000e}\u{000F}", 394 | ), 395 | r#"\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u000e\u000f"#, 396 | ), 397 | ] { 398 | assert_eq!(exp, elem.to_string(), "{name}"); 399 | } 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /serde_json_path_core/src/spec/integer.rs: -------------------------------------------------------------------------------- 1 | //! Representation of integers in the JSONPath specification 2 | //! 3 | //! The JSONPath specification defines some rules for integers used in query strings (see [here][spec]). 4 | //! 5 | //! [spec]: https://www.rfc-editor.org/rfc/rfc9535.html#name-overview 6 | 7 | use std::{ 8 | num::{ParseIntError, TryFromIntError}, 9 | str::FromStr, 10 | }; 11 | 12 | /// An integer for internet JSON ([RFC7493][ijson]) 13 | /// 14 | /// The value must be within the range [-(253)+1, (253)-1]. 15 | /// 16 | /// [ijson]: https://www.rfc-editor.org/rfc/rfc7493#section-2.2 17 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] 18 | pub struct Integer(i64); 19 | 20 | /// The maximum allowed value, 2^53 - 1 21 | const MAX: i64 = 9_007_199_254_740_992 - 1; 22 | /// The minimum allowed value (-2^53) + 1 23 | const MIN: i64 = -9_007_199_254_740_992 + 1; 24 | 25 | #[inline] 26 | fn check_i64_is_valid(v: i64) -> bool { 27 | (MIN..=MAX).contains(&v) 28 | } 29 | 30 | impl Integer { 31 | /// An [`Integer`] with the value 0 32 | pub const ZERO: Self = Self(0); 33 | 34 | fn try_new(value: i64) -> Result { 35 | if check_i64_is_valid(value) { 36 | Ok(Self(value)) 37 | } else { 38 | Err(IntegerError::OutOfBounds) 39 | } 40 | } 41 | 42 | /// Get an [`Integer`] from an `i64` 43 | /// 44 | /// This is intended for initializing an integer with small, non-zero numbers. 45 | /// 46 | /// # Panics 47 | /// 48 | /// This will panic if the inputted value is out of the valid range 49 | /// [-(253)+1, (253)-1]. 50 | pub fn from_i64_unchecked(value: i64) -> Self { 51 | Self::try_new(value).expect("value is out of the valid range") 52 | } 53 | 54 | /// Take the absolute value, producing a new instance of [`Integer`] 55 | /// 56 | /// This is safe and will never panic since no instance of [`Integer`] can be constructed with 57 | /// a value that is outside the valid range and since the absolute of the minimum allowed value 58 | /// is the maximum value. 59 | pub fn abs(self) -> Self { 60 | Self(self.0.abs()) 61 | } 62 | 63 | /// Add the two values, producing a new instance of [`Integer`] or `None` if the 64 | /// resulting value is outside the valid range [-(253)+1, (253)-1] 65 | pub fn checked_add(self, rhs: Self) -> Option { 66 | let i = self.0.checked_add(rhs.0)?; 67 | check_i64_is_valid(i).then_some(Self(i)) 68 | } 69 | 70 | /// Subtract the `rhs` from `self`, producing a new instance of [`Integer`] or `None` 71 | /// if the resulting value is outside the valid range [-(253)+1, (253)-1]. 72 | pub fn checked_sub(self, rhs: Self) -> Option { 73 | let i = self.0.checked_sub(rhs.0)?; 74 | check_i64_is_valid(i).then_some(Self(i)) 75 | } 76 | 77 | /// Multiply the two values, producing a new instance of [`Integer`] or `None` if the resulting 78 | /// value is outside the valid range [-(253)+1, (253)-1]. 79 | pub fn checked_mul(self, rhs: Self) -> Option { 80 | let i = self.0.checked_mul(rhs.0)?; 81 | check_i64_is_valid(i).then_some(Self(i)) 82 | } 83 | } 84 | 85 | impl TryFrom for Integer { 86 | type Error = IntegerError; 87 | 88 | fn try_from(value: i64) -> Result { 89 | Self::try_new(value) 90 | } 91 | } 92 | 93 | macro_rules! impl_try_from { 94 | ($type:ty) => { 95 | impl TryFrom<$type> for Integer { 96 | type Error = IntegerError; 97 | 98 | fn try_from(value: $type) -> Result { 99 | i64::try_from(value) 100 | .map_err(|_| IntegerError::OutOfBounds) 101 | .and_then(Self::try_from) 102 | } 103 | } 104 | }; 105 | } 106 | 107 | impl_try_from!(i128); 108 | impl_try_from!(u64); 109 | impl_try_from!(u128); 110 | impl_try_from!(usize); 111 | impl_try_from!(isize); 112 | 113 | macro_rules! impl_from { 114 | ($type:ty) => { 115 | impl From<$type> for Integer { 116 | fn from(value: $type) -> Self { 117 | Self(value.into()) 118 | } 119 | } 120 | }; 121 | } 122 | 123 | impl_from!(i8); 124 | impl_from!(i16); 125 | impl_from!(i32); 126 | impl_from!(u8); 127 | impl_from!(u16); 128 | impl_from!(u32); 129 | 130 | impl FromStr for Integer { 131 | type Err = IntegerError; 132 | 133 | fn from_str(s: &str) -> Result { 134 | s.parse::().map_err(Into::into).and_then(Self::try_new) 135 | } 136 | } 137 | 138 | impl std::fmt::Display for Integer { 139 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 140 | write!(f, "{}", self.0) 141 | } 142 | } 143 | 144 | impl TryFrom for usize { 145 | type Error = TryFromIntError; 146 | 147 | fn try_from(value: Integer) -> Result { 148 | Self::try_from(value.0) 149 | } 150 | } 151 | 152 | impl PartialEq for Integer { 153 | fn eq(&self, other: &i64) -> bool { 154 | self.0.eq(other) 155 | } 156 | } 157 | 158 | impl PartialOrd for Integer { 159 | fn partial_cmp(&self, other: &i64) -> Option { 160 | self.0.partial_cmp(other) 161 | } 162 | } 163 | 164 | /// An error for the [`Integer`] type 165 | #[derive(Debug, thiserror::Error)] 166 | pub enum IntegerError { 167 | /// The provided value was outside the valid range [-(2**53)+1, (2**53)-1] 168 | #[error("the provided integer was outside the valid range, see https://www.rfc-editor.org/rfc/rfc9535.html#section-2.1-4.1")] 169 | OutOfBounds, 170 | /// Integer parsing error 171 | #[error(transparent)] 172 | Parse(#[from] ParseIntError), 173 | } 174 | -------------------------------------------------------------------------------- /serde_json_path_core/src/spec/mod.rs: -------------------------------------------------------------------------------- 1 | //! Types representing the IETF JSONPath Standard 2 | pub mod functions; 3 | pub mod integer; 4 | pub mod query; 5 | pub mod segment; 6 | pub mod selector; 7 | -------------------------------------------------------------------------------- /serde_json_path_core/src/spec/query.rs: -------------------------------------------------------------------------------- 1 | //! Types representing queries in JSONPath 2 | use serde_json::Value; 3 | 4 | use crate::{node::LocatedNode, path::NormalizedPath}; 5 | 6 | use super::segment::QuerySegment; 7 | 8 | mod sealed { 9 | use crate::spec::{ 10 | segment::{QuerySegment, Segment}, 11 | selector::{ 12 | filter::{Filter, SingularQuery}, 13 | index::Index, 14 | name::Name, 15 | slice::Slice, 16 | Selector, 17 | }, 18 | }; 19 | 20 | use super::Query; 21 | 22 | pub trait Sealed {} 23 | impl Sealed for Query {} 24 | impl Sealed for QuerySegment {} 25 | impl Sealed for Segment {} 26 | impl Sealed for Slice {} 27 | impl Sealed for Name {} 28 | impl Sealed for Selector {} 29 | impl Sealed for Index {} 30 | impl Sealed for Filter {} 31 | impl Sealed for SingularQuery {} 32 | } 33 | 34 | /// A type that is query-able 35 | pub trait Queryable: sealed::Sealed { 36 | /// Query `self` using a current node, and the root node 37 | fn query<'b>(&self, current: &'b Value, root: &'b Value) -> Vec<&'b Value>; 38 | /// Query `self` using a current node, the root node, and the normalized path of the current 39 | /// node's parent 40 | fn query_located<'b>( 41 | &self, 42 | current: &'b Value, 43 | root: &'b Value, 44 | parent: NormalizedPath<'b>, 45 | ) -> Vec>; 46 | } 47 | 48 | /// Represents a JSONPath expression 49 | #[derive(Debug, PartialEq, Eq, Clone, Default)] 50 | pub struct Query { 51 | /// The kind of query, root (`$`), or current (`@`) 52 | pub kind: QueryKind, 53 | /// The segments constituting the query 54 | pub segments: Vec, 55 | } 56 | 57 | impl Query { 58 | pub(crate) fn is_singular(&self) -> bool { 59 | for s in &self.segments { 60 | if s.is_descendent() { 61 | return false; 62 | } 63 | if !s.segment.is_singular() { 64 | return false; 65 | } 66 | } 67 | true 68 | } 69 | } 70 | 71 | impl std::fmt::Display for Query { 72 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 73 | match self.kind { 74 | QueryKind::Root => write!(f, "$")?, 75 | QueryKind::Current => write!(f, "@")?, 76 | } 77 | for s in &self.segments { 78 | write!(f, "{s}")?; 79 | } 80 | Ok(()) 81 | } 82 | } 83 | 84 | /// The kind of query 85 | #[derive(Debug, PartialEq, Eq, Clone, Default)] 86 | pub enum QueryKind { 87 | /// A query against the root of a JSON object, i.e., with `$` 88 | #[default] 89 | Root, 90 | /// A query against the current node within a JSON object, i.e., with `@` 91 | Current, 92 | } 93 | 94 | impl Queryable for Query { 95 | #[cfg_attr(feature = "trace", tracing::instrument(name = "Main Query", level = "trace", parent = None, ret))] 96 | fn query<'b>(&self, current: &'b Value, root: &'b Value) -> Vec<&'b Value> { 97 | let mut query = match self.kind { 98 | QueryKind::Root => vec![root], 99 | QueryKind::Current => vec![current], 100 | }; 101 | for segment in &self.segments { 102 | let mut new_query = Vec::new(); 103 | for q in &query { 104 | new_query.append(&mut segment.query(q, root)); 105 | } 106 | query = new_query; 107 | } 108 | query 109 | } 110 | 111 | fn query_located<'b>( 112 | &self, 113 | current: &'b Value, 114 | root: &'b Value, 115 | parent: NormalizedPath<'b>, 116 | ) -> Vec> { 117 | let mut result: Vec> = match self.kind { 118 | QueryKind::Root => vec![LocatedNode { 119 | loc: Default::default(), 120 | node: root, 121 | }], 122 | QueryKind::Current => vec![LocatedNode { 123 | loc: parent, 124 | node: current, 125 | }], 126 | }; 127 | for s in &self.segments { 128 | let mut r = vec![]; 129 | for LocatedNode { loc, node } in result { 130 | r.append(&mut s.query_located(node, root, loc.clone())); 131 | } 132 | result = r; 133 | } 134 | result 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /serde_json_path_core/src/spec/segment.rs: -------------------------------------------------------------------------------- 1 | //! Types representing segments in JSONPath 2 | use serde_json::Value; 3 | 4 | use crate::{node::LocatedNode, path::NormalizedPath}; 5 | 6 | use super::{query::Queryable, selector::Selector}; 7 | 8 | /// A segment of a JSONPath query 9 | #[derive(Debug, PartialEq, Eq, Clone)] 10 | pub struct QuerySegment { 11 | /// The kind of segment 12 | pub kind: QuerySegmentKind, 13 | /// The segment 14 | pub segment: Segment, 15 | } 16 | 17 | impl QuerySegment { 18 | /// Is this a normal child segment 19 | pub fn is_child(&self) -> bool { 20 | matches!(self.kind, QuerySegmentKind::Child) 21 | } 22 | 23 | /// Is this a recursive descent child 24 | pub fn is_descendent(&self) -> bool { 25 | !self.is_child() 26 | } 27 | } 28 | 29 | impl std::fmt::Display for QuerySegment { 30 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 31 | if matches!(self.kind, QuerySegmentKind::Descendant) { 32 | write!(f, "..")?; 33 | } 34 | write!(f, "{segment}", segment = self.segment) 35 | } 36 | } 37 | 38 | /// The kind of query segment 39 | #[derive(Debug, PartialEq, Eq, Clone)] 40 | pub enum QuerySegmentKind { 41 | /// A normal child 42 | /// 43 | /// Addresses the direct descendant of the preceding segment 44 | Child, 45 | /// A descendant child 46 | /// 47 | /// Addresses all descendant children of the preceding segment, recursively 48 | Descendant, 49 | } 50 | 51 | impl Queryable for QuerySegment { 52 | #[cfg_attr(feature = "trace", tracing::instrument(name = "Query Path Segment", level = "trace", parent = None, ret))] 53 | fn query<'b>(&self, current: &'b Value, root: &'b Value) -> Vec<&'b Value> { 54 | let mut query = self.segment.query(current, root); 55 | if matches!(self.kind, QuerySegmentKind::Descendant) { 56 | query.append(&mut descend(self, current, root)); 57 | } 58 | query 59 | } 60 | 61 | fn query_located<'b>( 62 | &self, 63 | current: &'b Value, 64 | root: &'b Value, 65 | parent: NormalizedPath<'b>, 66 | ) -> Vec> { 67 | if matches!(self.kind, QuerySegmentKind::Descendant) { 68 | let mut result = self.segment.query_located(current, root, parent.clone()); 69 | result.append(&mut descend_paths(self, current, root, parent)); 70 | result 71 | } else { 72 | self.segment.query_located(current, root, parent) 73 | } 74 | } 75 | } 76 | 77 | #[cfg_attr(feature = "trace", tracing::instrument(name = "Descend", level = "trace", parent = None, ret))] 78 | fn descend<'b>(segment: &QuerySegment, current: &'b Value, root: &'b Value) -> Vec<&'b Value> { 79 | let mut query = Vec::new(); 80 | if let Some(list) = current.as_array() { 81 | for v in list { 82 | query.append(&mut segment.query(v, root)); 83 | } 84 | } else if let Some(obj) = current.as_object() { 85 | for (_, v) in obj { 86 | query.append(&mut segment.query(v, root)); 87 | } 88 | } 89 | query 90 | } 91 | 92 | fn descend_paths<'b>( 93 | segment: &QuerySegment, 94 | current: &'b Value, 95 | root: &'b Value, 96 | parent: NormalizedPath<'b>, 97 | ) -> Vec> { 98 | let mut result = Vec::new(); 99 | if let Some(list) = current.as_array() { 100 | for (i, v) in list.iter().enumerate() { 101 | result.append(&mut segment.query_located(v, root, parent.clone_and_push(i))); 102 | } 103 | } else if let Some(obj) = current.as_object() { 104 | for (k, v) in obj { 105 | result.append(&mut segment.query_located(v, root, parent.clone_and_push(k))); 106 | } 107 | } 108 | result 109 | } 110 | 111 | /// Represents the different forms of JSONPath segment 112 | #[derive(Debug, PartialEq, Eq, Clone)] 113 | pub enum Segment { 114 | /// Long hand segments contain multiple selectors inside square brackets 115 | LongHand(Vec), 116 | /// Dot-name selectors are a short form for representing keys in an object 117 | DotName(String), 118 | /// The wildcard shorthand `.*` 119 | Wildcard, 120 | } 121 | 122 | impl Segment { 123 | /// Does this segment extract a singular node 124 | pub fn is_singular(&self) -> bool { 125 | match self { 126 | Segment::LongHand(selectors) => { 127 | if selectors.len() > 1 { 128 | return false; 129 | } 130 | if let Some(s) = selectors.first() { 131 | s.is_singular() 132 | } else { 133 | // if the selector list is empty, this shouldn't be a valid 134 | // JSONPath, but at least, it would be selecting nothing, and 135 | // that could be considered singular, i.e., None. 136 | true 137 | } 138 | } 139 | Segment::DotName(_) => true, 140 | Segment::Wildcard => false, 141 | } 142 | } 143 | 144 | /// Optionally produce self as a slice of selectors, from a long hand segment 145 | pub fn as_long_hand(&self) -> Option<&[Selector]> { 146 | match self { 147 | Segment::LongHand(v) => Some(v.as_slice()), 148 | _ => None, 149 | } 150 | } 151 | 152 | /// Optionally produce self as a single name segment 153 | pub fn as_dot_name(&self) -> Option<&str> { 154 | match self { 155 | Segment::DotName(s) => Some(s.as_str()), 156 | _ => None, 157 | } 158 | } 159 | } 160 | 161 | impl std::fmt::Display for Segment { 162 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 163 | match self { 164 | Segment::LongHand(selectors) => { 165 | write!(f, "[")?; 166 | for (i, s) in selectors.iter().enumerate() { 167 | write!( 168 | f, 169 | "{s}{comma}", 170 | comma = if i == selectors.len() - 1 { "" } else { "," } 171 | )?; 172 | } 173 | write!(f, "]")?; 174 | } 175 | Segment::DotName(name) => write!(f, ".{name}")?, 176 | Segment::Wildcard => write!(f, ".*")?, 177 | } 178 | Ok(()) 179 | } 180 | } 181 | 182 | impl Queryable for Segment { 183 | #[cfg_attr(feature = "trace", tracing::instrument(name = "Query Segment", level = "trace", parent = None, ret))] 184 | fn query<'b>(&self, current: &'b Value, root: &'b Value) -> Vec<&'b Value> { 185 | let mut query = Vec::new(); 186 | match self { 187 | Segment::LongHand(selectors) => { 188 | for selector in selectors { 189 | query.append(&mut selector.query(current, root)); 190 | } 191 | } 192 | Segment::DotName(key) => { 193 | if let Some(obj) = current.as_object() { 194 | if let Some(v) = obj.get(key) { 195 | query.push(v); 196 | } 197 | } 198 | } 199 | Segment::Wildcard => { 200 | if let Some(list) = current.as_array() { 201 | for v in list { 202 | query.push(v); 203 | } 204 | } else if let Some(obj) = current.as_object() { 205 | for (_, v) in obj { 206 | query.push(v); 207 | } 208 | } 209 | } 210 | } 211 | query 212 | } 213 | 214 | fn query_located<'b>( 215 | &self, 216 | current: &'b Value, 217 | root: &'b Value, 218 | mut parent: NormalizedPath<'b>, 219 | ) -> Vec> { 220 | let mut result = vec![]; 221 | match self { 222 | Segment::LongHand(selectors) => { 223 | for s in selectors { 224 | result.append(&mut s.query_located(current, root, parent.clone())); 225 | } 226 | } 227 | Segment::DotName(name) => { 228 | if let Some((k, v)) = current.as_object().and_then(|o| o.get_key_value(name)) { 229 | parent.push(k); 230 | result.push(LocatedNode { 231 | loc: parent, 232 | node: v, 233 | }); 234 | } 235 | } 236 | Segment::Wildcard => { 237 | if let Some(list) = current.as_array() { 238 | for (i, v) in list.iter().enumerate() { 239 | result.push(LocatedNode { 240 | loc: parent.clone_and_push(i), 241 | node: v, 242 | }); 243 | } 244 | } else if let Some(obj) = current.as_object() { 245 | for (k, v) in obj { 246 | result.push(LocatedNode { 247 | loc: parent.clone_and_push(k), 248 | node: v, 249 | }); 250 | } 251 | } 252 | } 253 | } 254 | result 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /serde_json_path_core/src/spec/selector/index.rs: -------------------------------------------------------------------------------- 1 | //! Index selectors in JSONPath 2 | use serde_json::Value; 3 | 4 | use crate::{ 5 | node::LocatedNode, 6 | path::NormalizedPath, 7 | spec::{integer::Integer, query::Queryable}, 8 | }; 9 | 10 | /// For selecting array elements by their index 11 | /// 12 | /// Can use negative indices to index from the end of an array 13 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 14 | pub struct Index(pub Integer); 15 | 16 | impl std::fmt::Display for Index { 17 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 18 | write!(f, "{index}", index = self.0) 19 | } 20 | } 21 | 22 | impl Queryable for Index { 23 | #[cfg_attr(feature = "trace", tracing::instrument(name = "Query Index", level = "trace", parent = None, ret))] 24 | fn query<'b>(&self, current: &'b Value, _root: &'b Value) -> Vec<&'b Value> { 25 | if let Some(list) = current.as_array() { 26 | if self.0 < 0 { 27 | let abs = self.0.abs(); 28 | usize::try_from(abs) 29 | .ok() 30 | .and_then(|i| list.len().checked_sub(i)) 31 | .and_then(|i| list.get(i)) 32 | .into_iter() 33 | .collect() 34 | } else { 35 | usize::try_from(self.0) 36 | .ok() 37 | .and_then(|i| list.get(i)) 38 | .into_iter() 39 | .collect() 40 | } 41 | } else { 42 | vec![] 43 | } 44 | } 45 | 46 | fn query_located<'b>( 47 | &self, 48 | current: &'b Value, 49 | _root: &'b Value, 50 | mut parent: NormalizedPath<'b>, 51 | ) -> Vec> { 52 | if let Some((index, node)) = current.as_array().and_then(|list| { 53 | if self.0 < 0 { 54 | let abs = self.0.abs(); 55 | usize::try_from(abs) 56 | .ok() 57 | .and_then(|i| list.len().checked_sub(i)) 58 | .and_then(|i| list.get(i).map(|v| (i, v))) 59 | } else { 60 | usize::try_from(self.0) 61 | .ok() 62 | .and_then(|i| list.get(i).map(|v| (i, v))) 63 | } 64 | }) { 65 | parent.push(index); 66 | vec![LocatedNode { loc: parent, node }] 67 | } else { 68 | vec![] 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /serde_json_path_core/src/spec/selector/mod.rs: -------------------------------------------------------------------------------- 1 | //! Types representing the different selectors in JSONPath 2 | pub mod filter; 3 | pub mod index; 4 | pub mod name; 5 | pub mod slice; 6 | 7 | use serde_json::Value; 8 | 9 | use crate::{node::LocatedNode, path::NormalizedPath}; 10 | 11 | use self::{filter::Filter, index::Index, name::Name, slice::Slice}; 12 | 13 | use super::query::Queryable; 14 | 15 | /// A JSONPath selector 16 | #[derive(Debug, PartialEq, Eq, Clone)] 17 | pub enum Selector { 18 | /// Select an object key 19 | Name(Name), 20 | /// Select all nodes 21 | /// 22 | /// For an object, this produces a nodelist of all member values; for an array, this produces a 23 | /// nodelist of all array elements. 24 | Wildcard, 25 | /// Select an array element 26 | Index(Index), 27 | /// Select a slice from an array 28 | ArraySlice(Slice), 29 | /// Use a filter to select nodes 30 | Filter(Filter), 31 | } 32 | 33 | impl Selector { 34 | /// Will the selector select at most only a single node 35 | pub fn is_singular(&self) -> bool { 36 | matches!(self, Selector::Name(_) | Selector::Index(_)) 37 | } 38 | } 39 | 40 | impl std::fmt::Display for Selector { 41 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 42 | match self { 43 | Selector::Name(name) => write!(f, "{name}"), 44 | Selector::Wildcard => write!(f, "*"), 45 | Selector::Index(index) => write!(f, "{index}"), 46 | Selector::ArraySlice(slice) => write!(f, "{slice}"), 47 | Selector::Filter(filter) => write!(f, "?{filter}"), 48 | } 49 | } 50 | } 51 | 52 | impl Queryable for Selector { 53 | #[cfg_attr(feature = "trace", tracing::instrument(name = "Query Selector", level = "trace", parent = None, ret))] 54 | fn query<'b>(&self, current: &'b Value, root: &'b Value) -> Vec<&'b Value> { 55 | let mut query = Vec::new(); 56 | match self { 57 | Selector::Name(name) => query.append(&mut name.query(current, root)), 58 | Selector::Wildcard => { 59 | if let Some(list) = current.as_array() { 60 | for v in list { 61 | query.push(v); 62 | } 63 | } else if let Some(obj) = current.as_object() { 64 | for (_, v) in obj { 65 | query.push(v); 66 | } 67 | } 68 | } 69 | Selector::Index(index) => query.append(&mut index.query(current, root)), 70 | Selector::ArraySlice(slice) => query.append(&mut slice.query(current, root)), 71 | Selector::Filter(filter) => query.append(&mut filter.query(current, root)), 72 | } 73 | query 74 | } 75 | 76 | fn query_located<'b>( 77 | &self, 78 | current: &'b Value, 79 | root: &'b Value, 80 | parent: NormalizedPath<'b>, 81 | ) -> Vec> { 82 | match self { 83 | Selector::Name(name) => name.query_located(current, root, parent), 84 | Selector::Wildcard => { 85 | if let Some(list) = current.as_array() { 86 | list.iter() 87 | .enumerate() 88 | .map(|(i, node)| LocatedNode { 89 | loc: parent.clone_and_push(i), 90 | node, 91 | }) 92 | .collect() 93 | } else if let Some(obj) = current.as_object() { 94 | obj.iter() 95 | .map(|(k, node)| LocatedNode { 96 | loc: parent.clone_and_push(k), 97 | node, 98 | }) 99 | .collect() 100 | } else { 101 | vec![] 102 | } 103 | } 104 | Selector::Index(index) => index.query_located(current, root, parent), 105 | Selector::ArraySlice(slice) => slice.query_located(current, root, parent), 106 | Selector::Filter(filter) => filter.query_located(current, root, parent), 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /serde_json_path_core/src/spec/selector/name.rs: -------------------------------------------------------------------------------- 1 | //! Name selector for selecting object keys in JSONPath 2 | use serde_json::Value; 3 | 4 | use crate::{node::LocatedNode, path::NormalizedPath, spec::query::Queryable}; 5 | 6 | /// Select a single JSON object key 7 | #[derive(Debug, PartialEq, Eq, Clone)] 8 | pub struct Name(pub String); 9 | 10 | impl Name { 11 | /// Get as a string slice 12 | pub fn as_str(&self) -> &str { 13 | &self.0 14 | } 15 | } 16 | 17 | impl std::fmt::Display for Name { 18 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 19 | write!(f, "'{name}'", name = self.0) 20 | } 21 | } 22 | 23 | impl Queryable for Name { 24 | #[cfg_attr(feature = "trace", tracing::instrument(name = "Query Name", level = "trace", parent = None, ret))] 25 | fn query<'b>(&self, current: &'b Value, _root: &'b Value) -> Vec<&'b Value> { 26 | if let Some(obj) = current.as_object() { 27 | obj.get(&self.0).into_iter().collect() 28 | } else { 29 | vec![] 30 | } 31 | } 32 | 33 | fn query_located<'b>( 34 | &self, 35 | current: &'b Value, 36 | _root: &'b Value, 37 | mut parent: NormalizedPath<'b>, 38 | ) -> Vec> { 39 | if let Some((name, node)) = current.as_object().and_then(|o| o.get_key_value(&self.0)) { 40 | parent.push(name); 41 | vec![LocatedNode { loc: parent, node }] 42 | } else { 43 | vec![] 44 | } 45 | } 46 | } 47 | 48 | impl From<&str> for Name { 49 | fn from(s: &str) -> Self { 50 | Self(s.to_owned()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /serde_json_path_core/src/spec/selector/slice.rs: -------------------------------------------------------------------------------- 1 | //! Slice selectors for selecting array slices in JSONPath 2 | use serde_json::Value; 3 | 4 | use crate::{ 5 | node::LocatedNode, 6 | path::NormalizedPath, 7 | spec::{integer::Integer, query::Queryable}, 8 | }; 9 | 10 | /// A slice selector 11 | #[derive(Debug, PartialEq, Eq, Default, Clone, Copy)] 12 | pub struct Slice { 13 | /// The start of the slice 14 | /// 15 | /// This can be negative to start the slice from a position relative to the end of the array 16 | /// being sliced. 17 | pub start: Option, 18 | /// The end of the slice 19 | /// 20 | /// This can be negative to end the slice at a position relative to the end of the array being 21 | /// sliced. 22 | pub end: Option, 23 | /// The step slice for the slice 24 | /// 25 | /// This can be negative to step in reverse order. 26 | pub step: Option, 27 | } 28 | 29 | impl std::fmt::Display for Slice { 30 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 31 | if let Some(start) = self.start { 32 | write!(f, "{start}")?; 33 | } 34 | write!(f, ":")?; 35 | if let Some(end) = self.end { 36 | write!(f, "{end}")?; 37 | } 38 | write!(f, ":")?; 39 | if let Some(step) = self.step { 40 | write!(f, "{step}")?; 41 | } 42 | Ok(()) 43 | } 44 | } 45 | 46 | #[doc(hidden)] 47 | impl Slice { 48 | pub fn new() -> Self { 49 | Self::default() 50 | } 51 | 52 | /// Set the slice `start` 53 | /// 54 | /// # Panics 55 | /// 56 | /// This will panic if the provided value is outside the range [-(253) + 1, (253) - 1]. 57 | pub fn with_start(mut self, start: i64) -> Self { 58 | self.start = Some(Integer::from_i64_unchecked(start)); 59 | self 60 | } 61 | 62 | /// Set the slice `end` 63 | /// 64 | /// # Panics 65 | /// 66 | /// This will panic if the provided value is outside the range [-(253) + 1, (253) - 1]. 67 | pub fn with_end(mut self, end: i64) -> Self { 68 | self.end = Some(Integer::from_i64_unchecked(end)); 69 | self 70 | } 71 | 72 | /// Set the slice `step` 73 | /// 74 | /// # Panics 75 | /// 76 | /// This will panic if the provided value is outside the range [-(253) + 1, (253) - 1]. 77 | pub fn with_step(mut self, step: i64) -> Self { 78 | self.step = Some(Integer::from_i64_unchecked(step)); 79 | self 80 | } 81 | 82 | #[inline] 83 | fn bounds_on_forward_slice(&self, len: Integer) -> (Integer, Integer) { 84 | let start_default = self.start.unwrap_or(Integer::ZERO); 85 | let end_default = self.end.unwrap_or(len); 86 | let start = normalize_slice_index(start_default, len) 87 | .unwrap_or(Integer::ZERO) 88 | .max(Integer::ZERO); 89 | let end = normalize_slice_index(end_default, len) 90 | .unwrap_or(Integer::ZERO) 91 | .max(Integer::ZERO); 92 | let lower = start.min(len); 93 | let upper = end.min(len); 94 | (lower, upper) 95 | } 96 | 97 | #[inline] 98 | fn bounds_on_reverse_slice(&self, len: Integer) -> Option<(Integer, Integer)> { 99 | let start_default = self 100 | .start 101 | .or_else(|| len.checked_sub(Integer::from_i64_unchecked(1)))?; 102 | let end_default = self.end.or_else(|| { 103 | let l = len.checked_mul(Integer::from_i64_unchecked(-1))?; 104 | l.checked_sub(Integer::from_i64_unchecked(1)) 105 | })?; 106 | let start = normalize_slice_index(start_default, len) 107 | .unwrap_or(Integer::ZERO) 108 | .max(Integer::from_i64_unchecked(-1)); 109 | let end = normalize_slice_index(end_default, len) 110 | .unwrap_or(Integer::ZERO) 111 | .max(Integer::from_i64_unchecked(-1)); 112 | let lower = end.min( 113 | len.checked_sub(Integer::from_i64_unchecked(1)) 114 | .unwrap_or(len), 115 | ); 116 | let upper = start.min( 117 | len.checked_sub(Integer::from_i64_unchecked(1)) 118 | .unwrap_or(len), 119 | ); 120 | Some((lower, upper)) 121 | } 122 | } 123 | 124 | impl Queryable for Slice { 125 | #[cfg_attr(feature = "trace", tracing::instrument(name = "Query Slice", level = "trace", parent = None, ret))] 126 | fn query<'b>(&self, current: &'b Value, _root: &'b Value) -> Vec<&'b Value> { 127 | if let Some(list) = current.as_array() { 128 | let mut query = Vec::new(); 129 | let step = self.step.unwrap_or(Integer::from_i64_unchecked(1)); 130 | if step == 0 { 131 | return vec![]; 132 | } 133 | let Ok(len) = Integer::try_from(list.len()) else { 134 | return vec![]; 135 | }; 136 | if step > 0 { 137 | let (lower, upper) = self.bounds_on_forward_slice(len); 138 | let mut i = lower; 139 | while i < upper { 140 | if let Some(v) = usize::try_from(i).ok().and_then(|i| list.get(i)) { 141 | query.push(v); 142 | } 143 | i = if let Some(i) = i.checked_add(step) { 144 | i 145 | } else { 146 | break; 147 | }; 148 | } 149 | } else { 150 | let Some((lower, upper)) = self.bounds_on_reverse_slice(len) else { 151 | return vec![]; 152 | }; 153 | let mut i = upper; 154 | while lower < i { 155 | if let Some(v) = usize::try_from(i).ok().and_then(|i| list.get(i)) { 156 | query.push(v); 157 | } 158 | i = if let Some(i) = i.checked_add(step) { 159 | i 160 | } else { 161 | break; 162 | }; 163 | } 164 | } 165 | query 166 | } else { 167 | vec![] 168 | } 169 | } 170 | 171 | fn query_located<'b>( 172 | &self, 173 | current: &'b Value, 174 | _root: &'b Value, 175 | parent: NormalizedPath<'b>, 176 | ) -> Vec> { 177 | if let Some(list) = current.as_array() { 178 | let mut result = Vec::new(); 179 | let step = self.step.unwrap_or(Integer::from_i64_unchecked(1)); 180 | if step == 0 { 181 | return vec![]; 182 | } 183 | let Ok(len) = Integer::try_from(list.len()) else { 184 | return vec![]; 185 | }; 186 | if step > 0 { 187 | let (lower, upper) = self.bounds_on_forward_slice(len); 188 | let mut i = lower; 189 | while i < upper { 190 | if let Some((i, node)) = usize::try_from(i) 191 | .ok() 192 | .and_then(|i| list.get(i).map(|v| (i, v))) 193 | { 194 | result.push(LocatedNode { 195 | loc: parent.clone_and_push(i), 196 | node, 197 | }); 198 | } 199 | i = if let Some(i) = i.checked_add(step) { 200 | i 201 | } else { 202 | break; 203 | }; 204 | } 205 | } else { 206 | let Some((lower, upper)) = self.bounds_on_reverse_slice(len) else { 207 | return vec![]; 208 | }; 209 | let mut i = upper; 210 | while lower < i { 211 | if let Some((i, node)) = usize::try_from(i) 212 | .ok() 213 | .and_then(|i| list.get(i).map(|v| (i, v))) 214 | { 215 | result.push(LocatedNode { 216 | loc: parent.clone_and_push(i), 217 | node, 218 | }); 219 | } 220 | i = if let Some(i) = i.checked_add(step) { 221 | i 222 | } else { 223 | break; 224 | }; 225 | } 226 | } 227 | result 228 | } else { 229 | vec![] 230 | } 231 | } 232 | } 233 | 234 | fn normalize_slice_index(index: Integer, len: Integer) -> Option { 235 | if index >= 0 { 236 | Some(index) 237 | } else { 238 | len.checked_sub(index.abs()) 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /serde_json_path_macros/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All noteable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | # Unreleased 9 | 10 | # 0.1.5 (3 November 2024) 11 | 12 | - **internal**: remove use of `once_cell` and use specific versions for crate dependencies ([#105]) 13 | 14 | [#105]: https://github.com/hiltontj/serde_json_path/pull/105 15 | 16 | # 0.1.4 (3 March 2024) 17 | 18 | - **fixed**: bug preventing registered functions from being used as arguments to other functions ([#84]) 19 | - **testing**: support tests for non-determinism in compliance test suite ([#85]) 20 | 21 | [#84]: https://github.com/hiltontj/serde_json_path/pull/84 22 | [#85]: https://github.com/hiltontj/serde_json_path/pull/85 23 | 24 | # 0.1.3 (23 February 2024) 25 | 26 | - **internal**: update serde_json_path_core dependency 27 | 28 | # 0.1.2 (2 February 2024) 29 | 30 | - **internal**: address new clippy lints in Rust 1.74 ([#70]) 31 | - **internal**: code clean-up ([#72]) 32 | 33 | [#70]: https://github.com/hiltontj/serde_json_path/pull/70 34 | [#72]: https://github.com/hiltontj/serde_json_path/pull/72 35 | 36 | # 0.1.1 (9 November 2023) 37 | 38 | - bump version for `serde_json_path_core` update 39 | 40 | _Previous versions not tracked here._ 41 | 42 | -------------------------------------------------------------------------------- /serde_json_path_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serde_json_path_macros" 3 | version = "0.1.6" 4 | edition = "2021" 5 | license = "MIT" 6 | authors = ["Trevor Hilton "] 7 | description = "Macros for the serde_json_path crate" 8 | repository = "https://github.com/hiltontj/serde_json_path" 9 | readme = "README.md" 10 | keywords = ["json", "jsonpath", "json_path", "serde", "serde_json"] 11 | 12 | [lib] 13 | 14 | [dependencies] 15 | # local crates: 16 | serde_json_path_macros_internal = { path = "src/internal", version = "0.1.2" } 17 | serde_json_path_core = { path = "../serde_json_path_core", version = "0.2.2" } 18 | 19 | # crates.io crates: 20 | inventory.workspace = true 21 | 22 | [dev-dependencies] 23 | serde_json = "1" 24 | 25 | -------------------------------------------------------------------------------- /serde_json_path_macros/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Trevor Hilton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /serde_json_path_macros/README.md: -------------------------------------------------------------------------------- 1 | # serde_json_path_macros 2 | 3 | Macros for the [`serde_json_path`][sjp] crate. 4 | 5 | [sjp]: https://crates.io/crates/serde_json_path 6 | -------------------------------------------------------------------------------- /serde_json_path_macros/src/internal/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serde_json_path_macros_internal" 3 | version = "0.1.2" 4 | edition = "2021" 5 | license = "MIT" 6 | authors = ["Trevor Hilton "] 7 | description = "Internal macro implementation for the serde_json_path crate" 8 | repository = "https://github.com/hiltontj/serde_json_path" 9 | readme = "README.md" 10 | keywords = ["json", "jsonpath", "json_path", "serde", "serde_json"] 11 | 12 | [lib] 13 | proc-macro = true 14 | path = "mod.rs" 15 | 16 | [dependencies] 17 | # crates.io crates: 18 | proc-macro2.workspace = true 19 | quote.workspace = true 20 | syn.workspace = true 21 | -------------------------------------------------------------------------------- /serde_json_path_macros/src/internal/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Trevor Hilton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /serde_json_path_macros/src/internal/README.md: -------------------------------------------------------------------------------- 1 | # serde_json_path_macros_internal 2 | 3 | This is an internal crate used by the [`serde_json_path`][sjp] crate. 4 | 5 | [sjp]: https://crates.io/crates/serde_json_path -------------------------------------------------------------------------------- /serde_json_path_macros/src/internal/common/args.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Ident; 2 | use syn::{ 3 | parse::{Parse, ParseStream}, 4 | LitStr, Token, 5 | }; 6 | 7 | pub(crate) struct StrArg { 8 | pub(crate) value: LitStr, 9 | _p: std::marker::PhantomData, 10 | } 11 | 12 | impl Parse for StrArg { 13 | fn parse(input: ParseStream<'_>) -> syn::Result { 14 | let _ = input.parse::()?; 15 | let _ = input.parse::()?; 16 | let value = input.parse()?; 17 | Ok(Self { 18 | value, 19 | _p: std::marker::PhantomData, 20 | }) 21 | } 22 | } 23 | 24 | pub(crate) struct IdentArg { 25 | pub(crate) value: Ident, 26 | _p: std::marker::PhantomData, 27 | } 28 | 29 | impl Parse for IdentArg { 30 | fn parse(input: ParseStream) -> syn::Result { 31 | let _ = input.parse::()?; 32 | let _ = input.parse::()?; 33 | let value = input.parse()?; 34 | Ok(Self { 35 | value, 36 | _p: std::marker::PhantomData, 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /serde_json_path_macros/src/internal/common/define.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, TokenStream}; 2 | use quote::quote; 3 | use syn::{ItemFn, LitStr}; 4 | 5 | use crate::common::extract::FnArgument; 6 | 7 | use super::extract::{extract_components, Components}; 8 | 9 | pub(crate) struct Expanded { 10 | pub(crate) name_str: LitStr, 11 | pub(crate) validator: TokenStream, 12 | pub(crate) validator_name: Ident, 13 | pub(crate) evaluator: TokenStream, 14 | pub(crate) evaluator_name: Ident, 15 | pub(crate) result: TokenStream, 16 | pub(crate) core: TokenStream, 17 | } 18 | 19 | /// Expand the macro input to produce the common elements used in the `#[function]` and 20 | /// `#[register]` macros. 21 | pub(crate) fn expand(input: ItemFn, name_str: Option) -> Result { 22 | let ItemFn { 23 | attrs: _, 24 | vis: _, 25 | sig, 26 | block, 27 | } = input; 28 | 29 | let Components { 30 | name, 31 | generics, 32 | validator_name, 33 | evaluator_name, 34 | result, 35 | args, 36 | ret, 37 | inputs, 38 | } = match extract_components(sig) { 39 | Ok(fd) => fd, 40 | Err(e) => return Err(e.into_compile_error()), 41 | }; 42 | // Stringified name of the function: 43 | let name_str = name_str.unwrap_or_else(|| LitStr::new(name.to_string().as_str(), name.span())); 44 | // The number of arguments the function accepts: 45 | let args_len = args.len(); 46 | // Generate token streams for some needed types: 47 | let lazy = quote! { 48 | std::sync::LazyLock 49 | }; 50 | let core = quote! { 51 | ::serde_json_path_macros::serde_json_path_core::spec::functions 52 | }; 53 | let res = quote! { 54 | std::result::Result 55 | }; 56 | // Generate code for checking each individual argument in a query at parse time: 57 | let arg_checks = args.iter().enumerate().map(|(idx, arg)| { 58 | let FnArgument { ident: _, ty } = arg; 59 | quote! { 60 | match a[#idx].as_type_kind() { 61 | #res::Ok(tk) => { 62 | if !tk.converts_to(#ty::json_path_type()) { 63 | return #res::Err(#core::FunctionValidationError::MismatchTypeKind { 64 | name: String::from(#name_str), 65 | expected: #ty::json_path_type(), 66 | received: tk, 67 | position: #idx, 68 | }); 69 | } 70 | }, 71 | #res::Err(err) => return #res::Err(err) 72 | } 73 | } 74 | }); 75 | // Generate the validator function used at parse time to validate a function declaration: 76 | let validator = quote! { 77 | static #validator_name: #core::Validator = #lazy::new(|| { 78 | std::boxed::Box::new(|a: &[#core::FunctionExprArg]| { 79 | if a.len() != #args_len { 80 | return #res::Err(#core::FunctionValidationError::NumberOfArgsMismatch { 81 | expected: a.len(), 82 | received: #args_len, 83 | }); 84 | } 85 | #(#arg_checks)* 86 | Ok(()) 87 | }) 88 | }); 89 | }; 90 | // Generate the code to declare each individual argument for evaluation, at query time: 91 | let arg_declarations = args.iter().map(|arg| { 92 | let FnArgument { ident, ty } = arg; 93 | // validation should ensure unwrap is okay here: 94 | quote! { 95 | let #ident = #ty::try_from(v.pop_front().unwrap()).unwrap(); 96 | } 97 | }); 98 | // Produce the argument name identifiers: 99 | let arg_names = args.iter().map(|arg| { 100 | let FnArgument { ident, ty: _ } = arg; 101 | ident 102 | }); 103 | // Generate the evaluator function used to evaluate a function at query time: 104 | let evaluator = quote! { 105 | fn #name #generics (#inputs) #ret #block 106 | static #evaluator_name: #core::Evaluator = #lazy::new(|| { 107 | std::boxed::Box::new(|mut v: std::collections::VecDeque<#core::JsonPathValue>| { 108 | #(#arg_declarations)* 109 | return #name(#(#arg_names,)*).into() 110 | }) 111 | }); 112 | }; 113 | 114 | Ok(Expanded { 115 | name_str, 116 | validator, 117 | validator_name, 118 | evaluator, 119 | evaluator_name, 120 | result, 121 | core, 122 | }) 123 | } 124 | -------------------------------------------------------------------------------- /serde_json_path_macros/src/internal/common/extract.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | use proc_macro2::{Ident, TokenStream}; 4 | use quote::quote; 5 | use syn::{ 6 | punctuated::Punctuated, spanned::Spanned, token::Comma, Error, FnArg, Generics, Pat, PatType, 7 | Path, Result, ReturnType, Signature, Type, 8 | }; 9 | 10 | pub struct Components { 11 | pub name: Ident, 12 | pub generics: Generics, 13 | pub validator_name: Ident, 14 | pub evaluator_name: Ident, 15 | pub result: TokenStream, 16 | pub ret: ReturnType, 17 | pub inputs: Punctuated, 18 | pub args: VecDeque, 19 | } 20 | 21 | pub struct FnArgument { 22 | pub ident: Ident, 23 | pub ty: TokenStream, 24 | } 25 | 26 | fn extract_pat_ident(pat: &Pat) -> Option { 27 | if let Pat::Ident(ref pat_ident) = pat { 28 | Some(pat_ident.ident.to_owned()) 29 | } else { 30 | None 31 | } 32 | } 33 | 34 | fn extract_type_path(ty: &Type) -> Option<&Path> { 35 | match ty { 36 | Type::Path(ref typepath) if typepath.qself.is_none() => Some(&typepath.path), 37 | Type::Reference(ref typeref) => match *typeref.elem { 38 | Type::Path(ref typepath) if typepath.qself.is_none() => Some(&typepath.path), 39 | _ => None, 40 | }, 41 | _ => None, 42 | } 43 | } 44 | 45 | fn extract_json_path_type(p: &Path) -> Result { 46 | let p_seg = p 47 | .segments 48 | .last() 49 | .ok_or_else(|| Error::new(p.span(), "expected a type identifier"))?; 50 | let ts = match p_seg.ident.to_string().as_str() { 51 | "NodesType" => quote! { 52 | ::serde_json_path_macros::serde_json_path_core::spec::functions::NodesType 53 | }, 54 | "ValueType" => quote! { 55 | ::serde_json_path_macros::serde_json_path_core::spec::functions::ValueType 56 | }, 57 | "LogicalType" => quote! { 58 | ::serde_json_path_macros::serde_json_path_core::spec::functions::LogicalType 59 | }, 60 | other => { 61 | return Err(Error::new( 62 | p_seg.ident.span(), 63 | format!( 64 | "expected 'NodesType', 'ValueType', or 'LogicalType', got '{}'", 65 | other, 66 | ), 67 | )) 68 | } 69 | }; 70 | Ok(ts) 71 | } 72 | 73 | pub fn extract_components(input: Signature) -> Result { 74 | let name = input.ident.clone(); 75 | let generics = input.generics.clone(); 76 | let inputs = input.inputs.clone(); 77 | let ret = input.output.clone(); 78 | 79 | let result = match &ret { 80 | ReturnType::Default => { 81 | return Err(Error::new( 82 | input.span(), 83 | "function signature expected to have return type", 84 | )) 85 | } 86 | ReturnType::Type(_, ty) => { 87 | if let Some(path) = extract_type_path(ty.as_ref()) { 88 | extract_json_path_type(path)? 89 | } else { 90 | return Err(Error::new( 91 | ty.span(), 92 | "return type can only be one of the serde_json_path types: NodesType, \ 93 | ValueType, or LogicalType", 94 | )); 95 | } 96 | } 97 | }; 98 | 99 | let args: Result> = inputs 100 | .iter() 101 | .map(|i| match i { 102 | FnArg::Receiver(_) => Err(Error::new( 103 | inputs.span(), 104 | "receiver arguments like self, &self, or &mut self are not supported", 105 | )), 106 | FnArg::Typed(PatType { 107 | attrs: _, 108 | pat, 109 | colon_token: _, 110 | ty, 111 | }) => { 112 | let ident = if let Some(id) = extract_pat_ident(pat) { 113 | id 114 | } else { 115 | return Err(Error::new( 116 | pat.span(), 117 | "expected identifier in function argument", 118 | )); 119 | }; 120 | let ty = if let Some(path) = extract_type_path(ty) { 121 | extract_json_path_type(path)? 122 | } else { 123 | return Err(Error::new( 124 | ty.span(), 125 | "argument type can only be one of the serde_json_path types: NodesType, \ 126 | ValueType, or LogicalType", 127 | )); 128 | }; 129 | Ok(FnArgument { ident, ty }) 130 | } 131 | }) 132 | .collect(); 133 | 134 | let args = args?; 135 | let validator_name = Ident::new( 136 | format!("{name}_validator").to_uppercase().as_str(), 137 | name.span(), 138 | ); 139 | let evaluator_name = Ident::new( 140 | format!("{name}_evaluator").to_uppercase().as_str(), 141 | name.span(), 142 | ); 143 | 144 | Ok(Components { 145 | name, 146 | generics, 147 | result, 148 | inputs, 149 | ret, 150 | args, 151 | validator_name, 152 | evaluator_name, 153 | }) 154 | } 155 | -------------------------------------------------------------------------------- /serde_json_path_macros/src/internal/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod args; 2 | pub(crate) mod define; 3 | pub(crate) mod extract; 4 | -------------------------------------------------------------------------------- /serde_json_path_macros/src/internal/func/args.rs: -------------------------------------------------------------------------------- 1 | use syn::{parse::Parse, LitStr}; 2 | 3 | use crate::common::args::StrArg; 4 | 5 | #[derive(Default)] 6 | pub(crate) struct FunctionMacroArgs { 7 | pub(crate) name: Option, 8 | } 9 | 10 | impl Parse for FunctionMacroArgs { 11 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 12 | let mut args = Self::default(); 13 | while !input.is_empty() { 14 | let lookahead = input.lookahead1(); 15 | if lookahead.peek(kw::name) { 16 | if args.name.is_some() { 17 | return Err(input.error("expected only a single `name` argument")); 18 | } 19 | let name = input.parse::>()?.value; 20 | args.name = Some(name); 21 | } else { 22 | // TODO - may want to warn here when found a invalid arg - see how 23 | // tracing::instrument stores warnings and emits them later when generating the 24 | // expanded token stream. 25 | let _ = input.parse::(); 26 | } 27 | } 28 | Ok(args) 29 | } 30 | } 31 | 32 | mod kw { 33 | syn::custom_keyword!(name); 34 | } 35 | -------------------------------------------------------------------------------- /serde_json_path_macros/src/internal/func/define.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use syn::ItemFn; 4 | 5 | use crate::common::{self, define::Expanded}; 6 | 7 | use super::args::FunctionMacroArgs; 8 | 9 | pub(crate) fn expand(attrs: FunctionMacroArgs, input: ItemFn) -> TokenStream { 10 | let Expanded { 11 | name_str, 12 | validator, 13 | validator_name, 14 | evaluator, 15 | evaluator_name, 16 | result, 17 | core, 18 | } = match common::define::expand(input, attrs.name) { 19 | Ok(exp) => exp, 20 | Err(err) => return err.into(), 21 | }; 22 | 23 | let inventory = quote! { 24 | ::serde_json_path_macros::inventory 25 | }; 26 | 27 | TokenStream::from(quote! { 28 | #validator 29 | #evaluator 30 | #inventory::submit! { 31 | #core::Function::new( 32 | #name_str, 33 | #result::function_type(), 34 | &#evaluator_name, 35 | &#validator_name, 36 | ) 37 | } 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /serde_json_path_macros/src/internal/func/mod.rs: -------------------------------------------------------------------------------- 1 | mod args; 2 | mod define; 3 | 4 | pub(crate) use args::FunctionMacroArgs; 5 | pub(crate) use define::expand; 6 | -------------------------------------------------------------------------------- /serde_json_path_macros/src/internal/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use syn::{parse_macro_input, ItemFn}; 3 | 4 | mod common; 5 | mod func; 6 | mod reg; 7 | 8 | #[proc_macro_attribute] 9 | pub fn function(attr: TokenStream, item: TokenStream) -> TokenStream { 10 | let args = parse_macro_input!(attr as func::FunctionMacroArgs); 11 | let item_fn = parse_macro_input!(item as ItemFn); 12 | 13 | func::expand(args, item_fn) 14 | } 15 | 16 | #[proc_macro_attribute] 17 | pub fn register(attr: TokenStream, item: TokenStream) -> TokenStream { 18 | let args = parse_macro_input!(attr as reg::RegisterMacroArgs); 19 | let item_fn = parse_macro_input!(item as ItemFn); 20 | 21 | reg::expand(args, item_fn) 22 | } 23 | -------------------------------------------------------------------------------- /serde_json_path_macros/src/internal/reg/args.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Ident; 2 | use syn::{parse::Parse, LitStr}; 3 | 4 | use crate::common::args::{IdentArg, StrArg}; 5 | 6 | pub(crate) struct RegisterMacroArgs { 7 | pub(crate) name: Option, 8 | pub(crate) target: Ident, 9 | } 10 | 11 | #[derive(Default)] 12 | struct RegisterMacroArgsBuilder { 13 | name: Option, 14 | target: Option, 15 | } 16 | 17 | impl RegisterMacroArgsBuilder { 18 | fn build(self, input: &syn::parse::ParseStream) -> syn::Result { 19 | if let Some(target) = self.target { 20 | Ok(RegisterMacroArgs { 21 | name: self.name, 22 | target, 23 | }) 24 | } else { 25 | Err(input.error("missing `target` argument")) 26 | } 27 | } 28 | } 29 | 30 | impl Parse for RegisterMacroArgs { 31 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 32 | let mut builder = RegisterMacroArgsBuilder::default(); 33 | while !input.is_empty() { 34 | let lookahead = input.lookahead1(); 35 | if lookahead.peek(kw::name) { 36 | if builder.name.is_some() { 37 | return Err(input.error("expected only a single `name` argument")); 38 | } 39 | let name = input.parse::>()?.value; 40 | builder.name = Some(name); 41 | } else if lookahead.peek(kw::target) { 42 | if builder.target.is_some() { 43 | return Err(input.error("expected only a single `target` argument")); 44 | } 45 | let target = input.parse::>()?.value; 46 | builder.target = Some(target); 47 | } else { 48 | let _ = input.parse::(); 49 | } 50 | } 51 | builder.build(&input) 52 | } 53 | } 54 | 55 | mod kw { 56 | syn::custom_keyword!(name); 57 | syn::custom_keyword!(target); 58 | } 59 | -------------------------------------------------------------------------------- /serde_json_path_macros/src/internal/reg/define.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use syn::ItemFn; 4 | 5 | use crate::common::{self, define::Expanded}; 6 | 7 | use super::args::RegisterMacroArgs; 8 | 9 | pub(crate) fn expand(attrs: RegisterMacroArgs, input: ItemFn) -> TokenStream { 10 | let RegisterMacroArgs { name, target } = attrs; 11 | 12 | let Expanded { 13 | name_str, 14 | validator, 15 | validator_name, 16 | evaluator, 17 | evaluator_name, 18 | result, 19 | core, 20 | } = match common::define::expand(input, name) { 21 | Ok(exp) => exp, 22 | Err(err) => return err.into(), 23 | }; 24 | 25 | TokenStream::from(quote! { 26 | #validator 27 | #evaluator 28 | static #target: #core::Function = #core::Function::new( 29 | #name_str, 30 | #result::function_type(), 31 | &#evaluator_name, 32 | &#validator_name, 33 | ); 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /serde_json_path_macros/src/internal/reg/mod.rs: -------------------------------------------------------------------------------- 1 | mod args; 2 | mod define; 3 | 4 | pub(crate) use args::RegisterMacroArgs; 5 | pub(crate) use define::expand; 6 | -------------------------------------------------------------------------------- /serde_json_path_macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Macros for [`serde_json_path`] 2 | //! 3 | //! [`serde_json_path`]: https://crates.io/crates/serde_json_path 4 | 5 | #![warn( 6 | clippy::all, 7 | clippy::dbg_macro, 8 | clippy::todo, 9 | clippy::empty_enum, 10 | clippy::enum_glob_use, 11 | clippy::mem_forget, 12 | clippy::unused_self, 13 | clippy::filter_map_next, 14 | clippy::needless_continue, 15 | clippy::needless_borrow, 16 | clippy::match_wildcard_for_single_variants, 17 | clippy::if_let_mutex, 18 | unexpected_cfgs, 19 | clippy::await_holding_lock, 20 | clippy::match_on_vec_items, 21 | clippy::imprecise_flops, 22 | clippy::suboptimal_flops, 23 | clippy::lossy_float_literal, 24 | clippy::rest_pat_in_fully_bound_structs, 25 | clippy::fn_params_excessive_bools, 26 | clippy::exit, 27 | clippy::inefficient_to_string, 28 | clippy::linkedlist, 29 | clippy::macro_use_imports, 30 | clippy::option_option, 31 | clippy::verbose_file_reads, 32 | clippy::unnested_or_patterns, 33 | clippy::str_to_string, 34 | rust_2018_idioms, 35 | future_incompatible, 36 | nonstandard_style, 37 | missing_debug_implementations, 38 | missing_docs 39 | )] 40 | #![deny(unreachable_pub)] 41 | #![allow(elided_lifetimes_in_paths, clippy::type_complexity)] 42 | #![forbid(unsafe_code)] 43 | 44 | pub use serde_json_path_macros_internal::function; 45 | pub use serde_json_path_macros_internal::register; 46 | 47 | #[doc(hidden)] 48 | pub use ::inventory; 49 | 50 | #[doc(hidden)] 51 | pub use ::serde_json_path_core; 52 | --------------------------------------------------------------------------------