chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

View File

@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"0e5970e9cf33dd8020b51839819f7aada72b37e14fccdf32100729271caac9a1",".config/nextest.toml":"f8c74367dd0858d6fa4c2f40962fd7f62d4f30884117157ae8406b5d258ecafc",".github/workflows/ci.yml":"b98fd862ea935fd54b177b7d129ca4ad0408e0aff0798de134ee72a345549e32","CHANGELOG.md":"7c9856b89f0f42ac4a04accb9447c6c8198053c10d2d5562c173f4f23bed15b9","Cargo.lock":"b620a0c780a13f812376ff763d663bd66affa68e186cba657b231851cb8e5749","Cargo.toml":"672fd4859df6c6412c2622b5deea1bce0cd99410c0bfadaf69347f1e3c9e9926","Cargo.toml.orig":"530e08fa9fc16882c7fd9d188d666561dd4197c33fc741707c2848d543fa85f9","LICENSE":"9dac8496e8b5a36924b9a6cde889f7f592ac3e698bf1138d6f402b8d64561506","README.md":"2dc3565e728fa8ef9055dba292fe68737ee62a56a55a17212a5021a470367567","benches/equal.rs":"811b1333eff72a82953936955831efac86d947293f39c632a5811e8b3fb768dd","benches/regex.rs":"17a6fbf3d8251a52104ad078562a879368e07b5fde0b363c164ddcabf99c0340","examples/hello-world.rs":"a04a892f5f8ce746eea02b9a5502d5599567cc02f245263586c7f41b617e677c","src/jsonpath.rs":"e7f5843710a16aed801108615b6ce4c0d2c770b46bd0517222d785c2c8b39503","src/lib.rs":"56c2dbfc904de968160c1e183152f82b70ffdb9ffd6933160ba7b9278b1c4f22","src/parser/errors.rs":"d27181e4bdc5f084d770f7d9079601c1a556e0555793ba171085258e30497d1a","src/parser/grammar/json_path.pest":"15e67c7efbbf258b46b52c76f051a68d89b9d5ada13592df811e07f65c5c0d60","src/parser/macros.rs":"9c6c7571e6dbd22e06d73922b5d4ed902374e06d538cc5a8be089242ec434f34","src/parser/mod.rs":"a44be25d3bac9b5169ee18dd2d10451a613acc5cdb235b61dfc02152bb443db2","src/parser/model.rs":"b7c2d950dbf694be9e1335d5687ea6584e2dc0048b630d933042dc084d300d38","src/parser/parser.rs":"96fb9a71a5dcd1404d9f8e7e3ee7d1c08cf3b5fd655d7c09a6d0c0d496273f66","src/path/index.rs":"45d1deba1c157809c02c04c5721cc7728a9bb8af758e5c20c2e07919130bbb40","src/path/mod.rs":"4fc8534577f18dbac261f6bd2ff12a083c8f027a7d7068e932e43722e7427fda","src/path/top.rs":"9c256690e1042144704a9441b19269b51c9e6d2d3b5b5c21511a13c3128e953f"},"package":"0c00ae348f9f8fd2d09f82a98ca381c60df9e0820d8d79fce43e649b4dc3128b"}

View File

@@ -0,0 +1,6 @@
{
"git": {
"sha1": "0a69c56707d6e2bdbf7ee7041f980e591378d24c"
},
"path_in_vcs": ""
}

View File

@@ -0,0 +1,3 @@
[profile.ci]
failure-output = "immediate-final"
fail-fast = false

View File

@@ -0,0 +1,65 @@
name: Rust CI
on:
push:
branches: ["main"]
tags: ["v*"]
pull_request:
types: [opened, synchronize, reopened]
jobs:
rustfmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: rustfmt
- run: cargo fmt --all -- --check
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: clippy
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings
test:
runs-on: ubuntu-latest
env:
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@v3
- uses: taiki-e/install-action@v2
with:
tool: nextest
- run: cargo nextest run --all-features --profile ci
doc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
- run: cargo doc --all-features --no-deps
publish:
name: publish on crates.io
needs:
- rustfmt
- clippy
- test
- doc
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: cargo publish -p jsonpath-rust --token ${{ secrets.CRATES_IO_TOKEN }}

62
vendor/jsonpath-rust/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,62 @@
- **`0.1.0`**
- Initial implementation
- **`0.1.1`**
- Technical improvements
- **`0.1.2`**
- added a trait to obtain the result from value
- added a method to get the cloned as Value
- change the name of the general method\*
- **`0.1.4`**
- add an ability to use references instead of values
- fix some clippy issues
- **`0.1.5`**
- correct grammar for `$.[..]`
- **`0.1.6`**
- add logical OR and logical And to filters
- fix bugs with objects in filters
- add internal macros to generate path objects
- **`0.2.0`**
- add json path value as a result for the library
- add functions (size)
- change a logical operator `size` into function `size()`
- **`0.2.1`**
- changed the contract for length() function.
- **`0.2.2`**
- add ..\*
- **`0.2.5`**
- build for tags
- **`0.2.6`**
- make parser mod public
- **`0.3.0`**
- introduce the different behaviour for empty results and non-existing result
- **`0.3.2`**
- make jsonpath inst cloneable.
- **`0.3.3`**
- fix a bug with the logical operators
- **`0.3.4`**
- add a result as a path
- **`0.3.5`**
- add `!` negation operation in filters
- allow using () in filters
- **`0.5`**
- add config for jsonpath
- add an option to add a regex cache for boosting performance
- **`0.5.1`**
- add double quotes for the expressions (before it was only possible to use single quotes)
- add Debug on the JsonPathFinder
- **`0.6`**
- allow to reuse regex, that improves performance without needing an internal cache
- **`0.6.1`**
- Performance improvements
- Change the contract for the struct of errors
- **`0.7.0`**
- Bug fixes and api changes
- **`0.7.1`**
- add Display to JsonPath
- **`0.7.2`**
- add JsonLike trait
- **`0.7.3`**
- make some methods public
- **`0.7.5`**
- add reference and reference_mut methods

760
vendor/jsonpath-rust/Cargo.lock generated vendored Normal file
View File

@@ -0,0 +1,760 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "anes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anstyle"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "ciborium"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
dependencies = [
"ciborium-io",
"ciborium-ll",
"serde",
]
[[package]]
name = "ciborium-io"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
[[package]]
name = "ciborium-ll"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
dependencies = [
"ciborium-io",
"half",
]
[[package]]
name = "clap"
version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
dependencies = [
"anstyle",
"clap_lex",
]
[[package]]
name = "clap_lex"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "cpufeatures"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
dependencies = [
"libc",
]
[[package]]
name = "criterion"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
dependencies = [
"anes",
"cast",
"ciborium",
"clap",
"criterion-plot",
"is-terminal",
"itertools",
"num-traits",
"once_cell",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
dependencies = [
"cast",
"itertools",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "either"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
[[package]]
name = "generic-array"
version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "half"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
dependencies = [
"cfg-if",
"crunchy",
]
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "is-terminal"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
dependencies = [
"hermit-abi",
"libc",
"windows-sys",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "js-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "jsonpath-rust"
version = "0.7.5"
dependencies = [
"criterion",
"pest",
"pest_derive",
"regex",
"serde_json",
"thiserror 2.0.9",
]
[[package]]
name = "libc"
version = "0.2.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "oorandom"
version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "pest"
version = "2.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95"
dependencies = [
"memchr",
"thiserror 1.0.50",
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.91",
]
[[package]]
name = "pest_meta"
version = "2.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f"
dependencies = [
"once_cell",
"pest",
"sha2",
]
[[package]]
name = "plotters"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609"
[[package]]
name = "plotters-svg"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab"
dependencies = [
"plotters-backend",
]
[[package]]
name = "proc-macro2"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "regex"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "ryu"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "serde"
version = "1.0.156"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "314b5b092c0ade17c00142951e50ced110ec27cea304b1037c6969246c2469a4"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.156"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7e29c4601e36bcec74a223228dce795f4cd3616341a4af93520ca1a837c087d"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "serde_json"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "sha2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
dependencies = [
"thiserror-impl 1.0.50",
]
[[package]]
name = "thiserror"
version = "2.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc"
dependencies = [
"thiserror-impl 2.0.9",
]
[[package]]
name = "thiserror-impl"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.91",
]
[[package]]
name = "thiserror-impl"
version = "2.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.91",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "ucd-trie"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.91",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.91",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "web-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi-util"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
dependencies = [
"windows-sys",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"

59
vendor/jsonpath-rust/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,59 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "jsonpath-rust"
version = "0.7.5"
authors = ["BorisZhguchev <zhguchev@gmail.com>"]
description = "The library provides the basic functionality to find the set of the data according to the filtering query."
homepage = "https://github.com/besok/jsonpath-rust"
readme = "README.md"
keywords = [
"json",
"json-path",
"jsonpath",
"jsonpath-rust",
"xpath",
]
categories = [
"development-tools",
"parsing",
"text-processing",
]
license = "MIT"
repository = "https://github.com/besok/jsonpath-rust"
[[bench]]
name = "regex"
harness = false
[[bench]]
name = "equal"
harness = false
[dependencies.pest]
version = "2.0"
[dependencies.pest_derive]
version = "2.0"
[dependencies.regex]
version = "1"
[dependencies.serde_json]
version = "1.0"
[dependencies.thiserror]
version = "2.0.9"
[dev-dependencies.criterion]
version = "0.5.1"

21
vendor/jsonpath-rust/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) [2021] [Boris Zhguchev]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

365
vendor/jsonpath-rust/README.md vendored Normal file
View File

@@ -0,0 +1,365 @@
# jsonpath-rust
[![Crates.io](https://img.shields.io/crates/v/jsonpath-rust)](https://crates.io/crates/jsonpath-rust)
[![docs.rs](https://img.shields.io/docsrs/jsonpath-rust)](https://docs.rs/jsonpath-rust/latest/jsonpath_rust)
[![Rust CI](https://github.com/besok/jsonpath-rust/actions/workflows/ci.yml/badge.svg)](https://github.com/besok/jsonpath-rust/actions/workflows/ci.yml)
The library provides the basic functionality to find the set of the data according to the filtering query. The idea
comes from XPath for XML structures. The details can be found [there](https://goessner.net/articles/JsonPath/)
Therefore JsonPath is a query language for JSON, similar to XPath for XML. The JsonPath query is a set of assertions to
specify the JSON fields that need to be verified.
Python bindings ([jsonpath-rust-bindings](https://github.com/night-crawler/jsonpath-rust-bindings)) are available on
pypi:
```bash
pip install jsonpath-rust-bindings
```
## Simple examples
Let's suppose we have a following json:
```json
{
"shop": {
"orders": [
{
"id": 1,
"active": true
},
{
"id": 2
},
{
"id": 3
},
{
"id": 4,
"active": true
}
]
}
}
```
And we pursue to find all orders id having the field 'active'. We can construct the jsonpath instance like
that ```$.shop.orders[?(@.active)].id``` and get the result ``` [1,4] ```
## The jsonpath description
### Functions
#### Size
A function `length()` transforms the output of the filtered expression into a size of this element
It works with arrays, therefore it returns a length of a given array, otherwise null.
`$.some_field.length()`
**To use it** for objects, the operator `[*]` can be used.
`$.object.[*].length()`
### Operators
| Operator | Description | Where to use |
|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
| `$` | Pointer to the root of the json. | It is gently advising to start every jsonpath from the root. Also, inside the filters to point out that the path is starting from the root. |
| `@` | Pointer to the current element inside the filter operations. | It is used inside the filter operations to iterate the collection. |
| `*` or `[*]` | Wildcard. It brings to the list all objects and elements regardless their names. | It is analogue a flatmap operation. |
| `<..>` | Descent operation. It brings to the list all objects, children of that objects and etc | It is analogue a flatmap operation. |
| `.<name>` or `.['<name>']` | the key pointing to the field of the object | It is used to obtain the specific field. |
| `['<name>' (, '<name>')]` | the list of keys | the same usage as for a single key but for list |
| `[<number>]` | the filter getting the element by its index. | |
| `[<number> (, <number>)]` | the list if elements of array according to their indexes representing these numbers. | |
| `[<start>:<end>:<step>]` | slice operator to get a list of element operating with their indexes. By default step = 1, start = 0, end = array len. The elements can be omitted ```[:]``` | |
| `[?(<expression>)]` | the logical expression to filter elements in the list. | It is used with arrays preliminary. |
### Filter expressions
The expressions appear in the filter operator like that `[?(@.len > 0)]`. The expression in general consists of the
following elements:
- Left and right operands, that is ,in turn, can be a static value,representing as a primitive type like a number,
string value `'value'`, array of them or another json path instance.
- Expression sign, denoting what action can be performed
| Expression sign | Description | Where to use |
|-----------------|--------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
| `!` | Not | To negate the expression |
| `==` | Equal | To compare numbers or string literals |
| `!=` | Unequal | To compare numbers or string literals in opposite way to equals |
| `<` | Less | To compare numbers |
| `>` | Greater | To compare numbers |
| `<=` | Less or equal | To compare numbers |
| `>=` | Greater or equal | To compare numbers |
| `~=` | Regular expression | To find the incoming right side in the left side. |
| `in` | Find left element in the list of right elements. | |
| `nin` | The same one as saying above but carrying the opposite sense. | |
| `size` | The size of array on the left size should be corresponded to the number on the right side. | |
| `noneOf` | The left size has no intersection with right | |
| `anyOf` | The left size has at least one intersection with right | |
| `subsetOf` | The left is a subset of the right side | |
| `?` | Exists operator. | The operator checks the existence of the field depicted on the left side like that `[?(@.key.isActive)]` |
Filter expressions can be chained using `||` and `&&` (logical or and logical and correspondingly) in the following way:
```json
{
"key": [
{
"city": "London",
"capital": true,
"size": "big"
},
{
"city": "Berlin",
"capital": true,
"size": "big"
},
{
"city": "Tokyo",
"capital": true,
"size": "big"
},
{
"city": "Moscow",
"capital": true,
"size": "big"
},
{
"city": "Athlon",
"capital": false,
"size": "small"
},
{
"city": "Dortmund",
"capital": false,
"size": "big"
},
{
"city": "Dublin",
"capital": true,
"size": "small"
}
]
}
```
The path ``` $.key[?(@.capital == false || @size == 'small')].city ``` will give the following result:
```json
[
"Athlon",
"Dublin",
"Dortmund"
]
```
And the path ``` $.key[?(@.capital == false && @size != 'small')].city ``` ,in its turn, will give the following result:
```json
[
"Dortmund"
]
```
By default, the operators have the different priority so `&&` has a higher priority so to change it the brackets can be
used.
``` $.[?((@.f == 0 || @.f == 1) && ($.x == 15))].city ```
## Examples
Given the json
```json
{
"store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
},
"expensive": 10
}
```
| JsonPath | Result |
|--------------------------------------|:-------------------------------------------------------------|
| `$.store.book[*].author` | The authors of all books |
| `$..book[?(@.isbn)]` | All books with an ISBN number |
| `$.store.*` | All things, both books and bicycles |
| `$..author` | All authors |
| `$.store..price` | The price of everything |
| `$..book[2]` | The third book |
| `$..book[-2]` | The second to last book |
| `$..book[0,1]` | The first two books |
| `$..book[:2]` | All books from index 0 (inclusive) until index 2 (exclusive) |
| `$..book[1:2]` | All books from index 1 (inclusive) until index 2 (exclusive) |
| `$..book[-2:]` | Last two books |
| `$..book[2:]` | Book number two from tail |
| `$.store.book[?(@.price < 10)]` | All books in store cheaper than 10 |
| `$..book[?(@.price <= $.expensive)]` | All books in store that are not "expensive" |
| `$..book[?(@.author ~= '(?i)REES')]` | All books matching regex (ignore case) |
| `$..*` | Give me every thing |
## Library Usage
The library intends to provide the basic functionality for ability to find the slices of data using the syntax, saying
above. The dependency can be found as following:
``` jsonpath-rust = *```
The basic example is the following one:
The library returns a `json path value` as a result.
This is enum type which represents:
- `Slice` - a point to the passed original json
- `NewValue` - a new json data that has been generated during the path( for instance length operator)
- `NoValue` - indicates there is no match between given json and jsonpath in the most cases due to absent fields or
inconsistent data.
To extract data there are two methods, provided on the `value`:
```rust
let v:JsonPathValue<Value> =...
v.to_data();
v.slice_or( & some_dafault_value)
```
### Find
there are 4 different functions to find data inside a `value`.
All take references, to increase reusability. Especially json parsing and jsonpath parsing can take significant time,
compared to a simple find.
The methods `find`, `find_as_path`, `find_slice` and `find_slice_ptr` take the same inputs, but handle them differently
depending on your usecase. They are further described in
the [docs](https://docs.rs/jsonpath-rust/latest/jsonpath_rust/enum.JsonPath.html#implementations).
```rust
use jsonpath_rust::{JsonPath, JsonPathValue};
use serde_json::json;
use std::str::FromStr;
fn main() {
let data = json!({"first":{"second":[{"active":1},{"passive":1}]}});
let path = JsonPath::from_str("$.first.second[?(@.active)]").unwrap();
let slice_of_data = path.find_slice(&data);
let expected_value = json!({"active":1});
let expected_path = "$.['first'].['second'][0]".to_string();
assert_eq!(
slice_of_data,
vec![JsonPathValue::Slice(&expected_value, expected_path)]
);
}
```
### The structure
The internal structure of the `JsonPath` can be found here:
https://docs.rs/jsonpath-rust/latest/jsonpath_rust/parser/model/enum.JsonPath.html
The internal structure of the `JsonPathIndex` can be found here:
https://docs.rs/jsonpath-rust/latest/jsonpath_rust/parser/model/enum.JsonPathIndex.html
### JsonLike
The library provides a trait `JsonLike` that can be implemented for any type.
This allows you to use the `JsonPath` methods on your own types.
### Update the JsonLike structure by path
The library does not provide the functionality to update the json structure in the query itself.
Instead, the library provides the ability to update the json structure by the path.
Thus, the user needs to find a path for the `JsonLike` structure and update it manually.
There are two methods in the `JsonLike` trait:
- `reference_mut` - returns a mutable reference to the element by the path
- `reference` - returns a reference to the element by the path
They accept a `JsonPath` instance and return a `Option<&mut Value>` or `Option<&Value>` respectively.
The path is supported with the limited elements namely only the elements with the direct access:
- root
- field
- index
The path can be obtained manually or `find_as_path` method can be used.
```rust
#[test]
fn update_by_path_test() -> Result<(), JsonPathParserError> {
let mut json = json!([
{"verb": "RUN","distance":[1]},
{"verb": "TEST"},
{"verb": "DO NOT RUN"}
]);
let path: Box<JsonPath> = Box::from(JsonPath::try_from("$.[?(@.verb == 'RUN')]")?);
let elem = path
.find_as_path(&json)
.get(0)
.cloned()
.ok_or(JsonPathParserError::InvalidJsonPath("".to_string()))?;
if let Some(v) = json
.reference_mut(elem)?
.and_then(|v| v.as_object_mut())
.and_then(|v| v.get_mut("distance"))
.and_then(|v| v.as_array_mut())
{
v.push(json!(2))
}
assert_eq!(
json,
json!([
{"verb": "RUN","distance":[1,2]},
{"verb": "TEST"},
{"verb": "DO NOT RUN"}
])
);
Ok(())
}
```
## How to contribute
TBD
## How to update version
- update files
- commit them
- add tag `git tag -a v<Version> -m "message"`
- git push origin <tag_name>

41
vendor/jsonpath-rust/benches/equal.rs vendored Normal file
View File

@@ -0,0 +1,41 @@
use criterion::{criterion_group, criterion_main, Criterion};
use jsonpath_rust::{JsonPath, JsonPathQuery};
use serde_json::json;
use std::str::FromStr;
struct SearchData {
json: serde_json::Value,
path: JsonPath,
}
const PATH: &str = "$.[?(@.author == 'abcd(Rees)')]";
fn equal_perf_test_with_reuse(cfg: &SearchData) {
let _v = cfg.path.find(&cfg.json);
}
fn equal_perf_test_without_reuse() {
let json = Box::new(json!({
"author":"abcd(Rees)",
}));
let _v = json.path(PATH).expect("the path is correct");
}
pub fn criterion_benchmark(c: &mut Criterion) {
let data = SearchData {
json: json!({
"author":"abcd(Rees)",
}),
path: JsonPath::from_str(PATH).unwrap(),
};
c.bench_function("equal bench with reuse", |b| {
b.iter(|| equal_perf_test_with_reuse(&data))
});
c.bench_function("equal bench without reuse", |b| {
b.iter(equal_perf_test_without_reuse)
});
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

46
vendor/jsonpath-rust/benches/regex.rs vendored Normal file
View File

@@ -0,0 +1,46 @@
use criterion::{criterion_group, criterion_main, Criterion};
use jsonpath_rust::{JsonPath, JsonPathQuery};
use serde_json::{json, Value};
use std::str::FromStr;
struct SearchData {
json: serde_json::Value,
path: JsonPath,
}
const PATH: &str = "$.[?(@.author ~= '.*(?i)d\\(Rees\\)')]";
fn regex_perf_test_with_reuse(cfg: &SearchData) {
let _v = cfg.path.find(&cfg.json);
}
fn regex_perf_test_without_reuse() {
let json = Box::new(json!({
"author":"abcd(Rees)",
}));
let _v = json.path(PATH).expect("the path is correct");
}
fn json_path_compiling() {
let _v = JsonPath::<Value>::from_str(PATH).unwrap();
}
pub fn criterion_benchmark(c: &mut Criterion) {
let data = SearchData {
json: json!({
"author":"abcd(Rees)",
}),
path: JsonPath::from_str(PATH).unwrap(),
};
c.bench_function("regex bench with reuse", |b| {
b.iter(|| regex_perf_test_with_reuse(&data))
});
c.bench_function("regex bench without reuse", |b| {
b.iter(regex_perf_test_without_reuse)
});
c.bench_function("JsonPath generation", |b| b.iter(json_path_compiling));
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@@ -0,0 +1,12 @@
use jsonpath_rust::JsonPath;
use serde_json::json;
fn main() {
let data = json!({
"Hello":"World",
"Good":"Bye",
});
let path = JsonPath::try_from("$.Hello").unwrap();
let search_result = path.find(&data);
println!("Hello, {}", search_result);
}

936
vendor/jsonpath-rust/src/jsonpath.rs vendored Normal file
View File

@@ -0,0 +1,936 @@
use crate::path::json_path_instance;
use crate::path::JsonLike;
use crate::JsonPathValue;
use crate::JsonPtr;
use crate::{JsonPath, JsonPathStr};
impl<T> JsonPath<T>
where
T: JsonLike,
{
/// finds a slice of data in the set json.
/// The result is a vector of references to the incoming structure.
///
/// In case, if there is no match [`Self::find_slice`] will return vec!<[`JsonPathValue::NoValue`]>.
///
/// ## Example
/// ```rust
/// use jsonpath_rust::{JsonPath, JsonPathValue};
/// use serde_json::json;
/// # use std::str::FromStr;
///
/// let data = json!({"first":{"second":[{"active":1},{"passive":1}]}});
/// let path = JsonPath::try_from("$.first.second[?(@.active)]").unwrap();
/// let slice_of_data = path.find_slice(&data);
///
/// let expected_value = json!({"active":1});
/// let expected_path = "$.['first'].['second'][0]".to_string();
///
/// assert_eq!(
/// slice_of_data,
/// vec![JsonPathValue::Slice(&expected_value, expected_path)]
/// );
/// ```
pub fn find_slice<'a>(&'a self, json: &'a T) -> Vec<JsonPathValue<'a, T>> {
use crate::path::Path;
let instance = json_path_instance(self, json);
let res = instance.find(JsonPathValue::from_root(json));
let has_v: Vec<JsonPathValue<'_, T>> = res.into_iter().filter(|v| v.has_value()).collect();
if has_v.is_empty() {
vec![JsonPathValue::NoValue]
} else {
has_v
}
}
/// like [`Self::find_slice`] but returns a vector of [`JsonPtr`], which has no [`JsonPathValue::NoValue`].
/// if there is no match, it will return an empty vector
pub fn find_slice_ptr<'a>(&'a self, json: &'a T) -> Vec<JsonPtr<'a, T>> {
use crate::path::Path;
json_path_instance(self, json)
.find(JsonPathValue::from_root(json))
.into_iter()
.filter(|v| v.has_value())
.map(|v| match v {
JsonPathValue::Slice(v, _) => JsonPtr::Slice(v),
JsonPathValue::NewValue(v) => JsonPtr::NewValue(v),
JsonPathValue::NoValue => unreachable!("has_value was already checked"),
})
.collect()
}
/// finds a slice of data and wrap it with Value::Array by cloning the data.
/// Returns either an array of elements or Json::Null if the match is incorrect.
///
/// In case, if there is no match `find` will return `json!(null)`.
///
/// ## Example
/// ```rust
/// use jsonpath_rust::{JsonPath, JsonPathValue};
/// use serde_json::{Value, json};
/// # use std::str::FromStr;
///
/// let data = json!({"first":{"second":[{"active":1},{"passive":1}]}});
/// let path = JsonPath::try_from("$.first.second[?(@.active)]").unwrap();
/// let cloned_data = path.find(&data);
///
/// assert_eq!(cloned_data, Value::Array(vec![json!({"active":1})]));
/// ```
pub fn find(&self, json: &T) -> T {
let slice = self.find_slice(json);
if !slice.is_empty() {
if JsonPathValue::only_no_value(&slice) {
T::null()
} else {
T::array(
slice
.into_iter()
.filter(|v| v.has_value())
.map(|v| v.to_data())
.collect(),
)
}
} else {
T::array(vec![])
}
}
/// finds a path describing the value, instead of the value itself.
/// If the values has been obtained by moving the data out of the initial json the path is absent.
///
/// ** If the value has been modified during the search, there is no way to find a path of a new value.
/// It can happen if we try to find a length() of array, for in stance.**
///
/// ## Example
/// ```rust
/// use jsonpath_rust::{JsonPathStr, JsonPath, JsonPathValue};
/// use serde_json::{Value, json};
/// # use std::str::FromStr;
///
/// let data = json!({"first":{"second":[{"active":1},{"passive":1}]}});
/// let path = JsonPath::try_from("$.first.second[?(@.active)]").unwrap();
/// let slice_of_data: Vec<JsonPathStr> = path.find_as_path(&data);
///
/// let expected_path = "$.['first'].['second'][0]".to_string();
/// assert_eq!(slice_of_data, vec![expected_path]);
/// ```
pub fn find_as_path(&self, json: &T) -> Vec<JsonPathStr> {
self.find_slice(json)
.into_iter()
.flat_map(|v| v.to_path())
.collect()
}
}
#[cfg(test)]
mod tests {
use crate::path::JsonLike;
use crate::JsonPathQuery;
use crate::JsonPathValue::{NoValue, Slice};
use crate::{jp_v, JsonPath, JsonPathParserError, JsonPathValue};
use serde_json::{json, Value};
use std::ops::Deref;
fn test(json: &str, path: &str, expected: Vec<JsonPathValue<Value>>) {
let json: Value = match serde_json::from_str(json) {
Ok(json) => json,
Err(e) => panic!("error while parsing json: {}", e),
};
let path = match JsonPath::try_from(path) {
Ok(path) => path,
Err(e) => panic!("error while parsing jsonpath: {}", e),
};
assert_eq!(path.find_slice(&json), expected)
}
fn template_json<'a>() -> &'a str {
r#" {"store": { "book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
},
"array":[0,1,2,3,4,5,6,7,8,9],
"orders":[
{
"ref":[1,2,3],
"id":1,
"filled": true
},
{
"ref":[4,5,6],
"id":2,
"filled": false
},
{
"ref":[7,8,9],
"id":3,
"filled": null
}
],
"expensive": 10 }"#
}
#[test]
fn simple_test() {
let j1 = json!(2);
test("[1,2,3]", "$[1]", jp_v![&j1;"$[1]",]);
}
#[test]
fn root_test() {
let js = serde_json::from_str(template_json()).unwrap();
test(template_json(), "$", jp_v![&js;"$",]);
}
#[test]
fn descent_test() {
let v1 = json!("reference");
let v2 = json!("fiction");
test(
template_json(),
"$..category",
jp_v![
&v1;"$.['store'].['book'][0].['category']",
&v2;"$.['store'].['book'][1].['category']",
&v2;"$.['store'].['book'][2].['category']",
&v2;"$.['store'].['book'][3].['category']",],
);
let js1 = json!(19.95);
let js2 = json!(8.95);
let js3 = json!(12.99);
let js4 = json!(8.99);
let js5 = json!(22.99);
test(
template_json(),
"$.store..price",
jp_v![
&js1;"$.['store'].['bicycle'].['price']",
&js2;"$.['store'].['book'][0].['price']",
&js3;"$.['store'].['book'][1].['price']",
&js4;"$.['store'].['book'][2].['price']",
&js5;"$.['store'].['book'][3].['price']",
],
);
let js1 = json!("Nigel Rees");
let js2 = json!("Evelyn Waugh");
let js3 = json!("Herman Melville");
let js4 = json!("J. R. R. Tolkien");
test(
template_json(),
"$..author",
jp_v![
&js1;"$.['store'].['book'][0].['author']",
&js2;"$.['store'].['book'][1].['author']",
&js3;"$.['store'].['book'][2].['author']",
&js4;"$.['store'].['book'][3].['author']",],
);
}
#[test]
fn wildcard_test() {
let js1 = json!("reference");
let js2 = json!("fiction");
test(
template_json(),
"$..book.[*].category",
jp_v![
&js1;"$.['store'].['book'][0].['category']",
&js2;"$.['store'].['book'][1].['category']",
&js2;"$.['store'].['book'][2].['category']",
&js2;"$.['store'].['book'][3].['category']",],
);
let js1 = json!("Nigel Rees");
let js2 = json!("Evelyn Waugh");
let js3 = json!("Herman Melville");
let js4 = json!("J. R. R. Tolkien");
test(
template_json(),
"$.store.book[*].author",
jp_v![
&js1;"$.['store'].['book'][0].['author']",
&js2;"$.['store'].['book'][1].['author']",
&js3;"$.['store'].['book'][2].['author']",
&js4;"$.['store'].['book'][3].['author']",],
);
}
#[test]
fn descendent_wildcard_test() {
let js1 = json!("Moby Dick");
let js2 = json!("The Lord of the Rings");
test(
template_json(),
"$..*.[?(@.isbn)].title",
jp_v![
&js1;"$.['store'].['book'][2].['title']",
&js2;"$.['store'].['book'][3].['title']",
&js1;"$.['store'].['book'][2].['title']",
&js2;"$.['store'].['book'][3].['title']"],
);
}
#[test]
fn field_test() {
let value = json!({"active":1});
test(
r#"{"field":{"field":[{"active":1},{"passive":1}]}}"#,
"$.field.field[?(@.active)]",
jp_v![&value;"$.['field'].['field'][0]",],
);
}
#[test]
fn index_index_test() {
let value = json!("0-553-21311-3");
test(
template_json(),
"$..book[2].isbn",
jp_v![&value;"$.['store'].['book'][2].['isbn']",],
);
}
#[test]
fn index_unit_index_test() {
let value = json!("0-553-21311-3");
test(
template_json(),
"$..book[2,4].isbn",
jp_v![&value;"$.['store'].['book'][2].['isbn']",],
);
let value1 = json!("0-395-19395-8");
test(
template_json(),
"$..book[2,3].isbn",
jp_v![&value;"$.['store'].['book'][2].['isbn']", &value1;"$.['store'].['book'][3].['isbn']",],
);
}
#[test]
fn index_unit_keys_test() {
let js1 = json!("Moby Dick");
let js2 = json!(8.99);
let js3 = json!("The Lord of the Rings");
let js4 = json!(22.99);
test(
template_json(),
"$..book[2,3]['title','price']",
jp_v![
&js1;"$.['store'].['book'][2].['title']",
&js2;"$.['store'].['book'][2].['price']",
&js3;"$.['store'].['book'][3].['title']",
&js4;"$.['store'].['book'][3].['price']",],
);
}
#[test]
fn index_slice_test() {
let i0 = "$.['array'][0]";
let i1 = "$.['array'][1]";
let i2 = "$.['array'][2]";
let i3 = "$.['array'][3]";
let i4 = "$.['array'][4]";
let i5 = "$.['array'][5]";
let i6 = "$.['array'][6]";
let i7 = "$.['array'][7]";
let i8 = "$.['array'][8]";
let i9 = "$.['array'][9]";
let j0 = json!(0);
let j1 = json!(1);
let j2 = json!(2);
let j3 = json!(3);
let j4 = json!(4);
let j5 = json!(5);
let j6 = json!(6);
let j7 = json!(7);
let j8 = json!(8);
let j9 = json!(9);
test(
template_json(),
"$.array[:]",
jp_v![
&j0;&i0,
&j1;&i1,
&j2;&i2,
&j3;&i3,
&j4;&i4,
&j5;&i5,
&j6;&i6,
&j7;&i7,
&j8;&i8,
&j9;&i9,],
);
test(template_json(), "$.array[1:4:2]", jp_v![&j1;&i1, &j3;&i3,]);
test(
template_json(),
"$.array[::3]",
jp_v![&j0;&i0, &j3;&i3, &j6;&i6, &j9;&i9,],
);
test(template_json(), "$.array[-1:]", jp_v![&j9;&i9,]);
test(template_json(), "$.array[-2:-1]", jp_v![&j8;&i8,]);
}
#[test]
fn index_filter_test() {
let moby = json!("Moby Dick");
let rings = json!("The Lord of the Rings");
test(
template_json(),
"$..book[?(@.isbn)].title",
jp_v![
&moby;"$.['store'].['book'][2].['title']",
&rings;"$.['store'].['book'][3].['title']",],
);
let sword = json!("Sword of Honour");
test(
template_json(),
"$..book[?(@.price != 8.95)].title",
jp_v![
&sword;"$.['store'].['book'][1].['title']",
&moby;"$.['store'].['book'][2].['title']",
&rings;"$.['store'].['book'][3].['title']",],
);
let sayings = json!("Sayings of the Century");
test(
template_json(),
"$..book[?(@.price == 8.95)].title",
jp_v![&sayings;"$.['store'].['book'][0].['title']",],
);
let js895 = json!(8.95);
test(
template_json(),
"$..book[?(@.author ~= '.*Rees')].price",
jp_v![&js895;"$.['store'].['book'][0].['price']",],
);
let js12 = json!(12.99);
let js899 = json!(8.99);
let js2299 = json!(22.99);
test(
template_json(),
"$..book[?(@.price >= 8.99)].price",
jp_v![
&js12;"$.['store'].['book'][1].['price']",
&js899;"$.['store'].['book'][2].['price']",
&js2299;"$.['store'].['book'][3].['price']",
],
);
test(
template_json(),
"$..book[?(@.price > 8.99)].price",
jp_v![
&js12;"$.['store'].['book'][1].['price']",
&js2299;"$.['store'].['book'][3].['price']",],
);
test(
template_json(),
"$..book[?(@.price < 8.99)].price",
jp_v![&js895;"$.['store'].['book'][0].['price']",],
);
test(
template_json(),
"$..book[?(@.price <= 8.99)].price",
jp_v![
&js895;"$.['store'].['book'][0].['price']",
&js899;"$.['store'].['book'][2].['price']",
],
);
test(
template_json(),
"$..book[?(@.price <= $.expensive)].price",
jp_v![
&js895;"$.['store'].['book'][0].['price']",
&js899;"$.['store'].['book'][2].['price']",
],
);
test(
template_json(),
"$..book[?(@.price >= $.expensive)].price",
jp_v![
&js12;"$.['store'].['book'][1].['price']",
&js2299;"$.['store'].['book'][3].['price']",
],
);
test(
template_json(),
"$..book[?(@.title in ['Moby Dick','Shmoby Dick','Big Dick','Dicks'])].price",
jp_v![&js899;"$.['store'].['book'][2].['price']",],
);
test(
template_json(),
"$..book[?(@.title nin ['Moby Dick','Shmoby Dick','Big Dick','Dicks'])].title",
jp_v![
&sayings;"$.['store'].['book'][0].['title']",
&sword;"$.['store'].['book'][1].['title']",
&rings;"$.['store'].['book'][3].['title']",],
);
test(
template_json(),
"$..book[?(@.author size 10)].title",
jp_v![&sayings;"$.['store'].['book'][0].['title']",],
);
let filled_true = json!(1);
test(
template_json(),
"$.orders[?(@.filled == true)].id",
jp_v![&filled_true;"$.['orders'][0].['id']",],
);
let filled_null = json!(3);
test(
template_json(),
"$.orders[?(@.filled == null)].id",
jp_v![&filled_null;"$.['orders'][2].['id']",],
);
}
#[test]
fn index_filter_sets_test() {
let j1 = json!(1);
test(
template_json(),
"$.orders[?(@.ref subsetOf [1,2,3,4])].id",
jp_v![&j1;"$.['orders'][0].['id']",],
);
let j2 = json!(2);
test(
template_json(),
"$.orders[?(@.ref anyOf [1,4])].id",
jp_v![&j1;"$.['orders'][0].['id']", &j2;"$.['orders'][1].['id']",],
);
let j3 = json!(3);
test(
template_json(),
"$.orders[?(@.ref noneOf [3,6])].id",
jp_v![&j3;"$.['orders'][2].['id']",],
);
}
#[test]
fn query_test() {
let json: Box<Value> = serde_json::from_str(template_json()).expect("to get json");
let v = json
.path("$..book[?(@.author size 10)].title")
.expect("the path is correct");
assert_eq!(v, json!(["Sayings of the Century"]));
let json: Value = serde_json::from_str(template_json()).expect("to get json");
let path = &json
.path("$..book[?(@.author size 10)].title")
.expect("the path is correct");
assert_eq!(path, &json!(["Sayings of the Century"]));
}
#[test]
fn find_slice_test() {
let json: Box<Value> = serde_json::from_str(template_json()).expect("to get json");
let path: Box<JsonPath<Value>> = Box::from(
JsonPath::try_from("$..book[?(@.author size 10)].title").expect("the path is correct"),
);
let v = path.find_slice(&json);
let js = json!("Sayings of the Century");
assert_eq!(v, jp_v![&js;"$.['store'].['book'][0].['title']",]);
}
#[test]
fn find_in_array_test() {
let json: Box<Value> = Box::new(json!([{"verb": "TEST"}, {"verb": "RUN"}]));
let path: Box<JsonPath<Value>> =
Box::from(JsonPath::try_from("$.[?(@.verb == 'TEST')]").expect("the path is correct"));
let v = path.find_slice(&json);
let js = json!({"verb":"TEST"});
assert_eq!(v, jp_v![&js;"$[0]",]);
}
#[test]
fn length_test() {
let json: Box<Value> =
Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}]));
let path: Box<JsonPath<Value>> = Box::from(
JsonPath::try_from("$.[?(@.verb == 'TEST')].length()").expect("the path is correct"),
);
let v = path.find(&json);
let js = json!([2]);
assert_eq!(v, js);
let json: Box<Value> =
Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}]));
let path: Box<JsonPath<Value>> =
Box::from(JsonPath::try_from("$.length()").expect("the path is correct"));
assert_eq!(path.find(&json), json!([3]));
// length of search following the wildcard returns correct result
let json: Box<Value> =
Box::new(json!([{"verb": "TEST"},{"verb": "TEST","x":3}, {"verb": "RUN"}]));
let path: Box<JsonPath<Value>> = Box::from(
JsonPath::try_from("$.[?(@.verb == 'TEST')].[*].length()")
.expect("the path is correct"),
);
assert_eq!(path.find(&json), json!([3]));
// length of object returns 0
let json: Box<Value> = Box::new(json!({"verb": "TEST"}));
let path: Box<JsonPath<Value>> =
Box::from(JsonPath::try_from("$.length()").expect("the path is correct"));
assert_eq!(path.find(&json), Value::Null);
// length of integer returns null
let json: Box<Value> = Box::new(json!(1));
let path: Box<JsonPath<Value>> =
Box::from(JsonPath::try_from("$.length()").expect("the path is correct"));
assert_eq!(path.find(&json), Value::Null);
// length of array returns correct result
let json: Box<Value> = Box::new(json!([[1], [2], [3]]));
let path: Box<JsonPath<Value>> =
Box::from(JsonPath::try_from("$.length()").expect("the path is correct"));
assert_eq!(path.find(&json), json!([3]));
// path does not exist returns length null
let json: Box<Value> =
Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}]));
let path: Box<JsonPath<Value>> =
Box::from(JsonPath::try_from("$.not.exist.length()").expect("the path is correct"));
assert_eq!(path.find(&json), Value::Null);
// seraching one value returns correct length
let json: Box<Value> =
Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}]));
let path: Box<JsonPath<Value>> = Box::from(
JsonPath::try_from("$.[?(@.verb == 'RUN')].length()").expect("the path is correct"),
);
let v = path.find(&json);
let js = json!([1]);
assert_eq!(v, js);
// searching correct path following unexisting key returns length 0
let json: Box<Value> =
Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}]));
let path: Box<JsonPath<Value>> = Box::from(
JsonPath::try_from("$.[?(@.verb == 'RUN')].key123.length()")
.expect("the path is correct"),
);
let v = path.find(&json);
let js = json!(null);
assert_eq!(v, js);
// fetching first object returns length null
let json: Box<Value> =
Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}]));
let path: Box<JsonPath<Value>> =
Box::from(JsonPath::try_from("$.[0].length()").expect("the path is correct"));
let v = path.find(&json);
let js = Value::Null;
assert_eq!(v, js);
// length on fetching the index after search gives length of the object (array)
let json: Box<Value> = Box::new(json!([{"prop": [["a", "b", "c"], "d"]}]));
let path: Box<JsonPath<Value>> = Box::from(
JsonPath::try_from("$.[?(@.prop)].prop.[0].length()").expect("the path is correct"),
);
let v = path.find(&json);
let js = json!([3]);
assert_eq!(v, js);
// length on fetching the index after search gives length of the object (string)
let json: Box<Value> = Box::new(json!([{"prop": [["a", "b", "c"], "d"]}]));
let path: Box<JsonPath<Value>> = Box::from(
JsonPath::try_from("$.[?(@.prop)].prop.[1].length()").expect("the path is correct"),
);
let v = path.find(&json);
let js = Value::Null;
assert_eq!(v, js);
}
#[test]
fn no_value_index_from_not_arr_filter_test() {
let json: Box<Value> = Box::new(json!({
"field":"field",
}));
let path: Box<JsonPath<Value>> =
Box::from(JsonPath::try_from("$.field[1]").expect("the path is correct"));
let v = path.find_slice(&json);
assert_eq!(v, vec![NoValue]);
let json: Box<Value> = Box::new(json!({
"field":[0],
}));
let path: Box<JsonPath<Value>> =
Box::from(JsonPath::try_from("$.field[1]").expect("the path is correct"));
let v = path.find_slice(&json);
assert_eq!(v, vec![NoValue]);
}
#[test]
fn no_value_filter_from_not_arr_filter_test() {
let json: Box<Value> = Box::new(json!({
"field":"field",
}));
let path: Box<JsonPath<Value>> =
Box::from(JsonPath::try_from("$.field[?(@ == 0)]").expect("the path is correct"));
let v = path.find_slice(&json);
assert_eq!(v, vec![NoValue]);
}
#[test]
fn no_value_index_filter_test() {
let json: Box<Value> = Box::new(json!({
"field":[{"f":1},{"f":0}],
}));
let path: Box<JsonPath<Value>> =
Box::from(JsonPath::try_from("$.field[?(@.f_ == 0)]").expect("the path is correct"));
let v = path.find_slice(&json);
assert_eq!(v, vec![NoValue]);
}
#[test]
fn no_value_decent_test() {
let json: Box<Value> = Box::new(json!({
"field":[{"f":1},{"f":{"f_":1}}],
}));
let path: Box<JsonPath<Value>> =
Box::from(JsonPath::try_from("$..f_").expect("the path is correct"));
let v = path.find_slice(&json);
assert_eq!(
v,
vec![Slice(&json!(1), "$.['field'][1].['f'].['f_']".to_string())]
);
}
#[test]
fn no_value_chain_test() {
let json: Box<Value> = Box::new(json!({
"field":{"field":[1]},
}));
let path: Box<JsonPath<Value>> =
Box::from(JsonPath::try_from("$.field_.field").expect("the path is correct"));
let v = path.find_slice(&json);
assert_eq!(v, vec![NoValue]);
let path: Box<JsonPath<Value>> = Box::from(
JsonPath::try_from("$.field_.field[?(@ == 1)]").expect("the path is correct"),
);
let v = path.find_slice(&json);
assert_eq!(v, vec![NoValue]);
}
#[test]
fn no_value_filter_test() {
// searching unexisting value returns length 0
let json: Box<Value> =
Box::new(json!([{"verb": "TEST"},{"verb": "TEST"}, {"verb": "RUN"}]));
let path: Box<JsonPath<Value>> = Box::from(
JsonPath::try_from("$.[?(@.verb == \"RUN1\")]").expect("the path is correct"),
);
let v = path.find(&json);
let js = json!(null);
assert_eq!(v, js);
}
#[test]
fn no_value_len_test() {
let json: Box<Value> = Box::new(json!({
"field":{"field":1},
}));
let path: Box<JsonPath<Value>> =
Box::from(JsonPath::try_from("$.field.field.length()").expect("the path is correct"));
let v = path.find_slice(&json);
assert_eq!(v, vec![NoValue]);
let json: Box<Value> = Box::new(json!({
"field":[{"a":1},{"a":1}],
}));
let path: Box<JsonPath<Value>> = Box::from(
JsonPath::try_from("$.field[?(@.a == 0)].f.length()").expect("the path is correct"),
);
let v = path.find_slice(&json);
assert_eq!(v, vec![NoValue]);
}
#[test]
fn no_clone_api_test() {
fn test_coercion(value: &Value) -> Value {
value.clone()
}
let json: Value = serde_json::from_str(template_json()).expect("to get json");
let query =
JsonPath::try_from("$..book[?(@.author size 10)].title").expect("the path is correct");
let results = query.find_slice_ptr(&json);
let v = results.first().expect("to get value");
// V can be implicitly converted to &Value
test_coercion(v);
// To explicitly convert to &Value, use deref()
assert_eq!(v.deref(), &json!("Sayings of the Century"));
}
#[test]
fn logical_exp_test() {
let json: Box<Value> = Box::new(json!({"first":{"second":[{"active":1},{"passive":1}]}}));
let path: Box<JsonPath<Value>> = Box::from(
JsonPath::try_from("$.first[?(@.does_not_exist && @.does_not_exist >= 1.0)]")
.expect("the path is correct"),
);
let v = path.find_slice(&json);
assert_eq!(v, vec![NoValue]);
let path: Box<JsonPath<Value>> = Box::from(
JsonPath::try_from("$.first[?(@.does_not_exist >= 1.0)]").expect("the path is correct"),
);
let v = path.find_slice(&json);
assert_eq!(v, vec![NoValue]);
}
#[test]
fn regex_filter_test() {
let json: Box<Value> = Box::new(json!({
"author":"abcd(Rees)",
}));
let path: Box<JsonPath<Value>> = Box::from(
JsonPath::try_from("$.[?(@.author ~= '(?i)d\\(Rees\\)')]")
.expect("the path is correct"),
);
assert_eq!(
path.find_slice(&json.clone()),
vec![Slice(&json!({"author":"abcd(Rees)"}), "$".to_string())]
);
}
#[test]
fn logical_not_exp_test() {
let json: Box<Value> = Box::new(json!({"first":{"second":{"active":1}}}));
let path: Box<JsonPath<Value>> = Box::from(
JsonPath::try_from("$.first[?(!@.does_not_exist >= 1.0)]")
.expect("the path is correct"),
);
let v = path.find_slice(&json);
assert_eq!(
v,
vec![Slice(
&json!({"second":{"active": 1}}),
"$.['first']".to_string()
)]
);
let path: Box<JsonPath<Value>> = Box::from(
JsonPath::try_from("$.first[?(!(@.does_not_exist >= 1.0))]")
.expect("the path is correct"),
);
let v = path.find_slice(&json);
assert_eq!(
v,
vec![Slice(
&json!({"second":{"active": 1}}),
"$.['first']".to_string()
)]
);
let path: Box<JsonPath<Value>> = Box::from(
JsonPath::try_from("$.first[?(!(@.second.active == 1) || @.second.active == 1)]")
.expect("the path is correct"),
);
let v = path.find_slice(&json);
assert_eq!(
v,
vec![Slice(
&json!({"second":{"active": 1}}),
"$.['first']".to_string()
)]
);
let path: Box<JsonPath<Value>> = Box::from(
JsonPath::try_from("$.first[?(!@.second.active == 1 && !@.second.active == 1 || !@.second.active == 2)]")
.expect("the path is correct"),
);
let v = path.find_slice(&json);
assert_eq!(
v,
vec![Slice(
&json!({"second":{"active": 1}}),
"$.['first']".to_string()
)]
);
}
#[test]
fn update_by_path_test() -> Result<(), JsonPathParserError> {
let mut json = json!([
{"verb": "RUN","distance":[1]},
{"verb": "TEST"},
{"verb": "DO NOT RUN"}
]);
let path: Box<JsonPath> = Box::from(JsonPath::try_from("$.[?(@.verb == 'RUN')]")?);
let elem = path
.find_as_path(&json)
.first()
.cloned()
.ok_or(JsonPathParserError::InvalidJsonPath("".to_string()))?;
if let Some(v) = json
.reference_mut(elem)?
.and_then(|v| v.as_object_mut())
.and_then(|v| v.get_mut("distance"))
.and_then(|v| v.as_array_mut())
{
v.push(json!(2))
}
assert_eq!(
json,
json!([
{"verb": "RUN","distance":[1,2]},
{"verb": "TEST"},
{"verb": "DO NOT RUN"}
])
);
Ok(())
}
}

401
vendor/jsonpath-rust/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,401 @@
//! # Json path
//! The library provides the basic functionality
//! to find the slice of data according to the query.
//! The idea comes from xpath for xml structures.
//! The details can be found over [`there`]
//! Therefore JSONPath is a query language for JSON,
//! similar to XPath for XML. The jsonpath query is a set of assertions to specify the JSON fields that need to be verified.
//!
//! # Simple example
//! Let's suppose we have a following json:
//! ```json
//! {
//! "shop": {
//! "orders": [
//! {"id": 1, "active": true},
//! {"id": 2 },
//! {"id": 3 },
//! {"id": 4, "active": true}
//! ]
//! }
//! }
//! ```
//! And we pursue to find all orders id having the field 'active'
//! we can construct the jsonpath instance like that
//! ```$.shop.orders[?(@.active)].id``` and get the result ``` [1,4] ```
//!
//! # Another examples
//! ```json
//! { "store": {
//! "book": [
//! { "category": "reference",
//! "author": "Nigel Rees",
//! "title": "Sayings of the Century",
//! "price": 8.95
//! },
//! { "category": "fiction",
//! "author": "Evelyn Waugh",
//! "title": "Sword of Honour",
//! "price": 12.99
//! },
//! { "category": "fiction",
//! "author": "Herman Melville",
//! "title": "Moby Dick",
//! "isbn": "0-553-21311-3",
//! "price": 8.99
//! },
//! { "category": "fiction",
//! "author": "J. R. R. Tolkien",
//! "title": "The Lord of the Rings",
//! "isbn": "0-395-19395-8",
//! "price": 22.99
//! }
//! ],
//! "bicycle": {
//! "color": "red",
//! "price": 19.95
//! }
//! }
//! }
//! ```
//! and examples
//! - ``` $.store.book[*].author ``` : the authors of all books in the store
//! - ``` $..book[?(@.isbn)]``` : filter all books with isbn number
//! - ``` $..book[?(@.price<10)]``` : filter all books cheapier than 10
//! - ``` $..*``` : all Elements in XML document. All members of JSON structure
//! - ``` $..book[0,1]``` : The first two books
//! - ``` $..book[:2]``` : The first two books
//!
//! # Operators
//!
//! - `$` : Pointer to the root of the json. It is gently advising to start every jsonpath from the root. Also, inside the filters to point out that the path is starting from the root.
//! - `@`Pointer to the current element inside the filter operations.It is used inside the filter operations to iterate the collection.
//! - `*` or `[*]`Wildcard. It brings to the list all objects and elements regardless their names.It is analogue a flatmap operation.
//! - `<..>`| Descent operation. It brings to the list all objects, children of that objects and etc It is analogue a flatmap operation.
//! - `.<name>` or `.['<name>']`the key pointing to the field of the objectIt is used to obtain the specific field.
//! - `['<name>' (, '<name>')]`the list of keysthe same usage as for a single key but for list
//! - `[<number>]`the filter getting the element by its index.
//! - `[<number> (, <number>)]`the list if elements of array according to their indexes representing these numbers. |
//! - `[<start>:<end>:<step>]`slice operator to get a list of element operating with their indexes. By default step = 1, start = 0, end = array len. The elements can be omitted ```[:]```
//! - `[?(<expression>)]`the logical expression to filter elements in the list.It is used with arrays preliminary.
//!
//! # Examples
//!```rust
//! use std::str::FromStr;
//! use serde_json::{json, Value};
//! use jsonpath_rust::{jp_v, JsonPathValue, JsonPath};
//!
//! fn test() -> Result<(), Box<dyn std::error::Error>> {
//! let json = serde_json::from_str(r#"{"first":{"second":[{"active":1},{"passive":1}]}}"#)?;
//! let path = JsonPath::try_from("$.first.second[?(@.active)]")?;
//! let slice_of_data:Vec<JsonPathValue<Value>> = path.find_slice(&json);
//! let js = json!({"active":1});
//! assert_eq!(slice_of_data, jp_v![&js;"$.first.second[0]",]);
//! # Ok(())
//! }
//! ```
//!
//!
//! [`there`]: https://goessner.net/articles/JsonPath/
pub use parser::model::JsonPath;
pub use parser::JsonPathParserError;
use serde_json::Value;
use std::fmt::Debug;
use std::ops::Deref;
use JsonPathValue::{NewValue, NoValue, Slice};
mod jsonpath;
pub mod parser;
pub mod path;
#[macro_use]
extern crate pest_derive;
extern crate core;
extern crate pest;
/// the trait allows to query a path on any value by just passing the &str of as JsonPath.
///
/// It is equal to
/// ```rust
/// # use serde_json::json;
/// # use std::str::FromStr;
/// use jsonpath_rust::JsonPath;
///
/// let query = "$.hello";
/// let json_path = JsonPath::from_str(query).unwrap();
/// json_path.find(&json!({"hello": "world"}));
/// ```
///
/// It is default implemented for [Value].
///
/// #Note:
/// the result is going to be cloned and therefore it can be significant for the huge queries.
/// if the same &str is used multiple times, it's more efficient to reuse a single JsonPath.
///
/// # Examples:
/// ```
/// use std::str::FromStr;
/// use serde_json::{json, Value};
/// use jsonpath_rust::jp_v;
/// use jsonpath_rust::{JsonPathQuery, JsonPath, JsonPathValue};
///
/// fn test() -> Result<(), Box<dyn std::error::Error>> {
/// let json: Value = serde_json::from_str("{}")?;
/// let v = json.path("$..book[?(@.author size 10)].title")?;
/// assert_eq!(v, json!([]));
///
/// let json: Value = serde_json::from_str("{}")?;
/// let path = json.path("$..book[?(@.author size 10)].title")?;
///
/// assert_eq!(path, json!(["Sayings of the Century"]));
///
/// let json: Value = serde_json::from_str("{}")?;
/// let path = JsonPath::try_from("$..book[?(@.author size 10)].title")?;
///
/// let v = path.find_slice(&json);
/// let js = json!("Sayings of the Century");
/// assert_eq!(v, jp_v![&js;"",]);
/// # Ok(())
/// }
///
/// ```
pub trait JsonPathQuery {
fn path(self, query: &str) -> Result<Value, JsonPathParserError>;
}
/// Json paths may return either pointers to the original json or new data. This custom pointer type allows us to handle both cases.
/// Unlike JsonPathValue, this type does not represent NoValue to allow the implementation of Deref.
#[derive(Debug, PartialEq, Clone)]
pub enum JsonPtr<'a, Data> {
/// The slice of the initial json data
Slice(&'a Data),
/// The new data that was generated from the input data (like length operator)
NewValue(Data),
}
/// Allow deref from json pointer to value.
impl Deref for JsonPtr<'_, Value> {
type Target = Value;
fn deref(&self) -> &Self::Target {
match self {
JsonPtr::Slice(v) => v,
JsonPtr::NewValue(v) => v,
}
}
}
impl JsonPathQuery for Value {
fn path(self, query: &str) -> Result<Value, JsonPathParserError> {
let p = JsonPath::try_from(query)?;
Ok(p.find(&self))
}
}
/*
impl<T> JsonPathQuery for T
where T: Deref<Target=Value> {
fn path(self, query: &str) -> Result<Value, String> {
let p = JsonPath::from_str(query)?;
Ok(p.find(self.deref()))
}
}
*/
/// just to create a json path value of data
/// Example:
/// - `jp_v(&json) = JsonPathValue::Slice(&json)`
/// - `jp_v(&json;"foo") = JsonPathValue::Slice(&json, "foo".to_string())`
/// - `jp_v(&json,) = vec![JsonPathValue::Slice(&json)]`
/// - `jp_v[&json1,&json1] = vec![JsonPathValue::Slice(&json1),JsonPathValue::Slice(&json2)]`
/// - `jp_v(json) = JsonPathValue::NewValue(json)`
/// ```
/// use std::str::FromStr;
/// use serde_json::{json, Value};
/// use jsonpath_rust::{jp_v, JsonPathQuery, JsonPath, JsonPathValue};
///
/// fn test() -> Result<(), Box<dyn std::error::Error>> {
/// let json: Value = serde_json::from_str("{}")?;
/// let path = JsonPath::try_from("$..book[?(@.author size 10)].title")?;
/// let v = path.find_slice(&json);
///
/// let js = json!("Sayings of the Century");
/// assert_eq!(v, jp_v![&js;"",]);
/// # Ok(())
/// }
/// ```
#[macro_export]
macro_rules! jp_v {
(&$v:expr) =>{
JsonPathValue::Slice(&$v, String::new())
};
(&$v:expr ; $s:expr) =>{
JsonPathValue::Slice(&$v, $s.to_string())
};
($(&$v:expr;$s:expr),+ $(,)?) =>{
{
vec![
$(
jp_v!(&$v ; $s),
)+
]
}
};
($(&$v:expr),+ $(,)?) => {
{
vec![
$(
jp_v!(&$v),
)+
]
}
};
($v:expr) =>{
JsonPathValue::NewValue($v)
};
}
/// Represents the path of the found json data
pub type JsonPathStr = String;
pub fn jsp_idx(prefix: &str, idx: usize) -> String {
format!("{}[{}]", prefix, idx)
}
pub fn jsp_obj(prefix: &str, key: &str) -> String {
format!("{}.['{}']", prefix, key)
}
/// A result of json path
/// Can be either a slice of initial data or a new generated value(like length of array)
#[derive(Debug, PartialEq, Clone)]
pub enum JsonPathValue<'a, Data> {
/// The slice of the initial json data
Slice(&'a Data, JsonPathStr),
/// The new data that was generated from the input data (like length operator)
NewValue(Data),
/// The absent value that indicates the input data is not matched to the given json path (like the absent fields)
NoValue,
}
impl<'a, Data: Clone + Debug + Default> JsonPathValue<'a, Data> {
/// Transforms given value into data either by moving value out or by cloning
pub fn to_data(self) -> Data {
match self {
Slice(r, _) => r.clone(),
NewValue(val) => val,
NoValue => Data::default(),
}
}
/// Transforms given value into path
pub fn to_path(self) -> Option<JsonPathStr> {
match self {
Slice(_, path) => Some(path),
_ => None,
}
}
pub fn from_root(data: &'a Data) -> Self {
Slice(data, String::from("$"))
}
pub fn new_slice(data: &'a Data, path: String) -> Self {
Slice(data, path.to_string())
}
}
impl<'a, Data> JsonPathValue<'a, Data> {
pub fn only_no_value(input: &[JsonPathValue<'a, Data>]) -> bool {
!input.is_empty() && input.iter().filter(|v| v.has_value()).count() == 0
}
pub fn map_vec(data: Vec<(&'a Data, JsonPathStr)>) -> Vec<JsonPathValue<'a, Data>> {
data.into_iter()
.map(|(data, pref)| Slice(data, pref))
.collect()
}
pub fn map_slice<F>(self, mapper: F) -> Vec<JsonPathValue<'a, Data>>
where
F: FnOnce(&'a Data, JsonPathStr) -> Vec<(&'a Data, JsonPathStr)>,
{
match self {
Slice(r, pref) => mapper(r, pref)
.into_iter()
.map(|(d, s)| Slice(d, s))
.collect(),
NewValue(_) => vec![],
no_v => vec![no_v],
}
}
pub fn flat_map_slice<F>(self, mapper: F) -> Vec<JsonPathValue<'a, Data>>
where
F: FnOnce(&'a Data, JsonPathStr) -> Vec<JsonPathValue<'a, Data>>,
{
match self {
Slice(r, pref) => mapper(r, pref),
_ => vec![NoValue],
}
}
pub fn has_value(&self) -> bool {
!matches!(self, NoValue)
}
pub fn vec_as_data(input: Vec<JsonPathValue<'a, Data>>) -> Vec<&'a Data> {
input
.into_iter()
.filter_map(|v| match v {
Slice(el, _) => Some(el),
_ => None,
})
.collect()
}
pub fn vec_as_pair(input: Vec<JsonPathValue<'a, Data>>) -> Vec<(&'a Data, JsonPathStr)> {
input
.into_iter()
.filter_map(|v| match v {
Slice(el, v) => Some((el, v)),
_ => None,
})
.collect()
}
/// moves a pointer (from slice) out or provides a default value when the value was generated
pub fn slice_or(self, default: &'a Data) -> &'a Data {
match self {
Slice(r, _) => r,
NewValue(_) | NoValue => default,
}
}
}
#[cfg(test)]
mod tests {
use serde_json::Value;
use crate::JsonPath;
use std::str::FromStr;
#[test]
fn to_string_test() {
let path: Box<JsonPath<Value>> = Box::from(
JsonPath::from_str(
"$.['a'].a..book[1:3][*][1]['a','b'][?(@)][?(@.verb == 'TEST')].a.length()",
)
.unwrap(),
);
assert_eq!(
path.to_string(),
"$.'a'.'a'..book[1:3:1][*][1]['a','b'][?(@ exists )][?(@.'verb' == \"TEST\")].'a'.length()"
);
}
}

View File

@@ -0,0 +1,29 @@
use thiserror::Error;
use super::parser::Rule;
#[derive(Error, Debug)]
pub enum JsonPathParserError {
#[error("Failed to parse rule: {0}")]
PestError(#[from] Box<pest::error::Error<Rule>>),
#[error("Unexpected rule {0:?} when trying to parse logic atom: {1} within {2}")]
UnexpectedRuleLogicError(Rule, String, String),
#[error("Unexpected `none` when trying to parse logic atom: {0} within {1}")]
UnexpectedNoneLogicError(String, String),
#[error("Pest returned successful parsing but did not produce any output, that should be unreachable due to .pest definition file: SOI ~ chain ~ EOI")]
UnexpectedPestOutput,
#[error("expected a `Rule::path` but found nothing")]
NoRulePath,
#[error("expected a `JsonPath::Descent` but found nothing")]
NoJsonPathDescent,
#[error("expected a `JsonPath::Field` but found nothing")]
NoJsonPathField,
#[error("expected a `f64` or `i64`, but got {0}")]
InvalidNumber(String),
#[error("Invalid toplevel rule for JsonPath: {0:?}")]
InvalidTopLevelRule(Rule),
#[error("Failed to get inner pairs for {0}")]
EmptyInner(String),
#[error("Invalid json path: {0}")]
InvalidJsonPath(String),
}

View File

@@ -0,0 +1,55 @@
WHITESPACE = _{ " " | "\t" | "\r\n" | "\n"}
boolean = {"true" | "false"}
null = {"null"}
min = _{"-"}
col = _{":"}
dot = _{ "." }
word = _{ ('a'..'z' | 'A'..'Z')+ }
specs = _{ "_" | "-" | "/" | "\\" | "#" }
number = @{"-"? ~ ("0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*) ~ ("." ~ ASCII_DIGIT+)? ~ (^"e" ~ ("+" | "-")? ~ ASCII_DIGIT+)?}
string_qt = ${ ("\'" ~ inner ~ "\'") | ("\"" ~ inner ~ "\"") }
inner = @{ char* }
char = _{
!("\"" | "\\" | "\'") ~ ANY
| "\\" ~ ("\"" | "\'" | "\\" | "/" | "b" | "f" | "n" | "r" | "t" | "(" | ")")
| "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4})
}
root = {"$"}
sign = { "==" | "!=" | "~=" | ">=" | ">" | "<=" | "<" | "in" | "nin" | "size" | "noneOf" | "anyOf" | "subsetOf"}
not = {"!"}
key_lim = {!"length()" ~ (word | ASCII_DIGIT | specs)+}
key_unlim = {"[" ~ string_qt ~ "]"}
key = ${key_lim | key_unlim}
descent = {dot ~ dot ~ key}
descent_w = {dot ~ dot ~ "*"} // refactor afterwards
wildcard = {dot? ~ "[" ~"*"~"]" | dot ~ "*"}
current = {"@" ~ chain?}
field = ${dot? ~ key_unlim | dot ~ key_lim }
function = { dot ~ "length" ~ "(" ~ ")"}
unsigned = {("0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*)}
signed = {min? ~ unsigned}
start_slice = {signed}
end_slice = {signed}
step_slice = {col ~ unsigned}
slice = {start_slice? ~ col ~ end_slice? ~ step_slice? }
unit_keys = { string_qt ~ ("," ~ string_qt)+ }
unit_indexes = { number ~ ("," ~ number)+ }
filter = {"?"~ "(" ~ logic_or ~ ")"}
logic_or = {logic_and ~ ("||" ~ logic_and)*}
logic_and = {logic_not ~ ("&&" ~ logic_not)*}
logic_not = {not? ~ logic_atom}
logic_atom = {atom ~ (sign ~ atom)? | "(" ~ logic_or ~ ")"}
atom = {chain | string_qt | number | boolean | null}
index = {dot? ~ "["~ (unit_keys | unit_indexes | slice | unsigned |filter) ~ "]" }
chain = {(root | descent | descent_w | wildcard | current | field | index | function)+}
path = {SOI ~ chain ~ EOI }

View File

@@ -0,0 +1,110 @@
#[macro_export]
macro_rules! filter {
() => {FilterExpression::Atom(op!,FilterSign::new(""),op!())};
( $left:expr, $s:literal, $right:expr) => {
FilterExpression::Atom($left,FilterSign::new($s),$right)
};
( $left:expr,||, $right:expr) => {FilterExpression::Or(Box::new($left),Box::new($right)) };
( $left:expr,&&, $right:expr) => {FilterExpression::And(Box::new($left),Box::new($right)) };
}
#[macro_export]
macro_rules! op {
( ) => {
Operand::Dynamic(Box::new(JsonPath::Empty))
};
( $s:literal) => {
Operand::Static(json!($s))
};
( s $s:expr) => {
Operand::Static(json!($s))
};
( $s:expr) => {
Operand::Dynamic(Box::new($s))
};
}
#[macro_export]
macro_rules! idx {
( $s:literal) => {JsonPathIndex::Single(json!($s))};
( idx $($ss:literal),+) => {{
let ss_vec = vec![
$(
json!($ss),
)+
];
JsonPathIndex::UnionIndex(ss_vec)
}};
( $($ss:literal),+) => {{
let ss_vec = vec![
$(
$ss.to_string(),
)+
];
JsonPathIndex::UnionKeys(ss_vec)
}};
( $s:literal) => {JsonPathIndex::Single(json!($s))};
( ? $s:expr) => {JsonPathIndex::Filter($s)};
( [$l:literal;$m:literal;$r:literal]) => {JsonPathIndex::Slice($l,$m,$r)};
( [$l:literal;$m:literal;]) => {JsonPathIndex::Slice($l,$m,1)};
( [$l:literal;;$m:literal]) => {JsonPathIndex::Slice($l,0,$m)};
( [;$l:literal;$m:literal]) => {JsonPathIndex::Slice(0,$l,$m)};
( [;;$m:literal]) => {JsonPathIndex::Slice(0,0,$m)};
( [;$m:literal;]) => {JsonPathIndex::Slice(0,$m,1)};
( [$m:literal;;]) => {JsonPathIndex::Slice($m,0,1)};
( [;;]) => {JsonPathIndex::Slice(0,0,1)};
}
#[macro_export]
macro_rules! chain {
($($ss:expr),+) => {{
let ss_vec = vec![
$(
$ss,
)+
];
JsonPath::Chain(ss_vec)
}};
}
/// Can be used to Parse a JsonPath with a more native way.
/// e.g.
/// ```rust
/// use jsonpath_rust::{path, JsonPath};
/// use std::str::FromStr;
/// use serde_json::Value;
///
/// let path:JsonPath<Value> = JsonPath::from_str(".abc.*").unwrap();
/// let path2 = JsonPath::Chain(vec![path!("abc"), path!(*)]);
/// assert_eq!(path, path2);
/// ```
#[macro_export]
macro_rules! path {
( ) => {JsonPath::Empty};
(*) => {JsonPath::Wildcard};
($) => {JsonPath::Root};
(@) => {JsonPath::Current(Box::new(JsonPath::Empty))};
(@$e:expr) => {JsonPath::Current(Box::new($e))};
(@,$($ss:expr),+) => {{
let ss_vec = vec![
$(
$ss,
)+
];
let chain = JsonPath::Chain(ss_vec);
JsonPath::Current(Box::new(chain))
}};
(..$e:literal) => {JsonPath::Descent($e.to_string())};
(..*) => {JsonPath::DescentW};
($e:literal) => {JsonPath::Field($e.to_string())};
($e:expr) => {JsonPath::Index($e)};
}
#[cfg(test)]
pub(crate) use chain;
#[cfg(test)]
pub(crate) use filter;
#[cfg(test)]
pub(crate) use idx;
#[cfg(test)]
pub(crate) use op;

15
vendor/jsonpath-rust/src/parser/mod.rs vendored Normal file
View File

@@ -0,0 +1,15 @@
//! The parser for the jsonpath.
//! The module grammar denotes the structure of the parsing grammar
mod errors;
pub(crate) mod macros;
pub(crate) mod model;
#[allow(clippy::module_inception)]
pub(crate) mod parser;
pub use errors::JsonPathParserError;
pub use model::FilterExpression;
pub use model::JsonPath;
pub use model::JsonPathIndex;
pub use model::Operand;
pub use parser::{parse_json_path, Rule};

334
vendor/jsonpath-rust/src/parser/model.rs vendored Normal file
View File

@@ -0,0 +1,334 @@
use serde_json::Value;
use crate::path::JsonLike;
use super::errors::JsonPathParserError;
use super::parse_json_path;
use std::fmt::{Display, Formatter};
use std::{convert::TryFrom, str::FromStr};
/// The basic structures for parsing json paths.
/// The common logic of the structures pursues to correspond the internal parsing structure.
///
/// usually it's created by using [`FromStr`] or [`TryFrom<&str>`]
#[derive(Debug, Clone)]
pub enum JsonPath<T = Value> {
/// The $ operator
Root,
/// Field represents key
Field(String),
/// The whole chain of the path.
Chain(Vec<JsonPath<T>>),
/// The .. operator
Descent(String),
/// The ..* operator
DescentW,
/// The indexes for array
Index(JsonPathIndex<T>),
/// The @ operator
Current(Box<JsonPath<T>>),
/// The * operator
Wildcard,
/// The item uses to define the unresolved state
Empty,
/// Functions that can calculate some expressions
Fn(Function),
}
impl<T: Display> Display for JsonPath<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let str = match self {
JsonPath::Root => "$".to_string(),
JsonPath::Field(e) => format!(".'{}'", e),
JsonPath::Chain(elems) => elems.iter().map(ToString::to_string).collect::<String>(),
JsonPath::Descent(e) => {
format!("..{}", e)
}
JsonPath::DescentW => "..*".to_string(),
JsonPath::Index(e) => e.to_string(),
JsonPath::Current(e) => format!("@{}", e),
JsonPath::Wildcard => "[*]".to_string(),
JsonPath::Empty => "".to_string(),
JsonPath::Fn(e) => format!(".{}", e),
};
write!(f, "{}", str)
}
}
impl<T> TryFrom<&str> for JsonPath<T>
where
T: JsonLike,
{
type Error = JsonPathParserError;
/// Parses a string into a [JsonPath].
///
/// # Errors
///
/// Returns a variant of [JsonPathParserError] if the parsing operation failed.
fn try_from(value: &str) -> Result<Self, Self::Error> {
parse_json_path(value)
}
}
impl<T> FromStr for JsonPath<T>
where
T: JsonLike,
{
type Err = JsonPathParserError;
/// Parses a string into a [JsonPath].
///
/// # Errors
///
/// Returns a variant of [JsonPathParserError] if the parsing operation failed.
fn from_str(value: &str) -> Result<Self, Self::Err> {
parse_json_path(value)
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum Function {
/// length()
Length,
}
impl Display for Function {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let str = match self {
Function::Length => "length()".to_string(),
};
write!(f, "{}", str)
}
}
#[derive(Debug, Clone)]
pub enum JsonPathIndex<T> {
/// A single element in array
Single(T),
/// Union represents a several indexes
UnionIndex(Vec<T>),
/// Union represents a several keys
UnionKeys(Vec<String>),
/// DEfault slice where the items are start/end/step respectively
Slice(i32, i32, usize),
/// Filter ?()
Filter(FilterExpression<T>),
}
impl<T: Display> Display for JsonPathIndex<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let str = match self {
JsonPathIndex::Single(e) => format!("[{}]", e),
JsonPathIndex::UnionIndex(elems) => {
format!(
"[{}]",
elems
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(",")
)
}
JsonPathIndex::UnionKeys(elems) => {
format!(
"[{}]",
elems
.iter()
.map(|el| format!("'{}'", el))
.collect::<Vec<_>>()
.join(",")
)
}
JsonPathIndex::Slice(s, e, st) => {
format!("[{}:{}:{}]", s, e, st)
}
JsonPathIndex::Filter(filter) => format!("[?({})]", filter),
};
write!(f, "{}", str)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum FilterExpression<T> {
/// a single expression like a > 2
Atom(Operand<T>, FilterSign, Operand<T>),
/// and with &&
And(Box<FilterExpression<T>>, Box<FilterExpression<T>>),
/// or with ||
Or(Box<FilterExpression<T>>, Box<FilterExpression<T>>),
/// not with !
Not(Box<FilterExpression<T>>),
}
impl<T: Display> Display for FilterExpression<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let str = match self {
FilterExpression::Atom(left, sign, right) => {
format!("{} {} {}", left, sign, right)
}
FilterExpression::And(left, right) => {
format!("{} && {}", left, right)
}
FilterExpression::Or(left, right) => {
format!("{} || {}", left, right)
}
FilterExpression::Not(expr) => {
format!("!{}", expr)
}
};
write!(f, "{}", str)
}
}
impl<T> FilterExpression<T> {
pub fn exists(op: Operand<T>) -> Self {
FilterExpression::Atom(
op,
FilterSign::Exists,
Operand::Dynamic(Box::new(JsonPath::Empty)),
)
}
}
/// Operand for filtering expressions
#[derive(Debug, Clone)]
pub enum Operand<T> {
Static(T),
Dynamic(Box<JsonPath<T>>),
}
impl<T: Display> Display for Operand<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let str = match self {
Operand::Static(e) => e.to_string(),
Operand::Dynamic(e) => e.to_string(),
};
write!(f, "{}", str)
}
}
#[allow(dead_code)]
impl<T> Operand<T> {
pub fn val(v: T) -> Self {
Operand::Static(v)
}
}
/// The operators for filtering functions
#[derive(Debug, Clone, PartialEq)]
pub enum FilterSign {
Equal,
Unequal,
Less,
Greater,
LeOrEq,
GrOrEq,
Regex,
In,
Nin,
Size,
NoneOf,
AnyOf,
SubSetOf,
Exists,
}
impl Display for FilterSign {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let str = match self {
FilterSign::Equal => "==",
FilterSign::Unequal => "!=",
FilterSign::Less => "<",
FilterSign::Greater => ">",
FilterSign::LeOrEq => "<=",
FilterSign::GrOrEq => ">=",
FilterSign::Regex => "~=",
FilterSign::In => "in",
FilterSign::Nin => "nin",
FilterSign::Size => "size",
FilterSign::NoneOf => "noneOf",
FilterSign::AnyOf => "anyOf",
FilterSign::SubSetOf => "subsetOf",
FilterSign::Exists => "exists",
};
write!(f, "{}", str)
}
}
impl FilterSign {
pub fn new(key: &str) -> Self {
match key {
"==" => FilterSign::Equal,
"!=" => FilterSign::Unequal,
"<" => FilterSign::Less,
">" => FilterSign::Greater,
"<=" => FilterSign::LeOrEq,
">=" => FilterSign::GrOrEq,
"~=" => FilterSign::Regex,
"in" => FilterSign::In,
"nin" => FilterSign::Nin,
"size" => FilterSign::Size,
"noneOf" => FilterSign::NoneOf,
"anyOf" => FilterSign::AnyOf,
"subsetOf" => FilterSign::SubSetOf,
_ => FilterSign::Exists,
}
}
}
impl<T> PartialEq for JsonPath<T>
where
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(JsonPath::Root, JsonPath::Root) => true,
(JsonPath::Descent(k1), JsonPath::Descent(k2)) => k1 == k2,
(JsonPath::DescentW, JsonPath::DescentW) => true,
(JsonPath::Field(k1), JsonPath::Field(k2)) => k1 == k2,
(JsonPath::Wildcard, JsonPath::Wildcard) => true,
(JsonPath::Empty, JsonPath::Empty) => true,
(JsonPath::Current(jp1), JsonPath::Current(jp2)) => jp1 == jp2,
(JsonPath::Chain(ch1), JsonPath::Chain(ch2)) => ch1 == ch2,
(JsonPath::Index(idx1), JsonPath::Index(idx2)) => idx1 == idx2,
(JsonPath::Fn(fn1), JsonPath::Fn(fn2)) => fn2 == fn1,
(_, _) => false,
}
}
}
impl<T> PartialEq for JsonPathIndex<T>
where
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(JsonPathIndex::Slice(s1, e1, st1), JsonPathIndex::Slice(s2, e2, st2)) => {
s1 == s2 && e1 == e2 && st1 == st2
}
(JsonPathIndex::Single(el1), JsonPathIndex::Single(el2)) => el1 == el2,
(JsonPathIndex::UnionIndex(elems1), JsonPathIndex::UnionIndex(elems2)) => {
elems1 == elems2
}
(JsonPathIndex::UnionKeys(elems1), JsonPathIndex::UnionKeys(elems2)) => {
elems1 == elems2
}
(JsonPathIndex::Filter(left), JsonPathIndex::Filter(right)) => left.eq(right),
(_, _) => false,
}
}
}
impl<T> PartialEq for Operand<T>
where
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Operand::Static(v1), Operand::Static(v2)) => v1 == v2,
(Operand::Dynamic(jp1), Operand::Dynamic(jp2)) => jp1 == jp2,
(_, _) => false,
}
}
}

View File

@@ -0,0 +1,624 @@
#![allow(clippy::empty_docs)]
use crate::parser::errors::JsonPathParserError;
use crate::parser::model::FilterExpression::{And, Not, Or};
use crate::parser::model::{
FilterExpression, FilterSign, Function, JsonPath, JsonPathIndex, Operand,
};
use crate::path::JsonLike;
use pest::iterators::{Pair, Pairs};
use pest::Parser;
#[derive(Parser)]
#[grammar = "parser/grammar/json_path.pest"]
struct JsonPathParser;
/// Parses a string into a [JsonPath].
///
/// # Errors
///
/// Returns a variant of [JsonPathParserError] if the parsing operation failed.
pub fn parse_json_path<T>(jp_str: &str) -> Result<JsonPath<T>, JsonPathParserError>
where
T: JsonLike,
{
JsonPathParser::parse(Rule::path, jp_str)
.map_err(Box::new)?
.next()
.ok_or(JsonPathParserError::UnexpectedPestOutput)
.and_then(parse_internal)
}
/// Internal function takes care of the logic by parsing the operators and unrolling the string into the final result.
///
/// # Errors
///
/// Returns a variant of [JsonPathParserError] if the parsing operation failed
fn parse_internal<T>(rule: Pair<'_, Rule>) -> Result<JsonPath<T>, JsonPathParserError>
where
T: JsonLike,
{
match rule.as_rule() {
Rule::path => rule
.into_inner()
.next()
.ok_or(JsonPathParserError::NoRulePath)
.and_then(parse_internal),
Rule::current => rule
.into_inner()
.next()
.map(parse_internal)
.unwrap_or(Ok(JsonPath::Empty))
.map(Box::new)
.map(JsonPath::Current),
Rule::chain => rule
.into_inner()
.map(parse_internal)
.collect::<Result<Vec<_>, _>>()
.map(JsonPath::Chain),
Rule::root => Ok(JsonPath::Root),
Rule::wildcard => Ok(JsonPath::Wildcard),
Rule::descent => parse_key(down(rule)?)?
.map(JsonPath::Descent)
.ok_or(JsonPathParserError::NoJsonPathDescent),
Rule::descent_w => Ok(JsonPath::DescentW),
Rule::function => Ok(JsonPath::Fn(Function::Length)),
Rule::field => parse_key(down(rule)?)?
.map(JsonPath::Field)
.ok_or(JsonPathParserError::NoJsonPathField),
Rule::index => parse_index(rule).map(JsonPath::Index),
rule => Err(JsonPathParserError::InvalidTopLevelRule(rule)),
}
}
/// parsing the rule 'key' with the structures either .key or .\['key'\]
fn parse_key(rule: Pair<Rule>) -> Result<Option<String>, JsonPathParserError> {
let parsed_key = match rule.as_rule() {
Rule::key | Rule::key_unlim | Rule::string_qt => parse_key(down(rule)?),
Rule::key_lim | Rule::inner => Ok(Some(String::from(rule.as_str()))),
_ => Ok(None),
};
parsed_key
}
fn parse_slice<T>(pairs: Pairs<Rule>) -> Result<JsonPathIndex<T>, JsonPathParserError> {
let mut start = 0;
let mut end = 0;
let mut step = 1;
for in_pair in pairs {
match in_pair.as_rule() {
Rule::start_slice => start = in_pair.as_str().parse::<i32>().unwrap_or(start),
Rule::end_slice => end = in_pair.as_str().parse::<i32>().unwrap_or(end),
Rule::step_slice => step = down(in_pair)?.as_str().parse::<usize>().unwrap_or(step),
_ => (),
}
}
Ok(JsonPathIndex::Slice(start, end, step))
}
fn parse_unit_keys<T>(pairs: Pairs<Rule>) -> Result<JsonPathIndex<T>, JsonPathParserError> {
let mut keys = vec![];
for pair in pairs {
keys.push(String::from(down(pair)?.as_str()));
}
Ok(JsonPathIndex::UnionKeys(keys))
}
fn number_to_value<T>(number: &str) -> Result<T, JsonPathParserError>
where
T: From<i64> + From<f64>,
{
match number
.parse::<i64>()
.ok()
.map(T::from)
.or_else(|| number.parse::<f64>().ok().map(T::from))
{
Some(value) => Ok(value),
None => Err(JsonPathParserError::InvalidNumber(number.to_string())),
}
}
fn bool_to_value<T>(boolean: &str) -> T
where
T: From<bool>,
{
boolean
.parse::<bool>()
.map(T::from)
.expect("unreachable: according to .pest this is either `true` or `false`")
}
fn parse_unit_indexes<T>(pairs: Pairs<Rule>) -> Result<JsonPathIndex<T>, JsonPathParserError>
where
T: From<i64> + From<f64>,
{
let mut keys = vec![];
for pair in pairs {
keys.push(number_to_value(pair.as_str())?);
}
Ok(JsonPathIndex::UnionIndex(keys))
}
fn parse_chain_in_operand<T>(rule: Pair<'_, Rule>) -> Result<Operand<T>, JsonPathParserError>
where
T: JsonLike,
{
let parsed_chain = match parse_internal::<T>(rule)? {
JsonPath::Chain(elems) => {
if elems.len() == 1 {
match elems.first() {
Some(JsonPath::Index(JsonPathIndex::UnionKeys(keys))) => {
Operand::val(T::from(keys.clone()))
}
Some(JsonPath::Index(JsonPathIndex::UnionIndex(keys))) => {
Operand::val(T::from(keys.clone()))
}
Some(JsonPath::Field(f)) => Operand::val(T::from(vec![f.to_string()])),
_ => Operand::Dynamic(Box::new(JsonPath::Chain(elems))),
}
} else {
Operand::Dynamic(Box::new(JsonPath::Chain(elems)))
}
}
jp => Operand::Dynamic(Box::new(jp)),
};
Ok(parsed_chain)
}
fn parse_filter_index<T>(pair: Pair<'_, Rule>) -> Result<JsonPathIndex<T>, JsonPathParserError>
where
T: JsonLike,
{
Ok(JsonPathIndex::Filter(parse_logic_or(pair.into_inner())?))
}
fn parse_logic_or<T>(pairs: Pairs<'_, Rule>) -> Result<FilterExpression<T>, JsonPathParserError>
where
T: JsonLike,
{
let mut expr: Option<FilterExpression<T>> = None;
// only possible for the loop not to produce any value (except Errors)
if pairs.len() == 0 {
return Err(JsonPathParserError::UnexpectedNoneLogicError(
pairs.get_input().to_string(),
pairs.as_str().to_string(),
));
}
for pair in pairs.into_iter() {
let next_expr = parse_logic_and(pair.into_inner())?;
match expr {
None => expr = Some(next_expr),
Some(e) => expr = Some(Or(Box::new(e), Box::new(next_expr))),
}
}
Ok(expr.expect("unreachable: above len() == 0 check should have catched this"))
}
fn parse_logic_and<T>(pairs: Pairs<'_, Rule>) -> Result<FilterExpression<T>, JsonPathParserError>
where
T: JsonLike,
{
let mut expr: Option<FilterExpression<T>> = None;
// only possible for the loop not to produce any value (except Errors)
if pairs.len() == 0 {
return Err(JsonPathParserError::UnexpectedNoneLogicError(
pairs.get_input().to_string(),
pairs.as_str().to_string(),
));
}
for pair in pairs {
let next_expr = parse_logic_not(pair.into_inner())?;
match expr {
None => expr = Some(next_expr),
Some(e) => expr = Some(And(Box::new(e), Box::new(next_expr))),
}
}
Ok(expr.expect("unreachable: above len() == 0 check should have catched this"))
}
fn parse_logic_not<T>(
mut pairs: Pairs<'_, Rule>,
) -> Result<FilterExpression<T>, JsonPathParserError>
where
T: JsonLike,
{
if let Some(rule) = pairs.peek().map(|x| x.as_rule()) {
match rule {
Rule::not => {
pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)");
parse_logic_not(pairs)
.map(|expr|Not(Box::new(expr)))
},
Rule::logic_atom => parse_logic_atom(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").into_inner()),
rule => Err(JsonPathParserError::UnexpectedRuleLogicError(rule, pairs.get_input().to_string(), pairs.as_str().to_string())),
}
} else {
Err(JsonPathParserError::UnexpectedNoneLogicError(
pairs.get_input().to_string(),
pairs.as_str().to_string(),
))
}
}
fn parse_logic_atom<T>(
mut pairs: Pairs<'_, Rule>,
) -> Result<FilterExpression<T>, JsonPathParserError>
where
T: JsonLike,
{
if let Some(rule) = pairs.peek().map(|x| x.as_rule()) {
match rule {
Rule::logic_or => parse_logic_or(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").into_inner()),
Rule::atom => {
let left: Operand<T> = parse_atom(pairs.next().unwrap())?;
if pairs.peek().is_none() {
Ok(FilterExpression::exists(left))
} else {
let sign: FilterSign = FilterSign::new(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").as_str());
let right: Operand<T> =
parse_atom(pairs.next().expect("unreachable in arithemetic: should have a right side operand"))?;
Ok(FilterExpression::Atom(left, sign, right))
}
}
rule => Err(JsonPathParserError::UnexpectedRuleLogicError(rule, pairs.get_input().to_string(), pairs.as_str().to_string())),
}
} else {
Err(JsonPathParserError::UnexpectedNoneLogicError(
pairs.get_input().to_string(),
pairs.as_str().to_string(),
))
}
}
fn parse_atom<T>(rule: Pair<'_, Rule>) -> Result<Operand<T>, JsonPathParserError>
where
T: JsonLike,
{
let atom = down(rule.clone())?;
let parsed_atom = match atom.as_rule() {
Rule::number => Operand::Static(number_to_value(rule.as_str())?),
Rule::string_qt => Operand::Static(T::from(down(atom)?.as_str())),
Rule::chain => parse_chain_in_operand(down(rule)?)?,
Rule::boolean => Operand::Static(bool_to_value(rule.as_str())),
_ => Operand::Static(T::null()),
};
Ok(parsed_atom)
}
fn parse_index<T>(rule: Pair<'_, Rule>) -> Result<JsonPathIndex<T>, JsonPathParserError>
where
T: JsonLike,
{
let next = down(rule)?;
let parsed_index = match next.as_rule() {
Rule::unsigned => JsonPathIndex::Single(number_to_value(next.as_str())?),
Rule::slice => parse_slice(next.into_inner())?,
Rule::unit_indexes => parse_unit_indexes(next.into_inner())?,
Rule::unit_keys => parse_unit_keys(next.into_inner())?,
Rule::filter => parse_filter_index(down(next)?)?,
_ => JsonPathIndex::Single(number_to_value(next.as_str())?),
};
Ok(parsed_index)
}
fn down(rule: Pair<Rule>) -> Result<Pair<Rule>, JsonPathParserError> {
let error_message = rule.to_string();
match rule.into_inner().next() {
Some(rule) => Ok(rule),
None => Err(JsonPathParserError::EmptyInner(error_message)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::macros::{chain, filter, idx, op};
use crate::path;
use serde_json::{json, Value};
use std::panic;
fn test_failed(input: &str) {
match parse_json_path::<Value>(input) {
Ok(elem) => panic!("should be false but got {:?}", elem),
Err(e) => println!("{}", e),
}
}
fn test<T>(input: &str, expected: Vec<JsonPath<T>>)
where
T: JsonLike,
{
match parse_json_path::<T>(input) {
Ok(JsonPath::Chain(elems)) => assert_eq!(elems, expected),
Ok(e) => panic!("unexpected value {:?}", e),
Err(e) => {
panic!("parsing error {}", e);
}
}
}
#[test]
fn path_test() {
test::<Value>("$.k.['k']['k']..k..['k'].*.[*][*][1][1,2]['k','k'][:][10:][:10][10:10:10][?(@)][?(@.abc >= 10)]",
vec![
path!($),
path!("k"),
path!("k"),
path!("k"),
path!(.."k"),
path!(.."k"),
path!(*),
path!(*),
path!(*),
path!(idx!(1)),
path!(idx!(idx 1,2)),
path!(idx!("k","k")),
path!(idx!([; ;])),
path!(idx!([10; ;])),
path!(idx!([;10;])),
path!(idx!([10;10;10])),
path!(idx!(?filter!(op!(chain!(path!(@path!()))), "exists", op!(path!())))),
path!(idx!(?filter!(op!(chain!(path!(@,path!("abc")))), ">=", op!(10)))),
]);
test::<Value>(
"$..*[?(@.isbn)].title",
vec![
// Root, DescentW, Index(Filter(Atom(Dynamic(Chain([Current(Chain([Field("isbn")]))])), Exists, Dynamic(Empty)))), Field("title")
path!($),
path!(..*),
path!(idx!(?filter!(op!(chain!(path!(@,path!("isbn")))), "exists", op!(path!())))),
path!("title"),
],
)
}
#[test]
fn descent_test() {
test::<Value>("..abc", vec![path!(.."abc")]);
test::<Value>("..['abc']", vec![path!(.."abc")]);
test_failed("...['abc']");
test_failed("...abc");
}
#[test]
fn field_test() {
test::<Value>(".abc", vec![path!("abc")]);
test::<Value>(".['abc']", vec![path!("abc")]);
test::<Value>("['abc']", vec![path!("abc")]);
test::<Value>(".['abc\\\"abc']", vec![path!("abc\\\"abc")]);
test_failed(".abc()abc");
test_failed("..[abc]");
test_failed(".'abc'");
}
#[test]
fn wildcard_test() {
test::<Value>(".*", vec![path!(*)]);
test::<Value>(".[*]", vec![path!(*)]);
test::<Value>(".abc.*", vec![path!("abc"), path!(*)]);
test::<Value>(".abc.[*]", vec![path!("abc"), path!(*)]);
test::<Value>(".abc[*]", vec![path!("abc"), path!(*)]);
test::<Value>("..*", vec![path!(..*)]);
test_failed("abc*");
}
#[test]
fn index_single_test() {
test::<Value>("[1]", vec![path!(idx!(1))]);
test_failed("[-1]");
test_failed("[1a]");
}
#[test]
fn index_slice_test() {
test::<Value>("[1:1000:10]", vec![path!(idx!([1; 1000; 10]))]);
test::<Value>("[:1000:10]", vec![path!(idx!([0; 1000; 10]))]);
test::<Value>("[:1000]", vec![path!(idx!([;1000;]))]);
test::<Value>("[:]", vec![path!(idx!([;;]))]);
test::<Value>("[::10]", vec![path!(idx!([;;10]))]);
test_failed("[::-1]");
test_failed("[:::0]");
}
#[test]
fn index_union_test() {
test::<Value>("[1,2,3]", vec![path!(idx!(idx 1,2,3))]);
test::<Value>("['abc','bcd']", vec![path!(idx!("abc", "bcd"))]);
test_failed("[]");
test::<Value>("[-1,-2]", vec![path!(idx!(idx - 1, -2))]);
test_failed("[abc,bcd]");
test::<Value>("[\"abc\",\"bcd\"]", vec![path!(idx!("abc", "bcd"))]);
}
#[test]
fn array_start_test() {
test::<Value>(
"$.[?(@.verb== \"TEST\")]",
vec![
path!($),
path!(idx!(?filter!(op!(chain!(path!(@,path!("verb")))),"==",op!("TEST")))),
],
);
}
#[test]
fn logical_filter_test() {
test::<Value>(
"$.[?(@.verb == 'T' || @.size > 0 && @.size < 10)]",
vec![
path!($),
path!(idx!(?
filter!(
filter!(op!(chain!(path!(@,path!("verb")))), "==", op!("T")),
||,
filter!(
filter!(op!(chain!(path!(@,path!("size")))), ">", op!(0)),
&&,
filter!(op!(chain!(path!(@,path!("size")))), "<", op!(10))
)
))),
],
);
test::<Value>(
"$.[?((@.verb == 'T' || @.size > 0) && @.size < 10)]",
vec![
path!($),
path!(idx!(?
filter!(
filter!(
filter!(op!(chain!(path!(@,path!("verb")))), "==", op!("T")),
||,
filter!(op!(chain!(path!(@,path!("size")))), ">", op!(0))
),
&&,
filter!(op!(chain!(path!(@,path!("size")))), "<", op!(10))
))),
],
);
test::<Value>(
"$.[?(@.verb == 'T' || @.size > 0 && @.size < 10 && @.elem == 0)]",
vec![
path!($),
path!(idx!(?filter!(
filter!(op!(chain!(path!(@,path!("verb")))), "==", op!("T")),
||,
filter!(
filter!(
filter!(op!(chain!(path!(@,path!("size")))), ">", op!(0)),
&&,
filter!(op!(chain!(path!(@,path!("size")))), "<", op!(10))
),
&&,
filter!(op!(chain!(path!(@,path!("elem")))), "==", op!(0))
)
))),
],
);
}
#[test]
fn index_filter_test() {
test::<Value>(
"[?('abc' == 'abc')]",
vec![path!(idx!(?filter!(op!("abc"),"==",op!("abc") )))],
);
test::<Value>(
"[?('abc' == 1)]",
vec![path!(idx!(?filter!( op!("abc"),"==",op!(1))))],
);
test::<Value>(
"[?('abc' == true)]",
vec![path!(idx!(?filter!( op!("abc"),"==",op!(true))))],
);
test::<Value>(
"[?('abc' == null)]",
vec![path!(
idx!(?filter!( op!("abc"),"==",Operand::Static(Value::Null)))
)],
);
test::<Value>(
"[?(@.abc in ['abc','bcd'])]",
vec![path!(
idx!(?filter!(op!(chain!(path!(@,path!("abc")))),"in",Operand::val(json!(["abc","bcd"]))))
)],
);
test::<Value>(
"[?(@.abc.[*] in ['abc','bcd'])]",
vec![path!(idx!(?filter!(
op!(chain!(path!(@,path!("abc"), path!(*)))),
"in",
op!(s json!(["abc","bcd"]))
)))],
);
test::<Value>(
"[?(@.[*]..next in ['abc','bcd'])]",
vec![path!(idx!(?filter!(
op!(chain!(path!(@,path!(*), path!(.."next")))),
"in",
op!(s json!(["abc","bcd"]))
)))],
);
test::<Value>(
"[?(@[1] in ['abc','bcd'])]",
vec![path!(idx!(?filter!(
op!(chain!(path!(@,path!(idx!(1))))),
"in",
op!(s json!(["abc","bcd"]))
)))],
);
test::<Value>(
"[?(@ == 'abc')]",
vec![path!(idx!(?filter!(
op!(chain!(path!(@path!()))),"==",op!("abc")
)))],
);
test::<Value>(
"[?(@ subsetOf ['abc'])]",
vec![path!(idx!(?filter!(
op!(chain!(path!(@path!()))),"subsetOf",op!(s json!(["abc"]))
)))],
);
test::<Value>(
"[?(@[1] subsetOf ['abc','abc'])]",
vec![path!(idx!(?filter!(
op!(chain!(path!(@,path!(idx!(1))))),
"subsetOf",
op!(s json!(["abc","abc"]))
)))],
);
test::<Value>(
"[?(@ subsetOf [1,2,3])]",
vec![path!(idx!(?filter!(
op!(chain!(path!(@path!()))),"subsetOf",op!(s json!([1,2,3]))
)))],
);
test_failed("[?(@[1] subsetof ['abc','abc'])]");
test_failed("[?(@ >< ['abc','abc'])]");
test_failed("[?(@ in {\"abc\":1})]");
}
#[test]
fn fn_size_test() {
test::<Value>(
"$.k.length()",
vec![path!($), path!("k"), JsonPath::Fn(Function::Length)],
);
test::<Value>(
"$.k.length.field",
vec![path!($), path!("k"), path!("length"), path!("field")],
)
}
#[test]
fn parser_error_test_invalid_rule() {
let result = parse_json_path::<Value>("notapath");
assert!(result.is_err());
assert!(result
.err()
.unwrap()
.to_string()
.starts_with("Failed to parse rule"));
}
#[test]
fn parser_error_test_empty_rule() {
let result = parse_json_path::<Value>("");
assert!(result.is_err());
assert!(result
.err()
.unwrap()
.to_string()
.starts_with("Failed to parse rule"));
}
}

907
vendor/jsonpath-rust/src/path/index.rs vendored Normal file
View File

@@ -0,0 +1,907 @@
use std::fmt::Debug;
use crate::jsp_idx;
use crate::parser::model::{FilterExpression, FilterSign, JsonPath};
use crate::path::top::ObjectField;
use crate::path::{json_path_instance, process_operand, JsonPathValue, Path, PathInstance};
use crate::JsonPathValue::{NoValue, Slice};
use super::{JsonLike, TopPaths};
/// process the slice like [start:end:step]
#[derive(Debug)]
pub struct ArraySlice<T> {
start_index: i32,
end_index: i32,
step: usize,
_t: std::marker::PhantomData<T>,
}
impl<T> ArraySlice<T> {
pub(crate) fn new(start_index: i32, end_index: i32, step: usize) -> Self {
ArraySlice {
start_index,
end_index,
step,
_t: std::marker::PhantomData,
}
}
fn end(&self, len: i32) -> Option<usize> {
if self.end_index >= 0 {
if self.end_index > len {
None
} else {
Some(self.end_index as usize)
}
} else if self.end_index < -len {
None
} else {
Some((len - (-self.end_index)) as usize)
}
}
fn start(&self, len: i32) -> Option<usize> {
if self.start_index >= 0 {
if self.start_index > len {
None
} else {
Some(self.start_index as usize)
}
} else if self.start_index < -len {
None
} else {
Some((len - -self.start_index) as usize)
}
}
fn process<'a, F>(&self, elements: &'a [F]) -> Vec<(&'a F, usize)> {
let len = elements.len() as i32;
let mut filtered_elems: Vec<(&'a F, usize)> = vec![];
match (self.start(len), self.end(len)) {
(Some(start_idx), Some(end_idx)) => {
let end_idx = if end_idx == 0 {
elements.len()
} else {
end_idx
};
for idx in (start_idx..end_idx).step_by(self.step) {
if let Some(v) = elements.get(idx) {
filtered_elems.push((v, idx))
}
}
filtered_elems
}
_ => filtered_elems,
}
}
}
impl<'a, T> Path<'a> for ArraySlice<T>
where
T: JsonLike,
{
type Data = T;
fn find(&self, input: JsonPathValue<'a, Self::Data>) -> Vec<JsonPathValue<'a, Self::Data>> {
input.flat_map_slice(|data, pref| {
data.as_array()
.map(|elems| self.process(elems))
.and_then(|v| {
if v.is_empty() {
None
} else {
let v = v.into_iter().map(|(e, i)| (e, jsp_idx(&pref, i))).collect();
Some(JsonPathValue::map_vec(v))
}
})
.unwrap_or_else(|| vec![NoValue])
})
}
}
/// process the simple index like [index]
pub struct ArrayIndex<T> {
index: usize,
_t: std::marker::PhantomData<T>,
}
impl<T> ArrayIndex<T> {
pub(crate) fn new(index: usize) -> Self {
ArrayIndex {
index,
_t: std::marker::PhantomData,
}
}
}
impl<'a, T> Path<'a> for ArrayIndex<T>
where
T: JsonLike,
{
type Data = T;
fn find(&self, input: JsonPathValue<'a, Self::Data>) -> Vec<JsonPathValue<'a, Self::Data>> {
input.flat_map_slice(|data, pref| {
data.as_array()
.and_then(|elems| elems.get(self.index))
.map(|e| vec![JsonPathValue::new_slice(e, jsp_idx(&pref, self.index))])
.unwrap_or_else(|| vec![NoValue])
})
}
}
/// process @ element
pub struct Current<'a, T> {
tail: Option<PathInstance<'a, T>>,
_t: std::marker::PhantomData<T>,
}
impl<'a, T> Current<'a, T>
where
T: JsonLike,
{
pub(crate) fn from(jp: &'a JsonPath<T>, root: &'a T) -> Self {
match jp {
JsonPath::Empty => Current::none(),
tail => Current::new(Box::new(json_path_instance(tail, root))),
}
}
pub(crate) fn new(tail: PathInstance<'a, T>) -> Self {
Current {
tail: Some(tail),
_t: std::marker::PhantomData,
}
}
pub(crate) fn none() -> Self {
Current {
tail: None,
_t: std::marker::PhantomData,
}
}
}
impl<'a, T> Path<'a> for Current<'a, T>
where
T: JsonLike,
{
type Data = T;
fn find(&self, input: JsonPathValue<'a, Self::Data>) -> Vec<JsonPathValue<'a, Self::Data>> {
self.tail
.as_ref()
.map(|p| p.find(input.clone()))
.unwrap_or_else(|| vec![input])
}
}
/// the list of indexes like [1,2,3]
pub struct UnionIndex<'a, T> {
indexes: Vec<TopPaths<'a, T>>,
}
impl<'a, T> UnionIndex<'a, T>
where
T: JsonLike,
{
pub fn from_indexes(elems: &'a [T]) -> Self {
let mut indexes: Vec<TopPaths<'a, T>> = vec![];
for idx in elems.iter() {
indexes.push(TopPaths::ArrayIndex(ArrayIndex::new(
idx.as_u64().unwrap() as usize
)))
}
UnionIndex::new(indexes)
}
pub fn from_keys(elems: &'a [String]) -> Self {
let mut indexes: Vec<TopPaths<'a, T>> = vec![];
for key in elems.iter() {
indexes.push(TopPaths::ObjectField(ObjectField::new(key)))
}
UnionIndex::new(indexes)
}
pub fn new(indexes: Vec<TopPaths<'a, T>>) -> Self {
UnionIndex { indexes }
}
}
impl<'a, T> Path<'a> for UnionIndex<'a, T>
where
T: JsonLike,
{
type Data = T;
fn find(&self, input: JsonPathValue<'a, Self::Data>) -> Vec<JsonPathValue<'a, Self::Data>> {
self.indexes
.iter()
.flat_map(|e| e.find(input.clone()))
.collect()
}
}
/// process filter element like [?(op sign op)]
pub enum FilterPath<'a, T> {
Filter {
left: PathInstance<'a, T>,
right: PathInstance<'a, T>,
op: &'a FilterSign,
},
Or {
left: PathInstance<'a, T>,
right: PathInstance<'a, T>,
},
And {
left: PathInstance<'a, T>,
right: PathInstance<'a, T>,
},
Not {
exp: PathInstance<'a, T>,
},
}
impl<'a, T> FilterPath<'a, T>
where
T: JsonLike,
{
pub(crate) fn new(expr: &'a FilterExpression<T>, root: &'a T) -> Self {
match expr {
FilterExpression::Atom(left, op, right) => FilterPath::Filter {
left: process_operand(left, root),
right: process_operand(right, root),
op,
},
FilterExpression::And(l, r) => FilterPath::And {
left: Box::new(FilterPath::new(l, root)),
right: Box::new(FilterPath::new(r, root)),
},
FilterExpression::Or(l, r) => FilterPath::Or {
left: Box::new(FilterPath::new(l, root)),
right: Box::new(FilterPath::new(r, root)),
},
FilterExpression::Not(exp) => FilterPath::Not {
exp: Box::new(FilterPath::new(exp, root)),
},
}
}
fn compound(
one: &'a FilterSign,
two: &'a FilterSign,
left: Vec<JsonPathValue<T>>,
right: Vec<JsonPathValue<T>>,
) -> bool {
FilterPath::process_atom(one, left.clone(), right.clone())
|| FilterPath::process_atom(two, left, right)
}
fn process_atom(
op: &'a FilterSign,
left: Vec<JsonPathValue<T>>,
right: Vec<JsonPathValue<T>>,
) -> bool {
match op {
FilterSign::Equal => <T as JsonLike>::eq(
JsonPathValue::vec_as_data(left),
JsonPathValue::vec_as_data(right),
),
FilterSign::Unequal => !FilterPath::process_atom(&FilterSign::Equal, left, right),
FilterSign::Less => <T as JsonLike>::less(
JsonPathValue::vec_as_data(left),
JsonPathValue::vec_as_data(right),
),
FilterSign::LeOrEq => {
FilterPath::compound(&FilterSign::Less, &FilterSign::Equal, left, right)
}
FilterSign::Greater => <T as JsonLike>::less(
JsonPathValue::vec_as_data(right),
JsonPathValue::vec_as_data(left),
),
FilterSign::GrOrEq => {
FilterPath::compound(&FilterSign::Greater, &FilterSign::Equal, left, right)
}
FilterSign::Regex => <T as JsonLike>::regex(
JsonPathValue::vec_as_data(left),
JsonPathValue::vec_as_data(right),
),
FilterSign::In => <T as JsonLike>::inside(
JsonPathValue::vec_as_data(left),
JsonPathValue::vec_as_data(right),
),
FilterSign::Nin => !FilterPath::process_atom(&FilterSign::In, left, right),
FilterSign::NoneOf => !FilterPath::process_atom(&FilterSign::AnyOf, left, right),
FilterSign::AnyOf => <T as JsonLike>::any_of(
JsonPathValue::vec_as_data(left),
JsonPathValue::vec_as_data(right),
),
FilterSign::SubSetOf => <T as JsonLike>::sub_set_of(
JsonPathValue::vec_as_data(left),
JsonPathValue::vec_as_data(right),
),
FilterSign::Exists => !JsonPathValue::vec_as_data(left).is_empty(),
FilterSign::Size => <T as JsonLike>::size(
JsonPathValue::vec_as_data(left),
JsonPathValue::vec_as_data(right),
),
}
}
fn process(&self, curr_el: &'a T) -> bool {
let pref = String::new();
match self {
FilterPath::Filter { left, right, op } => FilterPath::process_atom(
op,
left.find(Slice(curr_el, pref.clone())),
right.find(Slice(curr_el, pref)),
),
FilterPath::Or { left, right } => {
if !JsonPathValue::vec_as_data(left.find(Slice(curr_el, pref.clone()))).is_empty() {
true
} else {
!JsonPathValue::vec_as_data(right.find(Slice(curr_el, pref))).is_empty()
}
}
FilterPath::And { left, right } => {
if JsonPathValue::vec_as_data(left.find(Slice(curr_el, pref.clone()))).is_empty() {
false
} else {
!JsonPathValue::vec_as_data(right.find(Slice(curr_el, pref))).is_empty()
}
}
FilterPath::Not { exp } => {
JsonPathValue::vec_as_data(exp.find(Slice(curr_el, pref))).is_empty()
}
}
}
}
impl<'a, T> Path<'a> for FilterPath<'a, T>
where
T: JsonLike,
{
type Data = T;
fn find(&self, input: JsonPathValue<'a, Self::Data>) -> Vec<JsonPathValue<'a, Self::Data>> {
input.flat_map_slice(|data, pref| {
let mut res = vec![];
if data.is_array() {
let elems = data.as_array().unwrap();
for (i, el) in elems.iter().enumerate() {
if self.process(el) {
res.push(Slice(el, jsp_idx(&pref, i)))
}
}
} else if self.process(data) {
res.push(Slice(data, pref))
}
// match data {
// Array(elems) => {
// for (i, el) in elems.iter().enumerate() {
// if self.process(el) {
// res.push(Slice(el, jsp_idx(&pref, i)))
// }
// }
// }
// el => {
// if self.process(el) {
// res.push(Slice(el, pref))
// }
// }
// }
if res.is_empty() {
vec![NoValue]
} else {
res
}
})
}
}
#[cfg(test)]
mod tests {
use crate::jp_v;
use crate::parser::macros::{chain, filter, idx, op};
use crate::parser::model::{FilterExpression, FilterSign, JsonPath, JsonPathIndex, Operand};
use crate::path::index::{ArrayIndex, ArraySlice};
use crate::path::JsonPathValue;
use crate::path::{json_path_instance, Path};
use crate::JsonPathValue::NoValue;
use crate::path;
use serde_json::{json, Value};
#[test]
fn array_slice_end_start_test() {
let array = [0, 1, 2, 3, 4, 5];
let len = array.len() as i32;
let mut slice: ArraySlice<Value> = ArraySlice::new(0, 0, 0);
assert_eq!(slice.start(len).unwrap(), 0);
slice.start_index = 1;
assert_eq!(slice.start(len).unwrap(), 1);
slice.start_index = 2;
assert_eq!(slice.start(len).unwrap(), 2);
slice.start_index = 5;
assert_eq!(slice.start(len).unwrap(), 5);
slice.start_index = 7;
assert_eq!(slice.start(len), None);
slice.start_index = -1;
assert_eq!(slice.start(len).unwrap(), 5);
slice.start_index = -5;
assert_eq!(slice.start(len).unwrap(), 1);
slice.end_index = 0;
assert_eq!(slice.end(len).unwrap(), 0);
slice.end_index = 5;
assert_eq!(slice.end(len).unwrap(), 5);
slice.end_index = -1;
assert_eq!(slice.end(len).unwrap(), 5);
slice.end_index = -5;
assert_eq!(slice.end(len).unwrap(), 1);
}
#[test]
fn slice_test() {
let array = json!([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
let mut slice = ArraySlice::new(0, 6, 2);
let j1 = json!(0);
let j2 = json!(2);
let j4 = json!(4);
assert_eq!(
slice.find(JsonPathValue::new_slice(&array, "a".to_string())),
jp_v![&j1;"a[0]", &j2;"a[2]", &j4;"a[4]"]
);
slice.step = 3;
let j0 = json!(0);
let j3 = json!(3);
assert_eq!(slice.find(jp_v!(&array)), jp_v![&j0;"[0]", &j3;"[3]"]);
slice.start_index = -1;
slice.end_index = 1;
assert_eq!(
slice.find(JsonPathValue::new_slice(&array, "a".to_string())),
vec![NoValue]
);
slice.start_index = -10;
slice.end_index = 10;
let j1 = json!(1);
let j4 = json!(4);
let j7 = json!(7);
assert_eq!(
slice.find(JsonPathValue::new_slice(&array, "a".to_string())),
jp_v![&j1;"a[1]", &j4;"a[4]", &j7;"a[7]"]
);
}
#[test]
fn index_test() {
let array = json!([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
let mut index = ArrayIndex::new(0);
let j0 = json!(0);
let j10 = json!(10);
assert_eq!(
index.find(JsonPathValue::new_slice(&array, "a".to_string())),
jp_v![&j0;"a[0]",]
);
index.index = 10;
assert_eq!(
index.find(JsonPathValue::new_slice(&array, "a".to_string())),
jp_v![&j10;"a[10]",]
);
index.index = 100;
assert_eq!(
index.find(JsonPathValue::new_slice(&array, "a".to_string())),
vec![NoValue]
);
}
#[test]
fn current_test() {
let json = json!(
{
"object":{
"field_1":[1,2,3],
"field_2":42,
"field_3":{"a":"b"}
}
});
let chain = chain!(path!($), path!("object"), path!(@));
let path_inst = json_path_instance(&chain, &json);
let res = json!({
"field_1":[1,2,3],
"field_2":42,
"field_3":{"a":"b"}
});
let expected_res = jp_v!(&res;"$.['object']",);
assert_eq!(path_inst.find(jp_v!(&json)), expected_res);
let cur = path!(@,path!("field_3"),path!("a"));
let chain = chain!(path!($), path!("object"), cur);
let path_inst = json_path_instance(&chain, &json);
let res1 = json!("b");
let expected_res = vec![JsonPathValue::new_slice(
&res1,
"$.['object'].['field_3'].['a']".to_string(),
)];
assert_eq!(path_inst.find(jp_v!(&json)), expected_res);
}
#[test]
fn filter_exist_test() {
let json = json!({
"threshold" : 3,
"key":[{"field":[1,2,3,4,5],"field1":[7]},{"field":42}],
});
let index = path!(idx!(?filter!(op!(path!(@, path!("field"))), "exists", op!())));
let chain = chain!(path!($), path!("key"), index, path!("field"));
let path_inst = json_path_instance(&chain, &json);
let exp1 = json!([1, 2, 3, 4, 5]);
let exp2 = json!(42);
let expected_res = jp_v!(&exp1;"$.['key'][0].['field']",&exp2;"$.['key'][1].['field']");
assert_eq!(path_inst.find(jp_v!(&json)), expected_res)
}
#[test]
fn filter_gr_test() {
let json = json!({
"threshold" : 4,
"key":[
{"field":1},
{"field":10},
{"field":4},
{"field":5},
{"field":1},
]
});
let _exp1 = json!( {"field":10});
let _exp2 = json!( {"field":5});
let exp3 = json!( {"field":4});
let exp4 = json!( {"field":1});
let index = path!(
idx!(?filter!(op!(path!(@, path!("field"))), ">", op!(chain!(path!($), path!("threshold")))))
);
let chain = chain!(path!($), path!("key"), index);
let path_inst = json_path_instance(&chain, &json);
let exp1 = json!( {"field":10});
let exp2 = json!( {"field":5});
let expected_res = jp_v![&exp1;"$.['key'][1]", &exp2;"$.['key'][3]"];
assert_eq!(
path_inst.find(JsonPathValue::from_root(&json)),
expected_res
);
let expected_res = jp_v![&exp1;"$.['key'][1]", &exp2;"$.['key'][3]"];
assert_eq!(
path_inst.find(JsonPathValue::from_root(&json)),
expected_res
);
let index = path!(
idx!(?filter!(op!(path!(@, path!("field"))), ">=", op!(chain!(path!($), path!("threshold")))))
);
let chain = chain!(path!($), path!("key"), index);
let path_inst = json_path_instance(&chain, &json);
let expected_res = jp_v![
&exp1;"$.['key'][1]", &exp3;"$.['key'][2]", &exp2;"$.['key'][3]"];
assert_eq!(
path_inst.find(JsonPathValue::from_root(&json)),
expected_res
);
let index = path!(
idx!(?filter!(op!(path!(@, path!("field"))), "<", op!(chain!(path!($), path!("threshold")))))
);
let chain = chain!(path!($), path!("key"), index);
let path_inst = json_path_instance(&chain, &json);
let expected_res = jp_v![&exp4;"$.['key'][0]", &exp4;"$.['key'][4]"];
assert_eq!(
path_inst.find(JsonPathValue::from_root(&json)),
expected_res
);
let index = path!(
idx!(?filter!(op!(path!(@, path!("field"))), "<=", op!(chain!(path!($), path!("threshold")))))
);
let chain = chain!(path!($), path!("key"), index);
let path_inst = json_path_instance(&chain, &json);
let expected_res = jp_v![
&exp4;"$.['key'][0]",
&exp3;"$.['key'][2]",
&exp4;"$.['key'][4]"];
assert_eq!(
path_inst.find(JsonPathValue::from_root(&json)),
expected_res
);
}
#[test]
fn filter_regex_test() {
let json = json!({
"key":[
{"field":"a11#"},
{"field":"a1#1"},
{"field":"a#11"},
{"field":"#a11"},
]
});
let index = idx!(?filter!(op!(path!(@,path!("field"))),"~=", op!("[a-zA-Z]+[0-9]#[0-9]+")));
let chain = chain!(path!($), path!("key"), path!(index));
let path_inst = json_path_instance(&chain, &json);
let exp2 = json!( {"field":"a1#1"});
let expected_res = jp_v![&exp2;"$.['key'][1]",];
assert_eq!(
path_inst.find(JsonPathValue::from_root(&json)),
expected_res
)
}
#[test]
fn filter_any_of_test() {
let json = json!({
"key":[
{"field":"a11#"},
{"field":"a1#1"},
{"field":"a#11"},
{"field":"#a11"},
]
});
let index = idx!(?filter!(
op!(path!(@,path!("field"))),
"anyOf",
op!(s ["a11#","aaa","111"])
));
let chain = chain!(path!($), JsonPath::Field(String::from("key")), path!(index));
let path_inst = json_path_instance(&chain, &json);
let exp2 = json!( {"field":"a11#"});
let expected_res = jp_v![&exp2;"$.['key'][0]",];
assert_eq!(
path_inst.find(JsonPathValue::from_root(&json)),
expected_res
)
}
#[test]
fn size_test() {
let json = json!({
"key":[
{"field":"aaaa"},
{"field":"bbb"},
{"field":"cc"},
{"field":"dddd"},
{"field":[1,1,1,1]},
]
});
let index = idx!(?filter!(op!(path!(@, path!("field"))),"size",op!(4)));
let chain = chain!(path!($), path!("key"), path!(index));
let path_inst = json_path_instance(&chain, &json);
let f1 = json!( {"field":"aaaa"});
let f2 = json!( {"field":"dddd"});
let f3 = json!( {"field":[1,1,1,1]});
let expected_res = jp_v![&f1;"$.['key'][0]", &f2;"$.['key'][3]", &f3;"$.['key'][4]"];
assert_eq!(
path_inst.find(JsonPathValue::from_root(&json)),
expected_res
)
}
#[test]
fn nested_filter_test() {
let json = json!({
"obj":{
"id":1,
"not_id": 2,
"more_then_id" :3
}
});
let index = idx!(?filter!(
op!(path!(@,path!("not_id"))), "==",op!(2)
));
let chain = chain!(path!($), path!("obj"), path!(index));
let path_inst = json_path_instance(&chain, &json);
let js = json!({
"id":1,
"not_id": 2,
"more_then_id" :3
});
assert_eq!(
path_inst.find(JsonPathValue::from_root(&json)),
jp_v![&js;"$.['obj']",]
)
}
#[test]
fn or_arr_test() {
let json = json!({
"key":[
{"city":"London","capital":true, "size": "big"},
{"city":"Berlin","capital":true,"size": "big"},
{"city":"Tokyo","capital":true,"size": "big"},
{"city":"Moscow","capital":true,"size": "big"},
{"city":"Athlon","capital":false,"size": "small"},
{"city":"Dortmund","capital":false,"size": "big"},
{"city":"Dublin","capital":true,"size": "small"},
]
});
let index = idx!(?filter!(
filter!(op!(path!(@,path!("capital"))), "==", op!(false)),
||,
filter!(op!(path!(@,path!("size"))), "==", op!("small"))
)
);
let chain = chain!(path!($), path!("key"), path!(index), path!("city"));
let path_inst = json_path_instance(&chain, &json);
let a = json!("Athlon");
let d = json!("Dortmund");
let dd = json!("Dublin");
assert_eq!(
path_inst.find(JsonPathValue::from_root(&json)),
jp_v![
&a;"$.['key'][4].['city']",
&d;"$.['key'][5].['city']",
&dd;"$.['key'][6].['city']"]
)
}
#[test]
fn or_obj_test() {
let json = json!({
"key":{
"id":1,
"name":"a",
"another":"b"
}
});
let index = idx!(?filter!(
filter!(op!(path!(@,path!("name"))), "==", op!("a")),
||,
filter!(op!(path!(@,path!("another"))), "==", op!("b"))
)
);
let chain = chain!(path!($), path!("key"), path!(index), path!("id"));
let path_inst = json_path_instance(&chain, &json);
let j1 = json!(1);
assert_eq!(
path_inst.find(JsonPathValue::from_root(&json)),
jp_v![&j1;"$.['key'].['id']",]
)
}
#[test]
fn or_obj_2_test() {
let json = json!({
"key":{
"id":1,
"name":"a",
"another":"d"
}
});
let index = idx!(?filter!(
filter!(op!(path!(@,path!("name"))), "==", op!("c")),
||,
filter!(op!(path!(@,path!("another"))), "==", op!("d"))
)
);
let chain = chain!(path!($), path!("key"), path!(index), path!("id"));
let path_inst = json_path_instance(&chain, &json);
let j1 = json!(1);
assert_eq!(
path_inst.find(JsonPathValue::from_root(&json)),
jp_v![&j1;"$.['key'].['id']",]
)
}
#[test]
fn and_arr_test() {
let json = json!({
"key":[
{"city":"London","capital":true, "size": "big"},
{"city":"Berlin","capital":true,"size": "big"},
{"city":"Tokyo","capital":true,"size": "big"},
{"city":"Moscow","capital":true,"size": "big"},
{"city":"Athlon","capital":false,"size": "small"},
{"city":"Dortmund","capital":false,"size": "big"},
{"city":"Dublin","capital":true,"size": "small"},
]
});
let index = idx!(?filter!(
filter!(op!(path!(@,path!("capital"))), "==", op!(false)),
&&,
filter!(op!(path!(@,path!("size"))), "==", op!("small"))
)
);
let chain = chain!(path!($), path!("key"), path!(index), path!("city"));
let path_inst = json_path_instance(&chain, &json);
let a = json!("Athlon");
let value = jp_v!( &a;"$.['key'][4].['city']",);
assert_eq!(path_inst.find(JsonPathValue::from_root(&json)), value)
}
#[test]
fn and_obj_test() {
let json = json!({
"key":{
"id":1,
"name":"a",
"another":"b"
}
});
let index = idx!(?filter!(
filter!(op!(path!(@,path!("name"))), "==", op!("a")),
&&,
filter!(op!(path!(@,path!("another"))), "==", op!("b"))
)
);
let chain = chain!(path!($), path!("key"), path!(index), path!("id"));
let path_inst = json_path_instance(&chain, &json);
let j1 = json!(1);
assert_eq!(
path_inst.find(JsonPathValue::from_root(&json)),
jp_v![&j1; "$.['key'].['id']",]
)
}
#[test]
fn and_obj_2_test() {
let json = json!({
"key":{
"id":1,
"name":"a",
"another":"d"
}
});
let index = idx!(?filter!(
filter!(op!(path!(@,path!("name"))), "==", op!("c")),
&&,
filter!(op!(path!(@,path!("another"))), "==", op!("d"))
)
);
let chain = chain!(path!($), path!("key"), path!(index), path!("id"));
let path_inst = json_path_instance(&chain, &json);
assert_eq!(
path_inst.find(JsonPathValue::from_root(&json)),
vec![NoValue]
)
}
}

812
vendor/jsonpath-rust/src/path/mod.rs vendored Normal file
View File

@@ -0,0 +1,812 @@
use std::fmt::Debug;
use crate::{jsp_idx, jsp_obj, JsonPathParserError, JsonPathStr, JsonPathValue};
use regex::Regex;
use serde_json::{json, Value};
use crate::parser::model::{Function, JsonPath, JsonPathIndex, Operand};
use crate::parser::parse_json_path;
pub use crate::path::index::{ArrayIndex, ArraySlice, Current, FilterPath, UnionIndex};
pub use crate::path::top::ObjectField;
use crate::path::top::*;
/// The module is in charge of processing [[JsonPathIndex]] elements
mod index;
/// The module is responsible for processing of the [[JsonPath]] elements
mod top;
/// The `JsonLike` trait defines a set of methods and associated types for working with JSON-like data structures.
///
/// It provides a common interface for accessing and manipulating JSON data,
/// allowing for operations such as
/// - retrieving values by key,
/// - iterating over elements
/// - performing various comparisons and transformations.
///
/// The trait is implemented for the `serde_json::Value` type already
pub trait JsonLike:
Default
+ Clone
+ Debug
+ for<'a> From<&'a str>
+ From<Vec<String>>
+ From<bool>
+ From<i64>
+ From<f64>
+ From<Vec<Self>>
+ From<String>
+ PartialEq
+ 'static
{
/// Retrieves a reference to the value associated with the given key.
fn get(&self, key: &str) -> Option<&Self>;
/// Iterates over the elements with a given prefix and returns a vector of `JsonPathValue`.
fn itre(&self, pref: String) -> Vec<JsonPathValue<'_, Self>>;
/// Returns the length of the array as a `JsonPathValue`.
fn array_len(&self) -> JsonPathValue<'static, Self>;
/// Initializes an instance with a specific size.
fn init_with_usize(cnt: usize) -> Self;
/// Flattens nested structures and returns a vector of tuples containing references to the elements and their paths.
fn deep_flatten(&self, pref: String) -> Vec<(&Self, String)>;
/// Performs a deep search by key and returns a vector of tuples containing references to the elements and their paths.
fn deep_path_by_key<'a>(
&'a self,
key: ObjectField<'a, Self>,
pref: String,
) -> Vec<(&'a Self, String)>;
/// Converts the element to an `Option<u64>`.
fn as_u64(&self) -> Option<u64>;
/// Checks if the element is an array.
fn is_array(&self) -> bool;
/// Converts the element to an `Option<&Vec<Self>>`.
fn as_array(&self) -> Option<&Vec<Self>>;
/// Compares the size of two vectors of references to elements.
fn size(left: Vec<&Self>, right: Vec<&Self>) -> bool;
/// Checks if the left vector is a subset of the right vector.
fn sub_set_of(left: Vec<&Self>, right: Vec<&Self>) -> bool;
/// Checks if any element in the left vector is present in the right vector.
fn any_of(left: Vec<&Self>, right: Vec<&Self>) -> bool;
/// Checks if the elements in the left vector match the regex pattern in the right vector.
fn regex(left: Vec<&Self>, right: Vec<&Self>) -> bool;
/// Checks if any element in the left vector is inside the right vector.
fn inside(left: Vec<&Self>, right: Vec<&Self>) -> bool;
/// Ensures the number on the left side is less than the number on the right side.
fn less(left: Vec<&Self>, right: Vec<&Self>) -> bool;
/// Compares elements for equality.
fn eq(left: Vec<&Self>, right: Vec<&Self>) -> bool;
/// Returns a null value.
fn null() -> Self;
/// Creates an array from a vector of elements.
fn array(data: Vec<Self>) -> Self;
/// Retrieves a reference to the element at the specified path.
/// The path is specified as a string and can be obtained from the query.
///
/// # Arguments
/// * `path` - A json path to the element specified as a string (root, field, index only).
fn reference<T>(&self, path: T) -> Result<Option<&Self>, JsonPathParserError>
where
T: Into<JsonPathStr>;
/// Retrieves a mutable reference to the element at the specified path.
///
/// # Arguments
/// * `path` - A json path to the element specified as a string (root, field, index only).
///
/// # Examples
///
/// ```
/// use serde_json::json;
/// use jsonpath_rust::{JsonPath, JsonPathParserError};
/// use jsonpath_rust::path::JsonLike;
///
/// let mut json = json!([
/// {"verb": "RUN","distance":[1]},
/// {"verb": "TEST"},
/// {"verb": "DO NOT RUN"}
/// ]);
///
/// let path: Box<JsonPath> = Box::from(JsonPath::try_from("$.[?(@.verb == 'RUN')]").unwrap());
/// let elem = path
/// .find_as_path(&json)
/// .get(0)
/// .cloned()
/// .ok_or(JsonPathParserError::InvalidJsonPath("".to_string())).unwrap();
///
/// if let Some(v) = json
/// .reference_mut(elem).unwrap()
/// .and_then(|v| v.as_object_mut())
/// .and_then(|v| v.get_mut("distance"))
/// .and_then(|v| v.as_array_mut())
/// {
/// v.push(json!(2))
/// }
///
/// assert_eq!(
/// json,
/// json!([
/// {"verb": "RUN","distance":[1,2]},
/// {"verb": "TEST"},
/// {"verb": "DO NOT RUN"}
/// ])
/// );
/// ```
fn reference_mut<T>(&mut self, path: T) -> Result<Option<&mut Self>, JsonPathParserError>
where
T: Into<JsonPathStr>;
}
impl JsonLike for Value {
fn get(&self, key: &str) -> Option<&Self> {
self.get(key)
}
fn itre(&self, pref: String) -> Vec<JsonPathValue<Self>> {
let res = match self {
Value::Array(elems) => {
let mut res = vec![];
for (idx, el) in elems.iter().enumerate() {
res.push(JsonPathValue::Slice(el, jsp_idx(&pref, idx)));
}
res
}
Value::Object(elems) => {
let mut res = vec![];
for (key, el) in elems.into_iter() {
res.push(JsonPathValue::Slice(el, jsp_obj(&pref, key)));
}
res
}
_ => vec![],
};
if res.is_empty() {
vec![JsonPathValue::NoValue]
} else {
res
}
}
fn array_len(&self) -> JsonPathValue<'static, Self> {
match self {
Value::Array(elems) => JsonPathValue::NewValue(json!(elems.len())),
_ => JsonPathValue::NoValue,
}
}
fn init_with_usize(cnt: usize) -> Self {
json!(cnt)
}
fn deep_flatten(&self, pref: String) -> Vec<(&Self, String)> {
let mut acc = vec![];
match self {
Value::Object(elems) => {
for (f, v) in elems.into_iter() {
let pref = jsp_obj(&pref, f);
acc.push((v, pref.clone()));
acc.append(&mut v.deep_flatten(pref));
}
}
Value::Array(elems) => {
for (i, v) in elems.iter().enumerate() {
let pref = jsp_idx(&pref, i);
acc.push((v, pref.clone()));
acc.append(&mut v.deep_flatten(pref));
}
}
_ => (),
}
acc
}
fn deep_path_by_key<'a>(
&'a self,
key: ObjectField<'a, Self>,
pref: String,
) -> Vec<(&'a Self, String)> {
let mut result: Vec<(&'a Value, String)> =
JsonPathValue::vec_as_pair(key.find(JsonPathValue::new_slice(self, pref.clone())));
match self {
Value::Object(elems) => {
let mut next_levels: Vec<(&'a Value, String)> = elems
.into_iter()
.flat_map(|(k, v)| v.deep_path_by_key(key.clone(), jsp_obj(&pref, k)))
.collect();
result.append(&mut next_levels);
result
}
Value::Array(elems) => {
let mut next_levels: Vec<(&'a Value, String)> = elems
.iter()
.enumerate()
.flat_map(|(i, v)| v.deep_path_by_key(key.clone(), jsp_idx(&pref, i)))
.collect();
result.append(&mut next_levels);
result
}
_ => result,
}
}
fn as_u64(&self) -> Option<u64> {
self.as_u64()
}
fn is_array(&self) -> bool {
self.is_array()
}
fn as_array(&self) -> Option<&Vec<Self>> {
self.as_array()
}
fn size(left: Vec<&Self>, right: Vec<&Self>) -> bool {
if let Some(Value::Number(n)) = right.first() {
if let Some(sz) = n.as_f64() {
for el in left.iter() {
match el {
Value::String(v) if v.len() == sz as usize => true,
Value::Array(elems) if elems.len() == sz as usize => true,
Value::Object(fields) if fields.len() == sz as usize => true,
_ => return false,
};
}
return true;
}
}
false
}
fn sub_set_of(left: Vec<&Self>, right: Vec<&Self>) -> bool {
if left.is_empty() {
return true;
}
if right.is_empty() {
return false;
}
if let Some(elems) = left.first().and_then(|e| e.as_array()) {
if let Some(Value::Array(right_elems)) = right.first() {
if right_elems.is_empty() {
return false;
}
for el in elems {
let mut res = false;
for r in right_elems.iter() {
if el.eq(r) {
res = true
}
}
if !res {
return false;
}
}
return true;
}
}
false
}
fn any_of(left: Vec<&Self>, right: Vec<&Self>) -> bool {
if left.is_empty() {
return true;
}
if right.is_empty() {
return false;
}
if let Some(Value::Array(elems)) = right.first() {
if elems.is_empty() {
return false;
}
for el in left.iter() {
if let Some(left_elems) = el.as_array() {
for l in left_elems.iter() {
for r in elems.iter() {
if l.eq(r) {
return true;
}
}
}
} else {
for r in elems.iter() {
if el.eq(&r) {
return true;
}
}
}
}
}
false
}
fn regex(left: Vec<&Self>, right: Vec<&Self>) -> bool {
if left.is_empty() || right.is_empty() {
return false;
}
match right.first() {
Some(Value::String(str)) => {
if let Ok(regex) = Regex::new(str) {
for el in left.iter() {
if let Some(v) = el.as_str() {
if regex.is_match(v) {
return true;
}
}
}
}
false
}
_ => false,
}
}
fn inside(left: Vec<&Self>, right: Vec<&Self>) -> bool {
if left.is_empty() {
return false;
}
match right.first() {
Some(Value::Array(elems)) => {
for el in left.iter() {
if elems.contains(el) {
return true;
}
}
false
}
Some(Value::Object(elems)) => {
for el in left.iter() {
for r in elems.values() {
if el.eq(&r) {
return true;
}
}
}
false
}
_ => false,
}
}
/// ensure the number on the left side is less the number on the right side
fn less(left: Vec<&Self>, right: Vec<&Self>) -> bool {
if left.len() == 1 && right.len() == 1 {
match (left.first(), right.first()) {
(Some(Value::Number(l)), Some(Value::Number(r))) => l
.as_f64()
.and_then(|v1| r.as_f64().map(|v2| v1 < v2))
.unwrap_or(false),
_ => false,
}
} else {
false
}
}
/// compare elements
fn eq(left: Vec<&Self>, right: Vec<&Self>) -> bool {
if left.len() != right.len() {
false
} else {
left.iter().zip(right).map(|(a, b)| a.eq(&b)).all(|a| a)
}
}
fn null() -> Self {
Value::Null
}
fn array(data: Vec<Self>) -> Self {
Value::Array(data)
}
fn reference<T>(&self, path: T) -> Result<Option<&Self>, JsonPathParserError>
where
T: Into<JsonPathStr>,
{
Ok(self.pointer(&path_to_json_path(path.into())?))
}
fn reference_mut<T>(&mut self, path: T) -> Result<Option<&mut Self>, JsonPathParserError>
where
T: Into<JsonPathStr>,
{
Ok(self.pointer_mut(&path_to_json_path(path.into())?))
}
}
fn path_to_json_path(path: JsonPathStr) -> Result<String, JsonPathParserError> {
convert_part(&parse_json_path::<Value>(path.as_str())?)
}
fn convert_part(path: &JsonPath) -> Result<String, JsonPathParserError> {
match path {
JsonPath::Chain(elems) => elems
.iter()
.map(convert_part)
.collect::<Result<String, JsonPathParserError>>(),
JsonPath::Index(JsonPathIndex::Single(v)) => Ok(format!("/{}", v)),
JsonPath::Field(e) => Ok(format!("/{}", e)),
JsonPath::Root => Ok("".to_string()),
e => Err(JsonPathParserError::InvalidJsonPath(e.to_string())),
}
}
/// The trait defining the behaviour of processing every separated element.
/// type Data usually stands for json [[Value]]
/// The trait also requires to have a root json to process.
/// It needs in case if in the filter there will be a pointer to the absolute path
pub trait Path<'a> {
type Data;
/// when every element needs to handle independently
fn find(&self, input: JsonPathValue<'a, Self::Data>) -> Vec<JsonPathValue<'a, Self::Data>> {
vec![input]
}
/// when the whole output needs to handle
fn flat_find(
&self,
input: Vec<JsonPathValue<'a, Self::Data>>,
_is_search_length: bool,
) -> Vec<JsonPathValue<'a, Self::Data>> {
input.into_iter().flat_map(|d| self.find(d)).collect()
}
/// defines when we need to invoke `find` or `flat_find`
fn needs_all(&self) -> bool {
false
}
}
/// all known Paths, mostly to avoid a dynamic Box and vtable for internal function
pub enum TopPaths<'a, T> {
RootPointer(RootPointer<'a, T>),
ObjectField(ObjectField<'a, T>),
Chain(Chain<'a, T>),
Wildcard(Wildcard<T>),
DescentObject(DescentObject<'a, T>),
DescentWildcard(DescentWildcard<T>),
Current(Current<'a, T>),
ArrayIndex(ArrayIndex<T>),
ArraySlice(ArraySlice<T>),
UnionIndex(UnionIndex<'a, T>),
FilterPath(FilterPath<'a, T>),
IdentityPath(IdentityPath<T>),
FnPath(FnPath<T>),
}
impl<'a, T> Path<'a> for TopPaths<'a, T>
where
T: JsonLike,
{
type Data = T;
fn find(&self, input: JsonPathValue<'a, Self::Data>) -> Vec<JsonPathValue<'a, Self::Data>> {
match self {
TopPaths::RootPointer(inner) => inner.find(input),
TopPaths::ObjectField(inner) => inner.find(input),
TopPaths::Chain(inner) => inner.find(input),
TopPaths::Wildcard(inner) => inner.find(input),
TopPaths::DescentObject(inner) => inner.find(input),
TopPaths::DescentWildcard(inner) => inner.find(input),
TopPaths::Current(inner) => inner.find(input),
TopPaths::ArrayIndex(inner) => inner.find(input),
TopPaths::ArraySlice(inner) => inner.find(input),
TopPaths::UnionIndex(inner) => inner.find(input),
TopPaths::FilterPath(inner) => inner.find(input),
TopPaths::IdentityPath(inner) => inner.find(input),
TopPaths::FnPath(inner) => inner.find(input),
}
}
fn flat_find(
&self,
input: Vec<JsonPathValue<'a, Self::Data>>,
_is_search_length: bool,
) -> Vec<JsonPathValue<'a, Self::Data>> {
match self {
TopPaths::RootPointer(inner) => inner.flat_find(input, _is_search_length),
TopPaths::ObjectField(inner) => inner.flat_find(input, _is_search_length),
TopPaths::Chain(inner) => inner.flat_find(input, _is_search_length),
TopPaths::Wildcard(inner) => inner.flat_find(input, _is_search_length),
TopPaths::DescentObject(inner) => inner.flat_find(input, _is_search_length),
TopPaths::DescentWildcard(inner) => inner.flat_find(input, _is_search_length),
TopPaths::Current(inner) => inner.flat_find(input, _is_search_length),
TopPaths::ArrayIndex(inner) => inner.flat_find(input, _is_search_length),
TopPaths::ArraySlice(inner) => inner.flat_find(input, _is_search_length),
TopPaths::UnionIndex(inner) => inner.flat_find(input, _is_search_length),
TopPaths::FilterPath(inner) => inner.flat_find(input, _is_search_length),
TopPaths::IdentityPath(inner) => inner.flat_find(input, _is_search_length),
TopPaths::FnPath(inner) => inner.flat_find(input, _is_search_length),
}
}
fn needs_all(&self) -> bool {
match self {
TopPaths::RootPointer(inner) => inner.needs_all(),
TopPaths::ObjectField(inner) => inner.needs_all(),
TopPaths::Chain(inner) => inner.needs_all(),
TopPaths::Wildcard(inner) => inner.needs_all(),
TopPaths::DescentObject(inner) => inner.needs_all(),
TopPaths::DescentWildcard(inner) => inner.needs_all(),
TopPaths::Current(inner) => inner.needs_all(),
TopPaths::ArrayIndex(inner) => inner.needs_all(),
TopPaths::ArraySlice(inner) => inner.needs_all(),
TopPaths::UnionIndex(inner) => inner.needs_all(),
TopPaths::FilterPath(inner) => inner.needs_all(),
TopPaths::IdentityPath(inner) => inner.needs_all(),
TopPaths::FnPath(inner) => inner.needs_all(),
}
}
}
/// The basic type for instances.
pub(crate) type PathInstance<'a, T> = Box<dyn Path<'a, Data = T> + 'a>;
/// The major method to process the top part of json part
pub(crate) fn json_path_instance<'a, T>(json_path: &'a JsonPath<T>, root: &'a T) -> TopPaths<'a, T>
where
T: JsonLike,
{
match json_path {
JsonPath::Root => TopPaths::RootPointer(RootPointer::new(root)),
JsonPath::Field(key) => TopPaths::ObjectField(ObjectField::new(key)),
JsonPath::Chain(chain) => TopPaths::Chain(Chain::from(chain, root)),
JsonPath::Wildcard => TopPaths::Wildcard(Wildcard::new()),
JsonPath::Descent(key) => TopPaths::DescentObject(DescentObject::new(key)),
JsonPath::DescentW => TopPaths::DescentWildcard(DescentWildcard::new()),
JsonPath::Current(value) => TopPaths::Current(Current::from(value, root)),
JsonPath::Index(JsonPathIndex::Single(index)) => {
TopPaths::ArrayIndex(ArrayIndex::new(index.as_u64().unwrap() as usize))
}
JsonPath::Index(JsonPathIndex::Slice(s, e, step)) => {
TopPaths::ArraySlice(ArraySlice::new(*s, *e, *step))
}
JsonPath::Index(JsonPathIndex::UnionKeys(elems)) => {
TopPaths::UnionIndex(UnionIndex::from_keys(elems))
}
JsonPath::Index(JsonPathIndex::UnionIndex(elems)) => {
TopPaths::UnionIndex(UnionIndex::from_indexes(elems))
}
JsonPath::Index(JsonPathIndex::Filter(fe)) => {
TopPaths::FilterPath(FilterPath::new(fe, root))
}
JsonPath::Empty => TopPaths::IdentityPath(IdentityPath::new()),
JsonPath::Fn(Function::Length) => TopPaths::FnPath(FnPath::new_size()),
}
}
/// The method processes the operand inside the filter expressions
fn process_operand<'a, T>(op: &'a Operand<T>, root: &'a T) -> PathInstance<'a, T>
where
T: JsonLike,
{
Box::new(match op {
Operand::Static(v) => json_path_instance(&JsonPath::Root, v),
Operand::Dynamic(jp) => json_path_instance(jp, root),
})
}
#[cfg(test)]
mod tests {
use crate::path::JsonPathIndex;
use crate::path::{convert_part, JsonLike};
use crate::{idx, path, JsonPath, JsonPathParserError};
use serde_json::{json, Value};
#[test]
fn value_eq_test() {
let left = json!({"value":42});
let right = json!({"value":42});
let right_uneq = json!([42]);
assert!(&left.eq(&right));
assert!(!&left.eq(&right_uneq));
}
#[test]
fn vec_value_test() {
let left = json!({"value":42});
let left1 = json!(42);
let left2 = json!([1, 2, 3]);
let left3 = json!({"value2":[42],"value":[42]});
let right = json!({"value":42});
let right1 = json!(42);
let right2 = json!([1, 2, 3]);
let right3 = json!({"value":[42],"value2":[42]});
assert!(JsonLike::eq(vec![&left], vec![&right]));
assert!(!JsonLike::eq(vec![], vec![&right]));
assert!(!JsonLike::eq(vec![&right], vec![]));
assert!(JsonLike::eq(
vec![&left, &left1, &left2, &left3],
vec![&right, &right1, &right2, &right3]
));
assert!(!JsonLike::eq(
vec![&left1, &left, &left2, &left3],
vec![&right, &right1, &right2, &right3]
));
}
#[test]
fn less_value_test() {
let left = json!(10);
let right = json!(11);
assert!(JsonLike::less(vec![&left], vec![&right]));
assert!(!JsonLike::less(vec![&right], vec![&left]));
let left = json!(-10);
let right = json!(-11);
assert!(!JsonLike::less(vec![&left], vec![&right]));
assert!(JsonLike::less(vec![&right], vec![&left]));
let left = json!(-10.0);
let right = json!(-11.0);
assert!(!JsonLike::less(vec![&left], vec![&right]));
assert!(JsonLike::less(vec![&right], vec![&left]));
assert!(!JsonLike::less(vec![], vec![&right]));
assert!(!JsonLike::less(vec![&right, &right], vec![&left]));
}
#[test]
fn regex_test() {
let right = json!("[a-zA-Z]+[0-9]#[0-9]+");
let left1 = json!("a11#");
let left2 = json!("a1#1");
let left3 = json!("a#11");
let left4 = json!("#a11");
assert!(JsonLike::regex(
vec![&left1, &left2, &left3, &left4],
vec![&right]
));
assert!(!JsonLike::regex(vec![&left1, &left3, &left4], vec![&right]))
}
#[test]
fn any_of_test() {
let right = json!([1, 2, 3, 4, 5, 6]);
let left = json!([1, 100, 101]);
assert!(JsonLike::any_of(vec![&left], vec![&right]));
let left = json!([11, 100, 101]);
assert!(!JsonLike::any_of(vec![&left], vec![&right]));
let left1 = json!(1);
let left2 = json!(11);
assert!(JsonLike::any_of(vec![&left1, &left2], vec![&right]));
}
#[test]
fn sub_set_of_test() {
let left1 = json!(1);
let left2 = json!(2);
let left3 = json!(3);
let left40 = json!(40);
let right = json!([1, 2, 3, 4, 5, 6]);
assert!(JsonLike::sub_set_of(
vec![&Value::Array(vec![
left1.clone(),
left2.clone(),
left3.clone()
])],
vec![&right]
));
assert!(!JsonLike::sub_set_of(
vec![&Value::Array(vec![left1, left2, left3, left40])],
vec![&right]
));
}
#[test]
fn size_test() {
let left1 = json!("abc");
let left2 = json!([1, 2, 3]);
let left3 = json!([1, 2, 3, 4]);
let right = json!(3);
let right1 = json!(4);
assert!(JsonLike::size(vec![&left1], vec![&right]));
assert!(JsonLike::size(vec![&left2], vec![&right]));
assert!(!JsonLike::size(vec![&left3], vec![&right]));
assert!(JsonLike::size(vec![&left3], vec![&right1]));
}
#[test]
fn convert_paths() -> Result<(), JsonPathParserError> {
let r = convert_part(&JsonPath::Chain(vec![
path!($),
path!("abc"),
path!(idx!(1)),
]))?;
assert_eq!(r, "/abc/1");
assert!(convert_part(&JsonPath::Chain(vec![path!($), path!(.."abc")])).is_err());
Ok(())
}
#[test]
fn test_references() -> Result<(), JsonPathParserError> {
let mut json = json!({
"a": {
"b": {
"c": 42
}
}
});
let path_str = convert_part(&JsonPath::Chain(vec![path!("a"), path!("b"), path!("c")]))?;
if let Some(v) = json.pointer_mut(&path_str) {
*v = json!(43);
}
assert_eq!(
json,
json!({
"a": {
"b": {
"c": 43
}
}
})
);
Ok(())
}
#[test]
fn test_js_reference() -> Result<(), JsonPathParserError> {
let mut json = json!({
"a": {
"b": {
"c": 42
}
}
});
let path = "$.a.b.c";
if let Some(v) = json.reference_mut(path)? {
*v = json!(43);
}
assert_eq!(
json,
json!({
"a": {
"b": {
"c": 43
}
}
})
);
Ok(())
}
}

640
vendor/jsonpath-rust/src/path/top.rs vendored Normal file
View File

@@ -0,0 +1,640 @@
use crate::jsp_obj;
use crate::parser::model::*;
use crate::path::{json_path_instance, JsonPathValue, Path};
use crate::JsonPathValue::{NewValue, NoValue, Slice};
use super::{JsonLike, TopPaths};
/// to process the element [*]
pub struct Wildcard<T> {
_t: std::marker::PhantomData<T>,
}
impl<T> Wildcard<T> {
pub fn new() -> Self {
Self {
_t: std::marker::PhantomData,
}
}
}
impl<T> Default for Wildcard<T> {
fn default() -> Self {
Self::new()
}
}
impl<'a, T> Path<'a> for Wildcard<T>
where
T: JsonLike,
{
type Data = T;
fn find(&self, data: JsonPathValue<'a, Self::Data>) -> Vec<JsonPathValue<'a, Self::Data>> {
data.flat_map_slice(|data, pref| data.itre(pref))
}
}
/// empty path. Returns incoming data.
pub struct IdentityPath<T> {
_t: std::marker::PhantomData<T>,
}
impl<T> Default for IdentityPath<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> IdentityPath<T> {
pub fn new() -> Self {
Self {
_t: std::marker::PhantomData,
}
}
}
impl<'a, T> Path<'a> for IdentityPath<T> {
type Data = T;
fn find(&self, data: JsonPathValue<'a, Self::Data>) -> Vec<JsonPathValue<'a, Self::Data>> {
vec![data]
}
}
pub(crate) struct EmptyPath<T> {
_t: std::marker::PhantomData<T>,
}
impl<'a, T> Path<'a> for EmptyPath<T> {
type Data = T;
fn find(&self, _data: JsonPathValue<'a, Self::Data>) -> Vec<JsonPathValue<'a, Self::Data>> {
vec![]
}
}
/// process $ element
pub struct RootPointer<'a, T> {
root: &'a T,
}
impl<'a, T> RootPointer<'a, T> {
pub(crate) fn new(root: &'a T) -> RootPointer<'a, T> {
RootPointer { root }
}
}
impl<'a, T> Path<'a> for RootPointer<'a, T>
where
T: JsonLike,
{
type Data = T;
fn find(&self, _data: JsonPathValue<'a, Self::Data>) -> Vec<JsonPathValue<'a, Self::Data>> {
vec![JsonPathValue::from_root(self.root)]
}
}
/// process object fields like ['key'] or .key
pub struct ObjectField<'a, T> {
key: &'a str,
_t: std::marker::PhantomData<T>,
}
impl<'a, T> ObjectField<'a, T> {
pub(crate) fn new(key: &'a str) -> ObjectField<'a, T> {
ObjectField {
key,
_t: std::marker::PhantomData,
}
}
}
impl<T> Clone for ObjectField<'_, T> {
fn clone(&self) -> Self {
ObjectField::new(self.key)
}
}
impl<'a, T> Path<'a> for ObjectField<'a, T>
where
T: JsonLike,
{
type Data = T;
fn find(&self, data: JsonPathValue<'a, Self::Data>) -> Vec<JsonPathValue<'a, Self::Data>> {
let take_field = |v: &'a T| v.get(self.key);
let res = match data {
Slice(js, p) => take_field(js)
.map(|v| JsonPathValue::new_slice(v, jsp_obj(&p, self.key)))
.unwrap_or_else(|| NoValue),
_ => NoValue,
};
vec![res]
}
}
pub enum FnPath<T> {
Size { _t: std::marker::PhantomData<T> },
}
impl<T> FnPath<T> {
pub fn new_size() -> Self {
FnPath::Size {
_t: std::marker::PhantomData,
}
}
}
impl<'a, T> Path<'a> for FnPath<T>
where
T: JsonLike,
{
type Data = T;
fn flat_find(
&self,
input: Vec<JsonPathValue<'a, Self::Data>>,
is_search_length: bool,
) -> Vec<JsonPathValue<'a, Self::Data>> {
// todo rewrite
if JsonPathValue::only_no_value(&input) {
return vec![NoValue];
}
let res = if is_search_length {
NewValue(T::init_with_usize(
input.iter().filter(|v| v.has_value()).count(),
))
} else {
match input.first() {
Some(v) => match v {
NewValue(d) => d.array_len(),
Slice(s, _) => s.array_len(),
NoValue => NoValue,
},
None => NoValue,
}
};
vec![res]
}
fn needs_all(&self) -> bool {
true
}
}
/// the top method of the processing ..*
pub struct DescentWildcard<T> {
_t: std::marker::PhantomData<T>,
}
impl<T> Default for DescentWildcard<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> DescentWildcard<T> {
pub fn new() -> Self {
DescentWildcard {
_t: std::marker::PhantomData,
}
}
}
impl<'a, T> Path<'a> for DescentWildcard<T>
where
T: JsonLike,
{
type Data = T;
fn find(&self, data: JsonPathValue<'a, Self::Data>) -> Vec<JsonPathValue<'a, Self::Data>> {
data.map_slice(|data, pref| data.deep_flatten(pref))
}
}
/// processes decent object like ..
pub struct DescentObject<'a, T> {
key: &'a str,
_t: std::marker::PhantomData<T>,
}
impl<'a, T> Path<'a> for DescentObject<'a, T>
where
T: JsonLike,
{
type Data = T;
fn find(&self, data: JsonPathValue<'a, Self::Data>) -> Vec<JsonPathValue<'a, Self::Data>> {
data.flat_map_slice(|data, pref| {
let res_col = data.deep_path_by_key(ObjectField::new(self.key), pref.clone());
if res_col.is_empty() {
vec![NoValue]
} else {
JsonPathValue::map_vec(res_col)
}
})
}
}
impl<'a, T> DescentObject<'a, T> {
pub fn new(key: &'a str) -> Self {
DescentObject {
key,
_t: std::marker::PhantomData,
}
}
}
/// the top method of the processing representing the chain of other operators
pub struct Chain<'a, T> {
chain: Vec<TopPaths<'a, T>>,
is_search_length: bool,
}
impl<'a, T> Chain<'a, T>
where
T: JsonLike,
{
pub fn new(chain: Vec<TopPaths<'a, T>>, is_search_length: bool) -> Self {
Chain {
chain,
is_search_length,
}
}
pub fn from(chain: &'a [JsonPath<T>], root: &'a T) -> Self {
let chain_len = chain.len();
let is_search_length = if chain_len > 2 {
let mut res = false;
// if the result of the slice expected to be a slice, union or filter -
// length should return length of resulted array
// In all other cases, including single index, we should fetch item from resulting array
// and return length of that item
res = match chain.get(chain_len - 1).expect("chain element disappeared") {
JsonPath::Fn(Function::Length) => {
for item in chain.iter() {
match (item, res) {
// if we found union, slice, filter or wildcard - set search to true
(
JsonPath::Index(JsonPathIndex::UnionIndex(_))
| JsonPath::Index(JsonPathIndex::UnionKeys(_))
| JsonPath::Index(JsonPathIndex::Slice(_, _, _))
| JsonPath::Index(JsonPathIndex::Filter(_))
| JsonPath::Wildcard,
false,
) => {
res = true;
}
// if we found a fetching of single index - reset search to false
(JsonPath::Index(JsonPathIndex::Single(_)), true) => {
res = false;
}
(_, _) => {}
}
}
res
}
_ => false,
};
res
} else {
false
};
Chain::new(
chain.iter().map(|p| json_path_instance(p, root)).collect(),
is_search_length,
)
}
}
impl<'a, T> Path<'a> for Chain<'a, T>
where
T: JsonLike,
{
type Data = T;
fn find(&self, data: JsonPathValue<'a, Self::Data>) -> Vec<JsonPathValue<'a, Self::Data>> {
let mut res = vec![data];
for inst in self.chain.iter() {
if inst.needs_all() {
res = inst.flat_find(res, self.is_search_length)
} else {
res = res.into_iter().flat_map(|d| inst.find(d)).collect()
}
}
res
}
}
#[cfg(test)]
mod tests {
use crate::jp_v;
use crate::parser::macros::{chain, idx};
use crate::parser::model::{JsonPath, JsonPathIndex};
use crate::path;
use crate::path::top::{json_path_instance, Function, ObjectField, RootPointer};
use crate::path::{JsonPathValue, Path};
use crate::JsonPathValue::NoValue;
use serde_json::json;
use serde_json::Value;
#[test]
fn object_test() {
let js = json!({"product": {"key":42}});
let res_income = jp_v!(&js);
let key = String::from("product");
let mut field = ObjectField::new(&key);
let js = json!({"key":42});
assert_eq!(
field.find(res_income.clone()),
vec![jp_v!(&js;".['product']")]
);
let key = String::from("fake");
field.key = &key;
assert_eq!(field.find(res_income), vec![NoValue]);
}
#[test]
fn root_test() {
let res_income = json!({"product": {"key":42}});
let root = RootPointer::<Value>::new(&res_income);
assert_eq!(root.find(jp_v!(&res_income)), jp_v!(&res_income;"$",))
}
#[test]
fn path_instance_test() {
let json = json!({"v": {"k":{"f":42,"array":[0,1,2,3,4,5],"object":{"field1":"val1","field2":"val2"}}}});
let field1 = path!("v");
let field2 = path!("k");
let field3 = path!("f");
let field4 = path!("array");
let field5 = path!("object");
let path_inst = json_path_instance(&path!($), &json);
assert_eq!(path_inst.find(jp_v!(&json)), jp_v!(&json;"$",));
let path_inst = json_path_instance(&field1, &json);
let exp_json =
json!({"k":{"f":42,"array":[0,1,2,3,4,5],"object":{"field1":"val1","field2":"val2"}}});
assert_eq!(path_inst.find(jp_v!(&json)), jp_v!(&exp_json;".['v']",));
let chain = chain!(path!($), field1.clone(), field2.clone(), field3);
let path_inst = json_path_instance(&chain, &json);
let exp_json = json!(42);
assert_eq!(
path_inst.find(jp_v!(&json)),
jp_v!(&exp_json;"$.['v'].['k'].['f']",)
);
let chain = chain!(
path!($),
field1.clone(),
field2.clone(),
field4.clone(),
path!(idx!(3))
);
let path_inst = json_path_instance(&chain, &json);
let exp_json = json!(3);
assert_eq!(
path_inst.find(jp_v!(&json)),
jp_v!(&exp_json;"$.['v'].['k'].['array'][3]",)
);
let index = idx!([1;-1;2]);
let chain = chain!(
path!($),
field1.clone(),
field2.clone(),
field4.clone(),
path!(index)
);
let path_inst = json_path_instance(&chain, &json);
let one = json!(1);
let tree = json!(3);
assert_eq!(
path_inst.find(jp_v!(&json)),
jp_v!(&one;"$.['v'].['k'].['array'][1]", &tree;"$.['v'].['k'].['array'][3]")
);
let union = idx!(idx 1,2 );
let chain = chain!(
path!($),
field1.clone(),
field2.clone(),
field4,
path!(union)
);
let path_inst = json_path_instance(&chain, &json);
let tree = json!(1);
let two = json!(2);
assert_eq!(
path_inst.find(jp_v!(&json)),
jp_v!(&tree;"$.['v'].['k'].['array'][1]",&two;"$.['v'].['k'].['array'][2]")
);
let union = idx!("field1", "field2");
let chain = chain!(path!($), field1.clone(), field2, field5, path!(union));
let path_inst = json_path_instance(&chain, &json);
let one = json!("val1");
let two = json!("val2");
assert_eq!(
path_inst.find(jp_v!(&json)),
jp_v!(
&one;"$.['v'].['k'].['object'].['field1']",
&two;"$.['v'].['k'].['object'].['field2']")
);
}
#[test]
fn path_descent_arr_test() {
let json = json!([{"a":1}]);
let chain = chain!(path!($), path!(.."a"));
let path_inst = json_path_instance(&chain, &json);
let one = json!(1);
let expected_res = jp_v!(&one;"$[0].['a']",);
assert_eq!(path_inst.find(jp_v!(&json)), expected_res)
}
#[test]
fn deep_path_test() {
use crate::path::JsonLike;
let value = json!([1]);
let r = value.deep_flatten("".to_string());
assert_eq!(r, vec![(&json!(1), "[0]".to_string())])
}
#[test]
fn path_descent_w_array_test() {
let json = json!(
{
"key1": [1]
});
let chain = chain!(path!($), path!(..*));
let path_inst = json_path_instance(&chain, &json);
let arr = json!([1]);
let one = json!(1);
let expected_res = jp_v!(&arr;"$.['key1']",&one;"$.['key1'][0]");
assert_eq!(path_inst.find(jp_v!(&json)), expected_res)
}
#[test]
fn path_descent_w_nested_array_test() {
let json = json!(
{
"key2" : [{"a":1},{}]
});
let chain = chain!(path!($), path!(..*));
let path_inst = json_path_instance(&chain, &json);
let arr2 = json!([{"a": 1},{}]);
let obj = json!({"a": 1});
let empty = json!({});
let one = json!(1);
let expected_res = jp_v!(
&arr2;"$.['key2']",
&obj;"$.['key2'][0]",
&one;"$.['key2'][0].['a']",
&empty;"$.['key2'][1]"
);
assert_eq!(path_inst.find(jp_v!(&json)), expected_res)
}
#[test]
fn path_descent_w_test() {
let json = json!(
{
"key1": [1],
"key2": "key",
"key3": {
"key1": "key1",
"key2": {
"key1": {
"key1": 0
}
}
}
});
let chain = chain!(path!($), path!(..*));
let path_inst = json_path_instance(&chain, &json);
let key1 = json!([1]);
let one = json!(1);
let zero = json!(0);
let key = json!("key");
let key1_s = json!("key1");
let key_3 = json!( {
"key1": "key1",
"key2": {
"key1": {
"key1": 0
}
}
});
let key_sec = json!( {
"key1": {
"key1": 0
}
});
let key_th = json!( {
"key1": 0
});
let expected_res = vec![
jp_v!(&key1;"$.['key1']"),
jp_v!(&one;"$.['key1'][0]"),
jp_v!(&key;"$.['key2']"),
jp_v!(&key_3;"$.['key3']"),
jp_v!(&key1_s;"$.['key3'].['key1']"),
jp_v!(&key_sec;"$.['key3'].['key2']"),
jp_v!(&key_th;"$.['key3'].['key2'].['key1']"),
jp_v!(&zero;"$.['key3'].['key2'].['key1'].['key1']"),
];
assert_eq!(path_inst.find(jp_v!(&json)), expected_res)
}
#[test]
fn path_descent_test() {
let json = json!(
{
"key1": [1,2,3],
"key2": "key",
"key3": {
"key1": "key1",
"key2": {
"key1": {
"key1": 0
}
}
}
});
let chain = chain!(path!($), path!(.."key1"));
let path_inst = json_path_instance(&chain, &json);
let res1 = json!([1, 2, 3]);
let res2 = json!("key1");
let res3 = json!({"key1":0});
let res4 = json!(0);
let expected_res = jp_v!(
&res1;"$.['key1']",
&res2;"$.['key3'].['key1']",
&res3;"$.['key3'].['key2'].['key1']",
&res4;"$.['key3'].['key2'].['key1'].['key1']",
);
assert_eq!(path_inst.find(jp_v!(&json)), expected_res)
}
#[test]
fn wildcard_test() {
let json = json!({
"key1": [1,2,3],
"key2": "key",
"key3": {}
});
let chain = chain!(path!($), path!(*));
let path_inst = json_path_instance(&chain, &json);
let res1 = json!([1, 2, 3]);
let res2 = json!("key");
let res3 = json!({});
let expected_res = jp_v!(&res1;"$.['key1']", &res2;"$.['key2']", &res3;"$.['key3']");
assert_eq!(path_inst.find(jp_v!(&json)), expected_res)
}
#[test]
fn length_test() {
let json = json!({
"key1": [1,2,3],
"key2": "key",
"key3": {}
});
let chain = chain!(path!($), path!(*), JsonPath::Fn(Function::Length));
let path_inst = json_path_instance(&chain, &json);
assert_eq!(
path_inst.flat_find(vec![jp_v!(&json)], true),
vec![jp_v!(json!(3))]
);
let chain = chain!(path!($), path!("key1"), JsonPath::Fn(Function::Length));
let path_inst = json_path_instance(&chain, &json);
assert_eq!(
path_inst.flat_find(vec![jp_v!(&json)], false),
vec![jp_v!(json!(3))]
);
}
}

Binary file not shown.

View File

@@ -0,0 +1 @@
{"name":"jsonpath-rust","vers":"0.7.5","deps":[{"name":"pest","req":"^2.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal","registry":"https://github.com/rust-lang/crates.io-index","package":null,"public":null,"artifact":null,"bindep_target":null,"lib":false},{"name":"pest_derive","req":"^2.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal","registry":"https://github.com/rust-lang/crates.io-index","package":null,"public":null,"artifact":null,"bindep_target":null,"lib":false},{"name":"regex","req":"^1","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal","registry":"https://github.com/rust-lang/crates.io-index","package":null,"public":null,"artifact":null,"bindep_target":null,"lib":false},{"name":"serde_json","req":"^1.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal","registry":"https://github.com/rust-lang/crates.io-index","package":null,"public":null,"artifact":null,"bindep_target":null,"lib":false},{"name":"thiserror","req":"^2.0.9","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal","registry":"https://github.com/rust-lang/crates.io-index","package":null,"public":null,"artifact":null,"bindep_target":null,"lib":false},{"name":"criterion","req":"^0.5.1","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev","registry":"https://github.com/rust-lang/crates.io-index","package":null,"public":null,"artifact":null,"bindep_target":null,"lib":false}],"features":{},"features2":null,"cksum":"add2f5c4ae1e550693f0c1fb6a3ebe779fdf58f4c3e5029cfe3e62d4da70c129","yanked":null,"links":null,"rust_version":null,"v":2}