├── .config └── nextest.toml ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── benches ├── equal.rs └── regex.rs ├── jsonpath-ast ├── Cargo.toml └── src │ ├── ast.rs │ ├── lib.rs │ └── syn_parse.rs ├── jsonpath-rust-impl ├── Cargo.toml ├── src │ └── lib.rs └── tests │ ├── rfc9535_compile_tests │ ├── basic │ │ ├── compile_and_passes.rs │ │ ├── compile_but_expect_err.rs │ │ ├── does_not_compile.rs │ │ ├── does_not_compile.stderr │ │ └── mod.rs │ └── mod.rs │ └── test.rs ├── rfc9535 ├── Cargo.toml ├── README.md ├── src │ ├── console.rs │ ├── main.rs │ └── suite.rs └── test_suite │ ├── filtered_cases.json │ └── results.csv └── src ├── lib.rs ├── parser.rs ├── parser ├── errors.rs ├── grammar │ └── json_path_9535.pest ├── macros.rs ├── model.rs └── tests.rs ├── query.rs └── query ├── atom.rs ├── comparable.rs ├── comparison.rs ├── filter.rs ├── jp_query.rs ├── queryable.rs ├── segment.rs ├── selector.rs ├── state.rs ├── test.rs └── test_function.rs /.config/nextest.toml: -------------------------------------------------------------------------------- 1 | [profile.ci] 2 | failure-output = "immediate-final" 3 | fail-fast = false 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Rust CI 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | tags: ["v*"] 7 | pull_request: 8 | types: [opened, synchronize, reopened] 9 | 10 | jobs: 11 | rustfmt: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: stable 19 | components: rustfmt 20 | - run: cargo fmt --all -- --check 21 | 22 | clippy: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v3 26 | - uses: actions-rs/toolchain@v1 27 | with: 28 | profile: minimal 29 | toolchain: stable 30 | components: clippy 31 | - run: cargo clippy --workspace --all-targets --all-features -- -D warnings 32 | 33 | test: 34 | runs-on: ubuntu-latest 35 | env: 36 | CARGO_TERM_COLOR: always 37 | steps: 38 | - uses: actions/checkout@v3 39 | - uses: taiki-e/install-action@v2 40 | with: 41 | tool: nextest 42 | - run: cargo nextest run --all-features --profile ci 43 | 44 | doc: 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v3 48 | - uses: actions-rs/toolchain@v1 49 | with: 50 | profile: minimal 51 | toolchain: stable 52 | - run: cargo doc --all-features --no-deps 53 | 54 | publish: 55 | name: publish on crates.io 56 | needs: 57 | - rustfmt 58 | - clippy 59 | - test 60 | - doc 61 | if: ${{ startsWith(github.ref, 'refs/tags/v') }} 62 | runs-on: ubuntu-latest 63 | steps: 64 | - uses: actions/checkout@v3 65 | - run: cargo publish -p jsonpath-rust --token ${{ secrets.CRATES_IO_TOKEN }} 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/target 3 | .idea 4 | Cargo.lock 5 | .DS_Store 6 | .vscode 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "rfc9535/test_suite/jsonpath-compliance-test-suite"] 2 | path = rfc9535/test_suite/jsonpath-compliance-test-suite 3 | url = https://github.com/jsonpath-standard/jsonpath-compliance-test-suite.git 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | - **`0.1.0`** 2 | - Initial implementation 3 | - **`0.1.1`** 4 | - Technical improvements 5 | - **`0.1.2`** 6 | - added a trait to obtain the result from value 7 | - added a method to get the cloned as Value 8 | - change the name of the general method\* 9 | - **`0.1.4`** 10 | - add an ability to use references instead of values 11 | - fix some clippy issues 12 | - **`0.1.5`** 13 | - correct grammar for `$.[..]` 14 | - **`0.1.6`** 15 | - add logical OR and logical And to filters 16 | - fix bugs with objects in filters 17 | - add internal macros to generate path objects 18 | - **`0.2.0`** 19 | - add json path value as a result for the library 20 | - add functions (size) 21 | - change a logical operator `size` into function `size()` 22 | - **`0.2.1`** 23 | - changed the contract for length() function. 24 | - **`0.2.2`** 25 | - add ..\* 26 | - **`0.2.5`** 27 | - build for tags 28 | - **`0.2.6`** 29 | - make parser mod public 30 | - **`0.3.0`** 31 | - introduce the different behaviour for empty results and non-existing result 32 | - **`0.3.2`** 33 | - make jsonpath inst cloneable. 34 | - **`0.3.3`** 35 | - fix a bug with the logical operators 36 | - **`0.3.4`** 37 | - add a result as a path 38 | - **`0.3.5`** 39 | - add `!` negation operation in filters 40 | - allow using () in filters 41 | - **`0.5`** 42 | - add config for jsonpath 43 | - add an option to add a regex cache for boosting performance 44 | - **`0.5.1`** 45 | - add double quotes for the expressions (before it was only possible to use single quotes) 46 | - add Debug on the JsonPathFinder 47 | - **`0.6`** 48 | - allow to reuse regex, that improves performance without needing an internal cache 49 | 50 | - **`0.6.1`** 51 | - Performance improvements 52 | - Change the contract for the struct of errors 53 | - **`0.7.0`** 54 | - Bug fixes and api changes 55 | - **`0.7.1`** 56 | - add Display to JsonPath 57 | - **`0.7.2`** 58 | - add JsonLike trait 59 | - **`0.7.3`** 60 | - make some methods public 61 | - **`0.7.5`** 62 | - add reference and reference_mut methods 63 | - **`1.0.0`** 64 | - introduced breaking changes to the API to make it compliant with the RFC9535 65 | - **`1.0.1`** 66 | - add method to process parsed query 67 | - **`1.0.2`** 68 | - improve grammar -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonpath-rust" 3 | description = "The library provides the basic functionality to find the set of the data according to the filtering query." 4 | version = "1.0.2" 5 | authors = ["BorisZhguchev "] 6 | edition = "2021" 7 | license = "MIT" 8 | homepage = "https://github.com/besok/jsonpath-rust" 9 | repository = "https://github.com/besok/jsonpath-rust" 10 | readme = "README.md" 11 | keywords = ["json", "json-path", "jsonpath", "jsonpath-rust", "xpath"] 12 | categories = ["development-tools", "parsing", "text-processing"] 13 | 14 | [dependencies] 15 | serde_json = "1.0" 16 | regex = "1" 17 | pest = "2.7.15" 18 | pest_derive = "2.7.15" 19 | thiserror = "2.0.9" 20 | jsonpath-rust-impl = {path = "jsonpath-rust-impl", optional = true} 21 | jsonpath-ast = {path = "jsonpath-ast"} 22 | 23 | [dev-dependencies] 24 | serde = { version = "1.0", features = ["derive"] } 25 | criterion = "0.5.1" 26 | 27 | [features] 28 | compiled-path = ["jsonpath-ast/compiled-path", "dep:jsonpath-rust-impl"] 29 | 30 | [[bench]] 31 | name = "regex" 32 | harness = false 33 | 34 | [[bench]] 35 | name = "equal" 36 | harness = false 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2021] [Boris Zhguchev] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsonpath-rust 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/jsonpath-rust)](https://crates.io/crates/jsonpath-rust) 4 | [![docs.rs](https://img.shields.io/docsrs/jsonpath-rust)](https://docs.rs/jsonpath-rust/latest/jsonpath_rust) 5 | [![Rust CI](https://github.com/besok/jsonpath-rust/actions/workflows/ci.yml/badge.svg)](https://github.com/besok/jsonpath-rust/actions/workflows/ci.yml) 6 | 7 | The library provides the extensive functionality to find data sets according to filtering queries. 8 | Inspired by XPath for XML structures, JsonPath is a query language for JSON. 9 | The specification is described in [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535.html). 10 | 11 | # Important note 12 | 13 | The version 1.0.0 has a breaking change. The library has been rewritten from scratch to provide compliance with the RFC9535. 14 | 15 | The changes are: 16 | 17 | - The library is now fully compliant with the RFC 9535. 18 | - New structures and apis were introduced to provide the compliance with the RFC 9535. 19 | - `Queryable` instead of `JsonLike` 20 | - `Queried` instead of `Result` 21 | - `JsonPath#{query_with_path, query_only_path, query}` to operate with the `Queryable` structure 22 | - `JsonPathError` instead of `JsonPathParserError` 23 | - `QueryRef` to provide the reference to the value and path 24 | - The functions in, nin, noneOf, anyOf, subsetOf are now implemented as custom filter expressions and renamed to `in`, 25 | `nin`, `none_of`, `any_of`, `subset_of` respectively. 26 | - The function length was removed (the size can be checked using rust native functions for using it in filter there is length expression). 27 | 28 | ## The compliance with RFC 9535 29 | 30 | The library is fully compliant (except several cases) with the standard [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535.html) 31 | To check the compliance with the standard, please be headed to [rfc9535 subfolder](rfc9535/README.md) 32 | 33 | 34 | ## Examples 35 | 36 | Given the json 37 | 38 | ```json 39 | { 40 | "store": { 41 | "book": [ 42 | { 43 | "category": "reference", 44 | "author": "Nigel Rees", 45 | "title": "Sayings of the Century", 46 | "price": 8.95 47 | }, 48 | { 49 | "category": "fiction", 50 | "author": "Evelyn Waugh", 51 | "title": "Sword of Honour", 52 | "price": 12.99 53 | }, 54 | { 55 | "category": "fiction", 56 | "author": "Herman Melville", 57 | "title": "Moby Dick", 58 | "isbn": "0-553-21311-3", 59 | "price": 8.99 60 | }, 61 | { 62 | "category": "fiction", 63 | "author": "J. R. R. Tolkien", 64 | "title": "The Lord of the Rings", 65 | "isbn": "0-395-19395-8", 66 | "price": 22.99 67 | } 68 | ], 69 | "bicycle": { 70 | "color": "red", 71 | "price": 19.95 72 | } 73 | }, 74 | "expensive": 10 75 | } 76 | ``` 77 | 78 | | JsonPath | Result | 79 | |------------------------------------|:-------------------------------------------------------------| 80 | | `$.store.book[*].author` | The authors of all books | 81 | | `$..book[?@.isbn]` | All books with an ISBN number | 82 | | `$.store.*` | All things, both books and bicycles | 83 | | `$..author` | All authors | 84 | | `$.store..price` | The price of everything | 85 | | `$..book[2]` | The third book | 86 | | `$..book[-2]` | The second to last book | 87 | | `$..book[0,1]` | The first two books | 88 | | `$..book[:2]` | All books from index 0 (inclusive) until index 2 (exclusive) | 89 | | `$..book[1:2]` | All books from index 1 (inclusive) until index 2 (exclusive) | 90 | | `$..book[-2:]` | Last two books | 91 | | `$..book[2:]` | Book number two from tail | 92 | | `$.store.book[?@.price < 10]` | All books in store cheaper than 10 | 93 | | `$..book[?@.price <= $.expensive]` | All books in store that are not "expensive" | 94 | | `$..book[?@.author ~= '(?i)REES']` | All books matching regex (ignore case) | 95 | | `$..*` | Give me every thing | 96 | 97 | ## Library Usage 98 | 99 | ### Extensions 100 | The library provides the following extensions: 101 | 102 | - **in** 103 | Checks if the first argument is in the array provided as the second argument. Example: `$.elems[?in(@, $.list)]` 104 | Returns elements from `$.elems` that are present in `$.list`. 105 | 106 | - **nin** 107 | Checks if the first argument is not in the array provided as the second argument. Example: `$.elems[?nin(@, $.list)]` 108 | Returns elements from `$.elems` that are not present in `$.list`. 109 | 110 | - **none_of** 111 | Checks if none of the elements in the first array are in the second array. Example: `$.elems[?none_of(@, $.list)]` 112 | Returns arrays from `$.elems` that have no elements in common with `$.list`. 113 | 114 | - **any_of** 115 | Checks if any of the elements in the first array are in the second array. Example: `$.elems[?any_of(@, $.list)]` 116 | Returns arrays from `$.elems` that have at least one element in common with `$.list`. 117 | 118 | - **subset_of** 119 | Checks if all elements in the first array are in the second array. Example: `$.elems[?subset_of(@, $.list)]` 120 | Returns arrays from `$.elems` where all elements are present in `$.list`. 121 | 122 | 123 | ### Queryable 124 | 125 | The library provides a trait `Queryable` that can be implemented for any type. 126 | This allows you to use the `JsonPath` methods on your own types. 127 | 128 | ### Queried with path 129 | 130 | ```rust 131 | 132 | fn union() -> Queried<()> { 133 | let json = json!([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); 134 | 135 | // QueryRes is a tuple of (value, path) for references and just value for owned values 136 | let vec: Vec> = json.query_with_path("$[1,5:7]")?; 137 | assert_eq!( 138 | vec, 139 | vec![ 140 | (&json!(1), "$[1]".to_string()).into(), 141 | (&json!(5), "$[5]".to_string()).into(), 142 | (&json!(6), "$[6]".to_string()).into(), 143 | ] 144 | ); 145 | 146 | Ok(()) 147 | } 148 | 149 | ``` 150 | 151 | ### Queried without path 152 | 153 | ```rust 154 | fn exp_no_error() -> Queried<()> { 155 | let json = json!([ 156 | { 157 | "a": 100, 158 | "d": "e" 159 | }, 160 | { 161 | "a": 100.1, 162 | "d": "f" 163 | }, 164 | { 165 | "a": "100", 166 | "d": "g" 167 | } 168 | ]); 169 | 170 | let vec: Vec<&Value> = json.query("$[?@.a==1E2]")?; 171 | assert_eq!( 172 | vec.iter().collect::>(), 173 | vec![&json!({"a":100, "d":"e"})] 174 | ); 175 | 176 | Ok(()) 177 | } 178 | ``` 179 | 180 | ### Queried with only path 181 | 182 | ```rust 183 | fn filter_data() -> Queried<()> { 184 | let json = json!({ 185 | "a": 1, 186 | "b": 2, 187 | "c": 3 188 | }); 189 | 190 | let vec: Vec = json 191 | .query_only_path("$[?@<3]")? 192 | .into_iter() 193 | .map(Option::unwrap_or_default) 194 | .collect(); 195 | 196 | assert_eq!(vec, vec!["$['a']".to_string(), "$['b']".to_string()]); 197 | 198 | Ok(()) 199 | } 200 | ``` 201 | 202 | ### Update the Queryable structure by path 203 | 204 | The library does not provide the functionality to update the json structure in the query itself. 205 | Instead, the library provides the ability to update the json structure by the path. 206 | Thus, the user needs to find a path for the `JsonLike` structure and update it manually. 207 | 208 | There are two methods in the `Queryable` trait: 209 | 210 | - `reference_mut` - returns a mutable reference to the element by the path 211 | - `reference` - returns a reference to the element by the path 212 | 213 | They accept a `JsonPath` instance and return a `Option<&mut Self>` or `Option<&Self>` respectively. 214 | 215 | The path is supported with the limited elements namely only the elements with the direct access: 216 | 217 | - root 218 | - field 219 | - index 220 | 221 | ```rust 222 | fn update_by_path_test() -> Queried<()> { 223 | let mut json = json!([ 224 | {"verb": "RUN","distance":[1]}, 225 | {"verb": "TEST"}, 226 | {"verb": "DO NOT RUN"} 227 | ]); 228 | 229 | let path = json.query_only_path("$.[?(@.verb == 'RUN')]")?; 230 | let elem = path.first().unwrap_or_default(); 231 | 232 | if let Some(v) = json 233 | .reference_mut(elem) 234 | .and_then(|v| v.as_object_mut()) 235 | .and_then(|v| v.get_mut("distance")) 236 | .and_then(|v| v.as_array_mut()) 237 | { 238 | v.push(json!(2)) 239 | } 240 | 241 | assert_eq!( 242 | json, 243 | json!([ 244 | {"verb": "RUN","distance":[1,2]}, 245 | {"verb": "TEST"}, 246 | {"verb": "DO NOT RUN"} 247 | ]) 248 | ); 249 | 250 | Ok(()) 251 | } 252 | ``` 253 | 254 | ### Compiled Paths 255 | 🚧Under Construction: Unstable/Unimplemented🚧 256 | 257 | By enabling the `compiled-path` feature, the following syntax becomes available: 258 | ```rust 259 | fn macros() { 260 | // Existing 261 | let vec = js_path("$.values[?match(@, $.regex)]", &json)?; 262 | // New 263 | let q_ast: JpQuery = ::jsonpath_rust::json_query!($.values[?match(@, $.regex)]); 264 | } 265 | ``` 266 | 267 | This allows for query strings to be created infallibly at compile time for applications where query strings will be static strings in source code. 268 | 269 | #### Limitations Of Compiled Path Queries 270 | - Single quote strings are not allowed, however to replace this, rust's [raw string literals](https://doc.rust-lang.org/rust-by-example/std/str.html) such as `r"# ... #"` can be used. 271 | - The macro does not check whitespace, this means that with respect to whitespace, the domain of strings accepted by the macro is a superset of those accepted by the original RFC. 272 | - Due to [constraints on rust identifiers](https://internals.rust-lang.org/t/supporting-emoji-in-identifiers/16838), emoji in member name shorthands such as `json_query!( $.☺ )` are not allowed 273 | - Unicode characters still work in both string literals and bracket field access, ie: `json_query!( $["☺"] )` 274 | 275 | ### Python bindings 276 | 277 | Python bindings ([jsonpath-rust-bindings](https://github.com/night-crawler/jsonpath-rust-bindings)) are available on 278 | pypi: 279 | 280 | ```bash 281 | pip install jsonpath-rust-bindings 282 | ``` 283 | 284 | ## How to contribute 285 | 286 | TBD 287 | 288 | ## How to update version 289 | 290 | - update files 291 | - commit them 292 | - add tag `git tag -a v -m "message"` 293 | - git push origin 294 | -------------------------------------------------------------------------------- /benches/equal.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | 3 | use jsonpath_rust::parser::model::JpQuery; 4 | use jsonpath_rust::parser::parse_json_path; 5 | use jsonpath_rust::query::state::State; 6 | use jsonpath_rust::query::Query; 7 | use jsonpath_rust::JsonPath; 8 | use serde_json::{json, Value}; 9 | 10 | struct SearchData { 11 | json: Value, 12 | path: JpQuery, 13 | } 14 | 15 | const PATH: &str = "$[?@.author == 'abcd(Rees)']"; 16 | 17 | fn equal_perf_test_with_reuse(cfg: &SearchData) { 18 | let _v = cfg.path.process(State::root(&cfg.json)).data; 19 | } 20 | fn equal_perf_test_without_reuse() { 21 | let json = Box::new(json!({ 22 | "author":"abcd(Rees)", 23 | })); 24 | 25 | let _v = json.query(PATH).expect("the path is correct"); 26 | } 27 | 28 | pub fn criterion_benchmark(c: &mut Criterion) { 29 | let data = SearchData { 30 | json: json!({ 31 | "author":"abcd(Rees)", 32 | }), 33 | path: parse_json_path(PATH).unwrap(), 34 | }; 35 | c.bench_function("equal bench with reuse", |b| { 36 | b.iter(|| equal_perf_test_with_reuse(&data)) 37 | }); 38 | c.bench_function("equal bench without reuse", |b| { 39 | b.iter(equal_perf_test_without_reuse) 40 | }); 41 | } 42 | 43 | criterion_group!(benches, criterion_benchmark); 44 | criterion_main!(benches); 45 | -------------------------------------------------------------------------------- /benches/regex.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use jsonpath_rust::parser::model::JpQuery; 3 | use jsonpath_rust::parser::parse_json_path; 4 | use jsonpath_rust::query::state::State; 5 | use jsonpath_rust::query::Query; 6 | use jsonpath_rust::JsonPath; 7 | use serde_json::{json, Value}; 8 | 9 | struct SearchData { 10 | json: Value, 11 | path: JpQuery, 12 | } 13 | 14 | const PATH: &str = "$[?search(@.author,'.*(?i)d\\\\(Rees\\\\)')]"; 15 | 16 | fn regex_perf_test_with_reuse(cfg: &SearchData) { 17 | let _v = cfg.path.process(State::root(&cfg.json)).data; 18 | } 19 | 20 | fn regex_perf_test_without_reuse() { 21 | let json = Box::new(json!({ 22 | "author":"abcd(Rees)", 23 | })); 24 | 25 | let _v = json.query(PATH).expect("the path is correct"); 26 | } 27 | 28 | fn json_path_compiling() { 29 | let _v = parse_json_path(PATH).unwrap(); 30 | } 31 | 32 | pub fn criterion_benchmark(c: &mut Criterion) { 33 | let data = SearchData { 34 | json: json!({ 35 | "author":"abcd(Rees)", 36 | }), 37 | path: parse_json_path(PATH).unwrap(), 38 | }; 39 | c.bench_function("regex bench with reuse", |b| { 40 | b.iter(|| regex_perf_test_with_reuse(&data)) 41 | }); 42 | c.bench_function("regex bench without reuse", |b| { 43 | b.iter(regex_perf_test_without_reuse) 44 | }); 45 | c.bench_function("JsonPath generation", |b| b.iter(json_path_compiling)); 46 | } 47 | 48 | criterion_group!(benches, criterion_benchmark); 49 | criterion_main!(benches); 50 | -------------------------------------------------------------------------------- /jsonpath-ast/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonpath-ast" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | proc-macro2 = { version = "1.0.95", features = ["span-locations"] } 8 | pest = "2.7.15" 9 | pest_derive = "2.7.15" 10 | syn = { version = "2.0.101", features = ["default", "extra-traits"] } 11 | pest-ast = "0.3.5" 12 | from-pest = "0.3.3" 13 | syn_derive = { version = "0.2.0", optional = true } 14 | quote = "1.0.40" 15 | derive-new = "0.7.0" 16 | 17 | [features] 18 | compiled-path = ["dep:syn_derive"] 19 | -------------------------------------------------------------------------------- /jsonpath-ast/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod ast; 2 | pub use ast::parse; 3 | pub mod syn_parse; 4 | -------------------------------------------------------------------------------- /jsonpath-rust-impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonpath-rust-impl" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | jsonpath-ast = { path = "../jsonpath-ast", features = ["compiled-path"] } 11 | syn = "2.0.101" 12 | proc-macro2 = "1.0.95" 13 | quote = "1.0.40" 14 | 15 | [dev-dependencies] 16 | trybuild = "1.0.105" 17 | 18 | -------------------------------------------------------------------------------- /jsonpath-rust-impl/src/lib.rs: -------------------------------------------------------------------------------- 1 | use jsonpath_ast::ast::Main; 2 | use proc_macro::TokenStream; 3 | use quote::quote; 4 | use syn::parse_macro_input; 5 | 6 | #[proc_macro] 7 | pub fn json_query(input: TokenStream) -> TokenStream { 8 | let main = parse_macro_input!(input as Main); 9 | quote! {#main}.into() 10 | } 11 | -------------------------------------------------------------------------------- /jsonpath-rust-impl/tests/rfc9535_compile_tests/basic/compile_and_passes.rs: -------------------------------------------------------------------------------- 1 | // Test case: 00_root 2 | // Tags: No tags 3 | #[test] 4 | fn test_00_root() { 5 | let q_ast = ::jsonpath_rust_impl::json_query!($); 6 | let q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$"#).expect("failed to parse"); 7 | assert_eq!(q_pest, q_ast); 8 | } 9 | 10 | // Test case: 03_name_shorthand 11 | // Tags: No tags 12 | #[test] 13 | fn test_03_name_shorthand() { 14 | let q_ast = ::jsonpath_rust_impl::json_query!($.a); 15 | let q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$.a"#).expect("failed to parse"); 16 | assert_eq!(q_pest, q_ast); 17 | } 18 | 19 | // Test case: 04_name_shorthand_extended_unicode_uc 20 | // Tags: No tags 21 | #[test] 22 | fn test_04_name_shorthand_extended_unicode_uc() { 23 | let q_ast = ::jsonpath_rust_impl::json_query!($["☺"]); 24 | #[allow(unused_variables)] 25 | let q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$.☺"#).expect("failed to parse"); 26 | let q_pest = 27 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$["☺"]"#).expect("failed to parse"); 28 | assert_eq!(q_pest, q_ast); 29 | } 30 | 31 | // Test case: 05_name_shorthand_underscore 32 | // Tags: No tags 33 | #[test] 34 | fn test_05_name_shorthand_underscore() { 35 | let q_ast = ::jsonpath_rust_impl::json_query!($._); 36 | let q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$._"#).expect("failed to parse"); 37 | assert_eq!(q_pest, q_ast); 38 | } 39 | 40 | // Test case: 08_name_shorthand_absent_data 41 | // Tags: No tags 42 | #[test] 43 | fn test_08_name_shorthand_absent_data() { 44 | let q_ast = ::jsonpath_rust_impl::json_query!($.c); 45 | let q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$.c"#).expect("failed to parse"); 46 | assert_eq!(q_pest, q_ast); 47 | } 48 | 49 | // Test case: 09_name_shorthand_array_data 50 | // Tags: No tags 51 | #[test] 52 | fn test_09_name_shorthand_array_data() { 53 | let q_ast = ::jsonpath_rust_impl::json_query!($.a); 54 | let q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$.a"#).expect("failed to parse"); 55 | assert_eq!(q_pest, q_ast); 56 | } 57 | 58 | // Test case: 10_name_shorthand_object_data_nested 59 | // Tags: No tags 60 | #[test] 61 | fn test_10_name_shorthand_object_data_nested() { 62 | let q_ast = ::jsonpath_rust_impl::json_query!($.a.b.c); 63 | let q_pest = 64 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$.a.b.c"#).expect("failed to parse"); 65 | assert_eq!(q_pest, q_ast); 66 | } 67 | 68 | // Test case: 11_wildcard_shorthand_object_data 69 | // Tags: No tags 70 | #[test] 71 | fn test_11_wildcard_shorthand_object_data() { 72 | let q_ast = ::jsonpath_rust_impl::json_query!($.*); 73 | let q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$.*"#).expect("failed to parse"); 74 | assert_eq!(q_pest, q_ast); 75 | } 76 | 77 | // Test case: 12_wildcard_shorthand_array_data 78 | // Tags: No tags 79 | #[test] 80 | fn test_12_wildcard_shorthand_array_data() { 81 | let q_ast = ::jsonpath_rust_impl::json_query!($.*); 82 | let q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$.*"#).expect("failed to parse"); 83 | assert_eq!(q_pest, q_ast); 84 | } 85 | 86 | // Test case: 13_wildcard_selector_array_data 87 | // Tags: No tags 88 | #[test] 89 | fn test_13_wildcard_selector_array_data() { 90 | let q_ast = ::jsonpath_rust_impl::json_query!($[*]); 91 | let q_pest = 92 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[*]"#).expect("failed to parse"); 93 | assert_eq!(q_pest, q_ast); 94 | } 95 | 96 | // Test case: 14_wildcard_shorthand_then_name_shorthand 97 | // Tags: No tags 98 | #[test] 99 | fn test_14_wildcard_shorthand_then_name_shorthand() { 100 | let q_ast = ::jsonpath_rust_impl::json_query!($.*.a); 101 | let q_pest = 102 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$.*.a"#).expect("failed to parse"); 103 | assert_eq!(q_pest, q_ast); 104 | } 105 | 106 | // Test case: 15_multiple_selectors 107 | // Tags: No tags 108 | #[test] 109 | fn test_15_multiple_selectors() { 110 | let q_ast = ::jsonpath_rust_impl::json_query!($[0,2]); 111 | let q_pest = 112 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[0,2]"#).expect("failed to parse"); 113 | assert_eq!(q_pest, q_ast); 114 | } 115 | 116 | // Test case: 19_multiple_selectors_name_and_index_array_data(EDITED: due to macro limitations) 117 | // Tags: No tags 118 | #[test] 119 | fn test_19_multiple_selectors_name_and_index_array_data() { 120 | let q_ast = ::jsonpath_rust_impl::json_query!($["a",1]); 121 | let q_pest_single = 122 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$['a',1]"#).expect("failed to parse"); 123 | let q_pest_double = 124 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$["a",1]"#).expect("failed to parse"); 125 | assert_eq!(q_pest_single, q_ast); 126 | assert_eq!(q_pest_single, q_pest_double); 127 | } 128 | 129 | // Test case: 20_multiple_selectors_name_and_index_object_data(EDITED: due to macro limitations) 130 | // Tags: No tags 131 | #[test] 132 | fn test_20_multiple_selectors_name_and_index_object_data() { 133 | let q_ast = ::jsonpath_rust_impl::json_query!($["a",1]); 134 | let q_pest = 135 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$['a',1]"#).expect("failed to parse"); 136 | assert_eq!(q_pest, q_ast); 137 | } 138 | 139 | // Test case: 21_multiple_selectors_index_and_slice 140 | // Tags: No tags 141 | #[test] 142 | fn test_21_multiple_selectors_index_and_slice() { 143 | let q_ast = ::jsonpath_rust_impl::json_query!($[1,5:7]); 144 | let q_pest = 145 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[1,5:7]"#).expect("failed to parse"); 146 | assert_eq!(q_pest, q_ast); 147 | } 148 | 149 | // Test case: 22_multiple_selectors_index_and_slice_overlapping 150 | // Tags: No tags 151 | #[test] 152 | fn test_22_multiple_selectors_index_and_slice_overlapping() { 153 | let q_ast = ::jsonpath_rust_impl::json_query!($[1,0:3]); 154 | let q_pest = 155 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[1,0:3]"#).expect("failed to parse"); 156 | assert_eq!(q_pest, q_ast); 157 | } 158 | 159 | // Test case: 23_multiple_selectors_duplicate_index 160 | // Tags: No tags 161 | #[test] 162 | fn test_23_multiple_selectors_duplicate_index() { 163 | let q_ast = ::jsonpath_rust_impl::json_query!($[1,1]); 164 | let q_pest = 165 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[1,1]"#).expect("failed to parse"); 166 | assert_eq!(q_pest, q_ast); 167 | } 168 | 169 | // Test case: 24_multiple_selectors_wildcard_and_index 170 | // Tags: No tags 171 | #[test] 172 | fn test_24_multiple_selectors_wildcard_and_index() { 173 | let q_ast = ::jsonpath_rust_impl::json_query!($[*,1]); 174 | let q_pest = 175 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[*,1]"#).expect("failed to parse"); 176 | assert_eq!(q_pest, q_ast); 177 | } 178 | 179 | // Test case: 25_multiple_selectors_wildcard_and_name(EDITED: due to macro limitations) 180 | // Tags: No tags 181 | #[test] 182 | fn test_25_multiple_selectors_wildcard_and_name() { 183 | let q_ast = ::jsonpath_rust_impl::json_query!($[*,"a"]); 184 | let q_pest = 185 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[*,'a']"#).expect("failed to parse"); 186 | assert_eq!(q_pest, q_ast); 187 | } 188 | 189 | // Test case: 26_multiple_selectors_wildcard_and_slice 190 | // Tags: No tags 191 | #[test] 192 | fn test_26_multiple_selectors_wildcard_and_slice() { 193 | let q_ast = ::jsonpath_rust_impl::json_query!($[*,0:2]); 194 | let q_pest = 195 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[*,0:2]"#).expect("failed to parse"); 196 | assert_eq!(q_pest, q_ast); 197 | } 198 | 199 | // Test case: 27_multiple_selectors_multiple_wildcards 200 | // Tags: No tags 201 | #[test] 202 | fn test_27_multiple_selectors_multiple_wildcards() { 203 | let q_ast = ::jsonpath_rust_impl::json_query!($[*,*]); 204 | let q_pest = 205 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[*,*]"#).expect("failed to parse"); 206 | assert_eq!(q_pest, q_ast); 207 | } 208 | 209 | // Test case: 29_descendant_segment_index 210 | // Tags: No tags 211 | #[test] 212 | fn test_29_descendant_segment_index() { 213 | let q_ast = ::jsonpath_rust_impl::json_query!($..[1]); 214 | let q_pest = 215 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$..[1]"#).expect("failed to parse"); 216 | assert_eq!(q_pest, q_ast); 217 | } 218 | 219 | // Test case: 30_descendant_segment_name_shorthand 220 | // Tags: No tags 221 | #[test] 222 | fn test_30_descendant_segment_name_shorthand() { 223 | let q_ast = ::jsonpath_rust_impl::json_query!($..a); 224 | let q_pest = 225 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$..a"#).expect("failed to parse"); 226 | assert_eq!(q_pest, q_ast); 227 | } 228 | 229 | // Test case: 31_descendant_segment_wildcard_shorthand_array_data 230 | // Tags: No tags 231 | #[test] 232 | fn test_31_descendant_segment_wildcard_shorthand_array_data() { 233 | let q_ast = ::jsonpath_rust_impl::json_query!($..*); 234 | let q_pest = 235 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$..*"#).expect("failed to parse"); 236 | assert_eq!(q_pest, q_ast); 237 | } 238 | 239 | // Test case: 32_descendant_segment_wildcard_selector_array_data 240 | // Tags: No tags 241 | #[test] 242 | fn test_32_descendant_segment_wildcard_selector_array_data() { 243 | let q_ast = ::jsonpath_rust_impl::json_query!($..[*]); 244 | let q_pest = 245 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$..[*]"#).expect("failed to parse"); 246 | assert_eq!(q_pest, q_ast); 247 | } 248 | 249 | // Test case: 33_descendant_segment_wildcard_selector_nested_arrays 250 | // Tags: No tags 251 | #[test] 252 | fn test_33_descendant_segment_wildcard_selector_nested_arrays() { 253 | let q_ast = ::jsonpath_rust_impl::json_query!($..[*]); 254 | let q_pest = 255 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$..[*]"#).expect("failed to parse"); 256 | assert_eq!(q_pest, q_ast); 257 | } 258 | 259 | // Test case: 34_descendant_segment_wildcard_selector_nested_objects 260 | // Tags: No tags 261 | #[test] 262 | fn test_34_descendant_segment_wildcard_selector_nested_objects() { 263 | let q_ast = ::jsonpath_rust_impl::json_query!($..[*]); 264 | let q_pest = 265 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$..[*]"#).expect("failed to parse"); 266 | assert_eq!(q_pest, q_ast); 267 | } 268 | 269 | // Test case: 35_descendant_segment_wildcard_shorthand_object_data 270 | // Tags: No tags 271 | #[test] 272 | fn test_35_descendant_segment_wildcard_shorthand_object_data() { 273 | let q_ast = ::jsonpath_rust_impl::json_query!($..*); 274 | let q_pest = 275 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$..*"#).expect("failed to parse"); 276 | assert_eq!(q_pest, q_ast); 277 | } 278 | 279 | // Test case: 36_descendant_segment_wildcard_shorthand_nested_data 280 | // Tags: No tags 281 | #[test] 282 | fn test_36_descendant_segment_wildcard_shorthand_nested_data() { 283 | let q_ast = ::jsonpath_rust_impl::json_query!($..*); 284 | let q_pest = 285 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$..*"#).expect("failed to parse"); 286 | assert_eq!(q_pest, q_ast); 287 | } 288 | 289 | // Test case: 37_descendant_segment_multiple_selectors(EDITED: due to macro limitations) 290 | // Tags: No tags 291 | #[test] 292 | fn test_37_descendant_segment_multiple_selectors() { 293 | let q_ast = ::jsonpath_rust_impl::json_query!($..["a","d"]); 294 | let q_pest = 295 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$..['a','d']"#).expect("failed to parse"); 296 | assert_eq!(q_pest, q_ast); 297 | } 298 | 299 | // Test case: 38_descendant_segment_object_traversal_multiple_selectors(EDITED: due to macro limitations) 300 | // Tags: No tags 301 | #[test] 302 | fn test_38_descendant_segment_object_traversal_multiple_selectors() { 303 | let q_ast = ::jsonpath_rust_impl::json_query!($..["a","d"]); 304 | let q_pest = 305 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$..['a','d']"#).expect("failed to parse"); 306 | assert_eq!(q_pest, q_ast); 307 | } 308 | -------------------------------------------------------------------------------- /jsonpath-rust-impl/tests/rfc9535_compile_tests/basic/compile_but_expect_err.rs: -------------------------------------------------------------------------------- 1 | // Test case: 01_no_leading_whitespace 2 | // Tags: whitespace 3 | #[test] 4 | fn test_01_no_leading_whitespace() { 5 | // let q_ast = ::jsonpath_rust_impl::json_query!( $); 6 | let _q_pest = 7 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#" $"#).expect_err("should not parse"); 8 | } 9 | 10 | // Test case: 02_no_trailing_whitespace 11 | // Tags: whitespace 12 | #[test] 13 | fn test_02_no_trailing_whitespace() { 14 | // let q_ast = ::jsonpath_rust_impl::json_query!($ ); 15 | let _q_pest = 16 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$ "#).expect_err("should not parse"); 17 | } 18 | 19 | // Test case: 06_name_shorthand_symbol 20 | // Tags: No tags 21 | #[test] 22 | fn test_06_name_shorthand_symbol() { 23 | // let q_ast = ::jsonpath_rust_impl::json_query!($.&); 24 | let _q_pest = 25 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$.&"#).expect_err("should not parse"); 26 | } 27 | 28 | // Test case: 07_name_shorthand_number 29 | // Tags: No tags 30 | #[test] 31 | fn test_07_name_shorthand_number() { 32 | // let q_ast = ::jsonpath_rust_impl::json_query!($.1); 33 | let _q_pest = 34 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$.1"#).expect_err("should not parse"); 35 | } 36 | 37 | // Test case: 16_multiple_selectors_space_instead_of_comma 38 | // Tags: whitespace 39 | #[test] 40 | fn test_16_multiple_selectors_space_instead_of_comma() { 41 | // let q_ast = ::jsonpath_rust_impl::json_query!($[0 2]); 42 | let _q_pest = 43 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[0 2]"#).expect_err("should not parse"); 44 | } 45 | 46 | // Test case: 17_selector_leading_comma 47 | // Tags: No tags 48 | #[test] 49 | fn test_17_selector_leading_comma() { 50 | // let q_ast = ::jsonpath_rust_impl::json_query!($[,0]); 51 | let _q_pest = 52 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[,0]"#).expect_err("should not parse"); 53 | } 54 | 55 | // Test case: 18_selector_trailing_comma 56 | // Tags: No tags 57 | #[test] 58 | fn test_18_selector_trailing_comma() { 59 | // let q_ast = ::jsonpath_rust_impl::json_query!($[0,]); 60 | let _q_pest = 61 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[0,]"#).expect_err("should not parse"); 62 | } 63 | 64 | // Test case: 28_empty_segment 65 | // Tags: No tags 66 | #[test] 67 | fn test_28_empty_segment() { 68 | // let q_ast = ::jsonpath_rust_impl::json_query!($[]); 69 | let _q_pest = 70 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[]"#).expect_err("should not parse"); 71 | } 72 | 73 | // Test case: 39_bald_descendant_segment 74 | // Tags: No tags 75 | #[test] 76 | fn test_39_bald_descendant_segment() { 77 | // let q_ast = ::jsonpath_rust_impl::json_query!($..); 78 | let _q_pest = 79 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$.."#).expect_err("should not parse"); 80 | } 81 | 82 | // Test case: 40_current_node_identifier_without_filter_selector 83 | // Tags: No tags 84 | #[test] 85 | fn test_40_current_node_identifier_without_filter_selector() { 86 | // let q_ast = ::jsonpath_rust_impl::json_query!($[@.a]); 87 | let _q_pest = 88 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[@.a]"#).expect_err("should not parse"); 89 | } 90 | 91 | // Test case: 41_root_node_identifier_in_brackets_without_filter_selector 92 | // Tags: No tags 93 | #[test] 94 | fn test_41_root_node_identifier_in_brackets_without_filter_selector() { 95 | // let q_ast = ::jsonpath_rust_impl::json_query!($[$.a]); 96 | let _q_pest = 97 | ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[$.a]"#).expect_err("should not parse"); 98 | } 99 | -------------------------------------------------------------------------------- /jsonpath-rust-impl/tests/rfc9535_compile_tests/basic/does_not_compile.rs: -------------------------------------------------------------------------------- 1 | // Test case: 01_no_leading_whitespace(DISABLED: due to macro limitations) 2 | // Tags: whitespace 3 | // fn test_01_no_leading_whitespace() { 4 | // ::jsonpath_rust_impl::json_query!( $); 5 | // } 6 | 7 | // Test case: 02_no_trailing_whitespace(DISABLED: due to macro limitations) 8 | // Tags: whitespace 9 | // fn test_02_no_trailing_whitespace() { 10 | // ::jsonpath_rust_impl::json_query!($ ); 11 | // } 12 | 13 | // Test case: 06_name_shorthand_symbol 14 | // Tags: No tags 15 | fn test_06_name_shorthand_symbol() { 16 | ::jsonpath_rust_impl::json_query!($.&); 17 | } 18 | 19 | // Test case: 07_name_shorthand_number 20 | // Tags: No tags 21 | fn test_07_name_shorthand_number() { 22 | ::jsonpath_rust_impl::json_query!($.1); 23 | } 24 | 25 | // Test case: 16_multiple_selectors_space_instead_of_comma 26 | // Tags: whitespace 27 | fn test_16_multiple_selectors_space_instead_of_comma() { 28 | ::jsonpath_rust_impl::json_query!($[0 2]); 29 | } 30 | 31 | // Test case: 17_selector_leading_comma 32 | // Tags: No tags 33 | fn test_17_selector_leading_comma() { 34 | ::jsonpath_rust_impl::json_query!($[,0]); 35 | } 36 | 37 | // Test case: 18_selector_trailing_comma 38 | // Tags: No tags 39 | fn test_18_selector_trailing_comma() { 40 | ::jsonpath_rust_impl::json_query!($[0,]); 41 | } 42 | 43 | // Test case: 28_empty_segment 44 | // Tags: No tags 45 | fn test_28_empty_segment() { 46 | ::jsonpath_rust_impl::json_query!($[]); 47 | } 48 | 49 | // Test case: 39_bald_descendant_segment 50 | // Tags: No tags 51 | fn test_39_bald_descendant_segment() { 52 | ::jsonpath_rust_impl::json_query!($..); 53 | } 54 | 55 | // Test case: 40_current_node_identifier_without_filter_selector 56 | // Tags: No tags 57 | fn test_40_current_node_identifier_without_filter_selector() { 58 | ::jsonpath_rust_impl::json_query!($[@.a]); 59 | } 60 | 61 | // Test case: 41_root_node_identifier_in_brackets_without_filter_selector 62 | // Tags: No tags 63 | fn test_41_root_node_identifier_in_brackets_without_filter_selector() { 64 | ::jsonpath_rust_impl::json_query!($[$.a]); 65 | } 66 | 67 | fn main() {} 68 | -------------------------------------------------------------------------------- /jsonpath-rust-impl/tests/rfc9535_compile_tests/basic/does_not_compile.stderr: -------------------------------------------------------------------------------- 1 | error: unexpected token 2 | --> tests/rfc9535_compile_tests/basic/does_not_compile.rs:16:41 3 | | 4 | 16 | ::jsonpath_rust_impl::json_query!($.&); 5 | | ^ 6 | 7 | error: unexpected token 8 | --> tests/rfc9535_compile_tests/basic/does_not_compile.rs:22:41 9 | | 10 | 22 | ::jsonpath_rust_impl::json_query!($.1); 11 | | ^ 12 | 13 | error: unexpected token, expected `]` 14 | --> tests/rfc9535_compile_tests/basic/does_not_compile.rs:28:43 15 | | 16 | 28 | ::jsonpath_rust_impl::json_query!($[0 2]); 17 | | ^ 18 | 19 | error: unexpected token 20 | --> tests/rfc9535_compile_tests/basic/does_not_compile.rs:34:41 21 | | 22 | 34 | ::jsonpath_rust_impl::json_query!($[,0]); 23 | | ^ 24 | 25 | error: unexpected end of input 26 | --> tests/rfc9535_compile_tests/basic/does_not_compile.rs:40:43 27 | | 28 | 40 | ::jsonpath_rust_impl::json_query!($[0,]); 29 | | ^ 30 | 31 | error: unexpected end of input 32 | --> tests/rfc9535_compile_tests/basic/does_not_compile.rs:46:41 33 | | 34 | 46 | ::jsonpath_rust_impl::json_query!($[]); 35 | | ^ 36 | 37 | error: unexpected end of input 38 | --> tests/rfc9535_compile_tests/basic/does_not_compile.rs:52:5 39 | | 40 | 52 | ::jsonpath_rust_impl::json_query!($..); 41 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 42 | | 43 | = note: this error originates in the macro `::jsonpath_rust_impl::json_query` (in Nightly builds, run with -Z macro-backtrace for more info) 44 | 45 | error: unexpected token 46 | --> tests/rfc9535_compile_tests/basic/does_not_compile.rs:58:41 47 | | 48 | 58 | ::jsonpath_rust_impl::json_query!($[@.a]); 49 | | ^ 50 | 51 | error: unexpected token 52 | --> tests/rfc9535_compile_tests/basic/does_not_compile.rs:64:41 53 | | 54 | 64 | ::jsonpath_rust_impl::json_query!($[$.a]); 55 | | ^ 56 | -------------------------------------------------------------------------------- /jsonpath-rust-impl/tests/rfc9535_compile_tests/basic/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod compile_and_passes; 2 | pub(crate) mod compile_but_expect_err; 3 | -------------------------------------------------------------------------------- /jsonpath-rust-impl/tests/rfc9535_compile_tests/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod basic; 2 | -------------------------------------------------------------------------------- /jsonpath-rust-impl/tests/test.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_ascii_idents)] 2 | 3 | mod rfc9535_compile_tests; 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use jsonpath_ast::ast::Main; 8 | use jsonpath_rust_impl::json_query; 9 | 10 | #[test] 11 | fn scratch() { 12 | let q_ast = json_query!($.values[?match(@, $.regex)]).into(); 13 | json_query!( $..[1] ); 14 | json_query!( $[1,::] ); 15 | 16 | assert_eq!( 17 | json_query!( $["a",1] ), 18 | Main::try_from_pest_parse("$['a',1]").expect("failed to parse") 19 | ); 20 | Main::try_from_pest_parse("$['a',1,4]").expect("failed to parse"); 21 | assert_eq!( 22 | json_query!( $ . _ ), 23 | Main::try_from_pest_parse("$._").expect("failed to parse") 24 | ); 25 | let _ = json_query!($["a☺a"]); 26 | let _ = json_query!($[0,2]); 27 | let _ = Main::try_from_pest_parse("$ [ 0 , 2]") 28 | .expect("should work"); 29 | let _ = Main::try_from_pest_parse(" $[0,2]").expect_err("failed to parse"); 30 | let _ = Main::try_from_pest_parse("$[0,2] ").expect_err("failed to parse"); 31 | } 32 | 33 | #[test] 34 | fn syn_and_pest_are_equal() { 35 | let q1 = ( 36 | json_query!( $[?@.thing > 4] ), 37 | Main::try_from_pest_parse("$[?@.thing > 4]").expect("failed to parse"), 38 | ); 39 | 40 | assert_eq!( 41 | json_query!( $[?@.thing > 4] ), 42 | Main::try_from_pest_parse("$[?@.thing > 4]").expect("failed to parse") 43 | ); 44 | 45 | // let q2: Main = Main::try_from_pest_parse("$[?@.thing >= 5, ?@.thing <= 6]").expect("failed to parse"); 46 | // let q3: Main = Main::try_from_pest_parse("$[?@.thing >= 5, ?@.thing <= 6.0]").expect("failed to parse"); 47 | // let q4: Main = Main::try_from_pest_parse("$[?@.thing >= 5, ?@.thing == true]").expect("failed to parse"); 48 | // let q5: Main = Main::try_from_pest_parse("$[?@.thing >= 5, ?@.thing != null]").expect("failed to parse"); 49 | 50 | // let q1: Main = json_query!($[?@.thing >= 5]); 51 | // let q2: Main = Main::try_from_pest_parse("$[?@.thing >= 5]").expect("failed to parse"); 52 | 53 | assert_eq!(q1.0, q1.1); 54 | } 55 | 56 | // fn test_☺_() 57 | 58 | /// Common function to run trybuild for all in suite dir 59 | fn trybuild(dir: &str) { 60 | let t = ::trybuild::TestCases::new(); 61 | let fail_path = format!("tests/rfc9535_compile_tests/{}/does_not_compile.rs", dir); 62 | t.compile_fail(fail_path); 63 | } 64 | 65 | #[test] 66 | fn test_rfc_case_basic() { 67 | trybuild("basic"); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /rfc9535/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonpath-rust-rfc9535" 3 | description = "The tests to check the compliance with RFC 9535" 4 | version = "0.0.1" 5 | edition = "2021" 6 | license = "MIT" 7 | readme = "README.md" 8 | 9 | 10 | [dependencies] 11 | jsonpath-rust = { path = "../", features = ["compiled-path"]} 12 | serde_json = "1.0" 13 | serde = { version = "1.0.217", features = ["derive"] } 14 | colored = "2.0" 15 | chrono = "0.4.39" 16 | -------------------------------------------------------------------------------- /rfc9535/README.md: -------------------------------------------------------------------------------- 1 | # Tests for RFC9535 2 | 3 | This directory contains tests for the [RFC9535](https://www.rfc-editor.org/info/rfc9535) implementation. 4 | 5 | ## Usage 6 | Run `main.rs`. 7 | It will print the test results in the console with the following format: 8 | ``` 9 | ... 10 | Skipping test case: `` because of reason: `reason` 11 | ... 12 | Failed tests: 13 | 14 | ------- ------- 15 | 16 | 17 | ... 18 | 19 | RFC9535 Compliance tests: 20 | Total: 671 21 | Passed: 209 22 | Failed: 462 23 | Skipped: 25 where 18 to fix in 5 issues 24 | 25 | ``` 26 | 27 | The results will be saved in the `results.csv` file. 28 | 29 | The cases can be filtered using `filtered_cases.json` file. 30 | The file should contain json array with the test case names that should be filtered out and the reason. 31 | -------------------------------------------------------------------------------- /rfc9535/src/console.rs: -------------------------------------------------------------------------------- 1 | use crate::suite::TestFailure; 2 | use chrono::Local; 3 | use colored::Colorize; 4 | use std::fs::{File, OpenOptions}; 5 | use std::io::Write; 6 | use std::io::{BufRead, BufReader, Error}; 7 | pub fn process_results( 8 | results: Vec, 9 | skipped_cases: usize, 10 | skipped_to_fix: usize, 11 | issues: usize, 12 | ) -> Result<(), Error> { 13 | let (passed, failed): (Vec<_>, Vec<_>) = results.into_iter().partition(TestResult::is_ok); 14 | let total = passed.len() + failed.len() + skipped_cases; 15 | let passed_count = passed.len(); 16 | let failed_count = failed.len(); 17 | let date = Local::now().format("%Y-%m-%d %H:%M:%S").to_string(); 18 | 19 | if failed_count > 0 { 20 | println!("\n{}:", "Failed tests".bold()); 21 | println!("\n"); 22 | } 23 | for failure in failed.iter() { 24 | if let Err(TestFailure(case, reason)) = failure { 25 | println!(" ------- {} -------", case.name.bold()); 26 | println!("{}", reason.bold().red()); 27 | } 28 | } 29 | 30 | let mut file = OpenOptions::new() 31 | .create(true) 32 | .append(true) 33 | .open("test_suite/results.csv")?; 34 | writeln!( 35 | file, 36 | "{}; {}; {}; {}", 37 | total, passed_count, failed_count, date 38 | )?; 39 | 40 | clean_file(10)?; 41 | 42 | println!( 43 | "\n{}:\n{}\n{}\n{}\n{}", 44 | format!("RFC9535 Compliance tests").underline().bold(), 45 | format!("Total: {}", total).bold(), 46 | format!("Passed: {}", passed_count).green().bold(), 47 | format!("Failed: {}", failed_count).red().bold(), 48 | format!( 49 | "Skipped: {} where {} to fix in {} issues", 50 | skipped_cases, skipped_to_fix, issues 51 | ) 52 | .bold() 53 | ); 54 | Ok(()) 55 | } 56 | 57 | fn clean_file(limit: usize) -> Result<(), Error> { 58 | let file_path = "test_suite/results.csv"; 59 | let file = File::open(file_path)?; 60 | let reader = BufReader::new(file); 61 | let lines: Vec = reader.lines().collect::>()?; 62 | 63 | if lines.len() > limit { 64 | let header = &lines[0]; 65 | let trimmed_lines = [&[header.clone()], &lines[lines.len() - limit..]].concat(); 66 | 67 | let mut file = OpenOptions::new() 68 | .write(true) 69 | .truncate(true) 70 | .open(file_path)?; 71 | for line in trimmed_lines { 72 | writeln!(file, "{}", line)?; 73 | } 74 | } 75 | 76 | Ok(()) 77 | } 78 | 79 | pub type TestResult<'a> = Result<(), TestFailure<'a>>; 80 | -------------------------------------------------------------------------------- /rfc9535/src/main.rs: -------------------------------------------------------------------------------- 1 | 2 | mod console; 3 | mod suite; 4 | use crate::suite::get_suite; 5 | use colored::Colorize; 6 | use console::TestResult; 7 | use std::io::Error; 8 | use std::io::Write; 9 | use std::str::FromStr; 10 | 11 | fn main() -> Result<(), Error> { 12 | let (cases, skipped, skipped_to_fix, issues) = get_suite()?; 13 | console::process_results( 14 | cases 15 | .iter() 16 | .map(suite::handle_test_case) 17 | .collect::>(), 18 | skipped, 19 | skipped_to_fix, 20 | issues, 21 | ) 22 | } 23 | 24 | -------------------------------------------------------------------------------- /rfc9535/src/suite.rs: -------------------------------------------------------------------------------- 1 | use crate::console::TestResult; 2 | use colored::Colorize; 3 | use jsonpath_rust::parser::parse_json_path; 4 | use jsonpath_rust::JsonPath; 5 | use serde_json::Value; 6 | use std::str::FromStr; 7 | 8 | type SkippedCases = usize; 9 | type SkippedCasesToFix = usize; 10 | type Issues = usize; 11 | fn escape_control_chars(s: &str) -> String { 12 | s.replace("\n", "\\n") 13 | .replace("\t", "\\t") 14 | .replace("\r", "\\r") 15 | } 16 | pub fn get_suite( 17 | ) -> Result<(Vec, SkippedCases, SkippedCasesToFix, Issues), std::io::Error> { 18 | let file = std::fs::File::open("test_suite/jsonpath-compliance-test-suite/cts.json")?; 19 | let suite: TestCases = serde_json::from_reader(std::io::BufReader::new(file))?; 20 | let suite: Vec = suite.tests; 21 | 22 | let filter = std::fs::File::open("test_suite/filtered_cases.json")?; 23 | let filter: Vec = serde_json::from_reader(std::io::BufReader::new(filter))?; 24 | let mut skipped_cases = 0; 25 | let mut skipped_cases_to_fix = 0; 26 | let mut issues = vec![]; 27 | Ok(( 28 | suite 29 | .into_iter() 30 | .filter(|case| { 31 | if let Some(f) = filter.iter().find(|filter| case.name == filter.name) { 32 | println!( 33 | r#"Skipping test case:`{}` with the reason: `{}`"#, 34 | escape_control_chars(&case.name).green(), 35 | escape_control_chars(&f.reason).green() 36 | ); 37 | skipped_cases += 1; 38 | if f.expected_to_fix { 39 | skipped_cases_to_fix += 1; 40 | if !issues.contains(&f.issue) { 41 | issues.push(f.issue); 42 | } 43 | } 44 | false 45 | } else { 46 | true 47 | } 48 | }) 49 | .collect(), 50 | skipped_cases, 51 | skipped_cases_to_fix, 52 | issues.len(), 53 | )) 54 | } 55 | pub fn handle_test_case(case: &TestCase) -> TestResult { 56 | let jspath = parse_json_path(case.selector.as_str()); 57 | 58 | if case.invalid_selector { 59 | if jspath.is_ok() { 60 | Err(TestFailure::invalid(case)) 61 | } else { 62 | Ok(()) 63 | } 64 | } else { 65 | if let Some(doc) = case.document.as_ref() { 66 | let p = case.selector.as_str(); 67 | let result = doc.query(p).map(|vs| { 68 | vs.into_iter() 69 | .map(|v| (*v).clone()) 70 | .collect::>() 71 | .into() 72 | }); 73 | 74 | if result.is_err() { 75 | println!("---- Parsing error: '{}'", case.name); 76 | println!("reason: {}", result.as_ref().err().unwrap()); 77 | println!("selector: {}", case.selector); 78 | println!("document: {}", doc); 79 | return Err(TestFailure::invalid(case)); 80 | } 81 | let result = result.unwrap(); 82 | 83 | match (case.result.as_ref(), case.results.as_ref()) { 84 | (Some(expected), _) => { 85 | if result == *expected { 86 | Ok(()) 87 | } else { 88 | Err(TestFailure::match_one(case, &result)) 89 | } 90 | } 91 | (None, Some(expected)) => { 92 | if expected.iter().any(|exp| result == *exp) { 93 | Ok(()) 94 | } else { 95 | Err(TestFailure::match_any(case, &result)) 96 | } 97 | } 98 | _ => Ok(()), 99 | } 100 | } else { 101 | Ok(()) 102 | } 103 | } 104 | } 105 | 106 | #[derive(serde::Deserialize)] 107 | struct FilterCase { 108 | name: String, 109 | reason: String, 110 | expected_to_fix: bool, 111 | issue: usize, 112 | } 113 | 114 | #[derive(serde::Deserialize)] 115 | pub struct TestCase { 116 | pub(crate) name: String, 117 | pub(crate) selector: String, 118 | pub(crate) document: Option, 119 | pub(crate) result: Option, 120 | pub(crate) results: Option>, 121 | #[serde(default)] 122 | pub(crate) invalid_selector: bool, 123 | } 124 | #[derive(serde::Deserialize)] 125 | pub struct TestCases { 126 | pub(crate) description: String, 127 | pub(crate) tests: Vec, 128 | } 129 | 130 | pub struct TestFailure<'a>(pub &'a TestCase, pub String); 131 | 132 | impl<'a> TestFailure<'a> { 133 | pub(crate) fn invalid(case: &'a TestCase) -> Self { 134 | TestFailure( 135 | case, 136 | format!( 137 | "The path should have been considered invalid: {}", 138 | case.selector 139 | ), 140 | ) 141 | } 142 | 143 | pub(crate) fn match_one(case: &'a TestCase, actual: &Value) -> Self { 144 | TestFailure( 145 | case, 146 | format!( 147 | "Actual did not match expected. Actual: {:?}, Expected: {:?}", 148 | actual, &case.result 149 | ), 150 | ) 151 | } 152 | pub(crate) fn match_any(case: &'a TestCase, actual: &Value) -> Self { 153 | TestFailure( 154 | case, 155 | format!( 156 | "Actual did not match expected. Actual: {:?}, Expected: {:?}", 157 | actual, &case.results 158 | ), 159 | ) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /rfc9535/test_suite/filtered_cases.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "functions, value, result must be compared", 4 | "reason": "To handle it later either at the parser level or during execution", 5 | "expected_to_fix": true, 6 | "issue": 1 7 | }, 8 | { 9 | "name": "functions, length, result must be compared", 10 | "reason": "To handle it later either at the parser level or during execution", 11 | "expected_to_fix": true, 12 | "issue": 1 13 | }, 14 | { 15 | "name": "functions, count, result must be compared", 16 | "reason": "To handle it later either at the parser level or during execution", 17 | "expected_to_fix": true, 18 | "issue": 1 19 | }, 20 | { 21 | "name": "functions, length, non-singular query arg", 22 | "reason": "To handle it later either at the parser level or during execution", 23 | "expected_to_fix": true, 24 | "issue": 1 25 | }, 26 | 27 | { 28 | "name": "functions, search, dot matcher on \\u2029", 29 | "reason": "Rust by default handles \r as a symbol and rfc9485 it is not a symbol(\\n, \\r, \\u2028, and \\u2029).)", 30 | "expected_to_fix": true, 31 | "issue": 2 32 | }, 33 | { 34 | "name": "functions, search, dot matcher on \\u2028", 35 | "reason": "Rust by default handles \r as a symbol and rfc9485 it is not a symbol(\\n, \\r, \\u2028, and \\u2029).)", 36 | "expected_to_fix": true, 37 | "issue": 2 38 | }, 39 | { 40 | "name": "functions, match, dot matcher on \\u2029", 41 | "reason": "Rust by default handles \r as a symbol and rfc9485 it is not a symbol(\\n, \\r, \\u2028, and \\u2029).)", 42 | "expected_to_fix": true, 43 | "issue": 2 44 | }, 45 | { 46 | "name": "functions, match, dot matcher on \\u2028", 47 | "reason": "Rust by default handles \r as a symbol and rfc9485 it is not a symbol(\\n, \\r, \\u2028, and \\u2029).)", 48 | "expected_to_fix": true, 49 | "issue": 2 50 | }, 51 | 52 | { 53 | "name": "basic, descendant segment, multiple selectors", 54 | "reason": "The result is correct but the order differs from expected since we process selector by selector", 55 | "expected_to_fix": false, 56 | "issue": 0 57 | } , 58 | { 59 | "name": "basic, descendant segment, object traversal, multiple selectors", 60 | "reason": "The result is correct but the order differs from expected since we process selector by selector", 61 | "expected_to_fix": false, 62 | "issue": 0 63 | }, 64 | 65 | { 66 | "name": "name selector, double quotes, escaped ☺, lower case hex", 67 | "reason": "This case fails without quotes '..' and there is a bug to fix in the parser to handle it", 68 | "expected_to_fix": true, 69 | "issue": 3 70 | }, 71 | { 72 | "name": "name selector, single quotes, escaped ☺, lower case hex", 73 | "reason": "This case fails without quotes '..' and there is a bug to fix in the parser to handle it", 74 | "expected_to_fix": true, 75 | "issue": 3 76 | }, 77 | 78 | { 79 | "name": "filter, string literal, escaped single quote in single quotes", 80 | "reason": "Should be extra logic to handle this special case", 81 | "expected_to_fix": true, 82 | "issue": 4 83 | }, 84 | 85 | { 86 | "name": "filter, string literal, escaped double quote in double quotes", 87 | "reason": "Should be extra logic to handle this special case", 88 | "expected_to_fix": true, 89 | "issue": 4 90 | }, 91 | 92 | { 93 | "name": "name selector, single quotes, surrogate pair \uD83D\uDE00", 94 | "reason": "As test it works but in the doc it has double \\", 95 | "expected_to_fix": false, 96 | "issue": 0 97 | } , 98 | { 99 | "name": "name selector, single quotes, surrogate pair \uD834\uDD1E", 100 | "reason": "As test it works but in the doc it has double \\", 101 | "expected_to_fix": false, 102 | "issue": 0 103 | }, 104 | { 105 | "name": "name selector, single quotes, escaped tab", 106 | "reason": "As rust test it passes", 107 | "expected_to_fix": false, 108 | "issue": 0 109 | }, 110 | { 111 | "name": "name selector, single quotes, escaped ☺, upper case hex", 112 | "reason": "As rust test it passes", 113 | "expected_to_fix": false, 114 | "issue": 0 115 | }, 116 | { 117 | "name": "name selector, single quotes, escaped carriage return", 118 | "reason": "As rust test it passes", 119 | "expected_to_fix": false, 120 | "issue": 0 121 | }, 122 | 123 | { 124 | "name": "name selector, double quotes, escaped double quote", 125 | "reason": "Figure out if it is a bug in the parser or in the test", 126 | "expected_to_fix": true, 127 | "issue": 5 128 | }, 129 | { 130 | "name": "name selector, double quotes, escaped backspace", 131 | "reason": "Figure out if it is a bug in the parser or in the test", 132 | "expected_to_fix": true, 133 | "issue": 5 134 | }, 135 | { 136 | "name": "name selector, double quotes, escaped form feed", 137 | "reason": "Figure out if it is a bug in the parser or in the test", 138 | "expected_to_fix": true, 139 | "issue": 5 140 | }, 141 | { 142 | "name": "name selector, double quotes, escaped line feed", 143 | "reason": "Figure out if it is a bug in the parser or in the test", 144 | "expected_to_fix": true, 145 | "issue": 5 146 | }, 147 | { 148 | "name": "name selector, double quotes, escaped carriage return", 149 | "reason": "Figure out if it is a bug in the parser or in the test", 150 | "expected_to_fix": true, 151 | "issue": 5 152 | }, 153 | { 154 | "name": "name selector, double quotes, escaped tab", 155 | "reason": "Figure out if it is a bug in the parser or in the test", 156 | "expected_to_fix": true, 157 | "issue": 5 158 | }, 159 | { 160 | "name": "name selector, single quotes, escaped backspace", 161 | "reason": "Figure out if it is a bug in the parser or in the test", 162 | "expected_to_fix": true, 163 | "issue": 5 164 | }, 165 | { 166 | "name": "name selector, single quotes, escaped form feed", 167 | "reason": "Figure out if it is a bug in the parser or in the test", 168 | "expected_to_fix": true, 169 | "issue": 5 170 | }, 171 | { 172 | "name": "name selector, single quotes, escaped line feed", 173 | "reason": "Figure out if it is a bug in the parser or in the test", 174 | "expected_to_fix": true, 175 | "issue": 5 176 | }, 177 | { 178 | "name": "name selector, double quotes, escaped ☺, upper case hex", 179 | "reason": "Figure out if it is a bug in the parser or in the test", 180 | "expected_to_fix": true, 181 | "issue": 5 182 | }, 183 | { 184 | "name": "name selector, double quotes, surrogate pair \uD834\uDD1E", 185 | "reason": "Figure out if it is a bug in the parser or in the test", 186 | "expected_to_fix": true, 187 | "issue": 6 188 | }, 189 | { 190 | "name": "name selector, double quotes, surrogate pair \uD83D\uDE00", 191 | "reason": "Figure out if it is a bug in the parser or in the test", 192 | "expected_to_fix": true, 193 | "issue": 6 194 | }, 195 | { 196 | "name": "name selector, double quotes, before high surrogates", 197 | "reason": "Figure out if it is a bug in the parser or in the test", 198 | "expected_to_fix": true, 199 | "issue": 6 200 | }, 201 | { 202 | "name": "name selector, double quotes, after low surrogates", 203 | "reason": "Figure out if it is a bug in the parser or in the test", 204 | "expected_to_fix": true, 205 | "issue": 6 206 | }, 207 | { 208 | "name": "name selector, single quotes, escaped single quote", 209 | "reason": "Figure out if it is a bug in the parser or in the test", 210 | "expected_to_fix": true, 211 | "issue": 7 212 | }, 213 | { 214 | "name": "filter, nested", 215 | "reason": "Figure out if it is a bug in the parser or in the test", 216 | "expected_to_fix": true, 217 | "issue": 8 218 | } 219 | ] -------------------------------------------------------------------------------- /rfc9535/test_suite/results.csv: -------------------------------------------------------------------------------- 1 | Total; Passed; Failed; Date 2 | 687; 652; 4; 2025-03-18 22:40:55 3 | 687; 652; 2; 2025-03-18 22:41:43 4 | 687; 652; 1; 2025-03-18 22:41:53 5 | 687; 652; 1; 2025-03-18 22:49:15 6 | 687; 652; 1; 2025-03-18 22:52:05 7 | 687; 652; 0; 2025-03-18 22:56:39 8 | 687; 652; 0; 2025-03-18 22:57:01 9 | 687; 652; 0; 2025-05-13 21:21:59 10 | 687; 652; 0; 2025-05-19 15:05:31 11 | 687; 652; 0; 2025-05-29 10:41:32 12 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Json path 2 | //! The library provides the basic functionality 3 | //! to find the slice of data according to the query. 4 | //! The idea comes from xpath for xml structures. 5 | //! The details can be found over [`there`] 6 | //! Therefore JSONPath is a query language for JSON, 7 | //! similar to XPath for XML. The jsonpath query is a set of assertions to specify the JSON fields that need to be verified. 8 | //! 9 | //! # Simple example 10 | //! Let's suppose we have a following json: 11 | //! ```json 12 | //! { 13 | //! "shop": { 14 | //! "orders": [ 15 | //! {"id": 1, "active": true}, 16 | //! {"id": 2 }, 17 | //! {"id": 3 }, 18 | //! {"id": 4, "active": true} 19 | //! ] 20 | //! } 21 | //! } 22 | //! ``` 23 | //! And we pursue to find all orders id having the field 'active' 24 | //! we can construct the jsonpath instance like that 25 | //! ```$.shop.orders[?(@.active)].id``` and get the result ``` [1,4] ``` 26 | //! 27 | //! # Another examples 28 | //! ```json 29 | //! { "store": { 30 | //! "book": [ 31 | //! { "category": "reference", 32 | //! "author": "Nigel Rees", 33 | //! "title": "Sayings of the Century", 34 | //! "price": 8.95 35 | //! }, 36 | //! { "category": "fiction", 37 | //! "author": "Evelyn Waugh", 38 | //! "title": "Sword of Honour", 39 | //! "price": 12.99 40 | //! }, 41 | //! { "category": "fiction", 42 | //! "author": "Herman Melville", 43 | //! "title": "Moby Dick", 44 | //! "isbn": "0-553-21311-3", 45 | //! "price": 8.99 46 | //! }, 47 | //! { "category": "fiction", 48 | //! "author": "J. R. R. Tolkien", 49 | //! "title": "The Lord of the Rings", 50 | //! "isbn": "0-395-19395-8", 51 | //! "price": 22.99 52 | //! } 53 | //! ], 54 | //! "bicycle": { 55 | //! "color": "red", 56 | //! "price": 19.95 57 | //! } 58 | //! } 59 | //! } 60 | //! ``` 61 | //! and examples 62 | //! - ``` $.store.book[*].author ``` : the authors of all books in the store 63 | //! - ``` $..book[?(@.isbn)]``` : filter all books with isbn number 64 | //! - ``` $..book[?(@.price<10)]``` : filter all books cheapier than 10 65 | //! - ``` $..*``` : all Elements in XML document. All members of JSON structure 66 | //! - ``` $..book[0,1]``` : The first two books 67 | //! - ``` $..book[:2]``` : The first two books 68 | //! 69 | //! # Operators 70 | //! 71 | //! - `$` : 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. 72 | //! - `@`Pointer to the current element inside the filter operations.It is used inside the filter operations to iterate the collection. 73 | //! - `*` or `[*]`Wildcard. It brings to the list all objects and elements regardless their names.It is analogue a flatmap operation. 74 | //! - `<..>`| Descent operation. It brings to the list all objects, children of that objects and etc It is analogue a flatmap operation. 75 | //! - `.` or `.['']`the key pointing to the field of the objectIt is used to obtain the specific field. 76 | //! - `['' (, '')]`the list of keysthe same usage as for a single key but for list 77 | //! - `[]`the filter getting the element by its index. 78 | //! - `[ (, )]`the list if elements of array according to their indexes representing these numbers. | 79 | //! - `[::]`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 ```[:]``` 80 | //! - `[?()]`the logical expression to filter elements in the list.It is used with arrays preliminary. 81 | //! 82 | //! 83 | //! [`there`]: https://goessner.net/articles/JsonPath/ 84 | #![allow(warnings)] 85 | 86 | pub mod query; 87 | 88 | #[allow(clippy::module_inception)] 89 | pub mod parser; 90 | 91 | #[macro_use] 92 | extern crate pest_derive; 93 | extern crate core; 94 | extern crate pest; 95 | 96 | use crate::query::queryable::Queryable; 97 | use crate::query::{Queried, QueryPath, QueryRef}; 98 | use serde_json::Value; 99 | 100 | /// A trait for types that can be queried with JSONPath. 101 | pub trait JsonPath: Queryable { 102 | /// Queries the value with a JSONPath expression and returns a vector of `QueryResult`. 103 | fn query_with_path(&self, path: &str) -> Queried>> { 104 | query::js_path(path, self) 105 | } 106 | 107 | /// Queries the value with a JSONPath expression and returns a vector of values. 108 | fn query_only_path(&self, path: &str) -> Queried> { 109 | query::js_path_path(path, self) 110 | } 111 | 112 | /// Queries the value with a JSONPath expression and returns a vector of values, omitting the path. 113 | fn query(&self, path: &str) -> Queried> { 114 | query::js_path_vals(path, self) 115 | } 116 | } 117 | 118 | impl JsonPath for Value {} 119 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::empty_docs)] 2 | pub mod errors; 3 | mod macros; 4 | pub mod model; 5 | mod tests; 6 | 7 | use crate::parser::errors::JsonPathError; 8 | use crate::parser::model::{ 9 | Comparable, Comparison, Filter, FilterAtom, FnArg, JpQuery, Literal, Segment, Selector, 10 | SingularQuery, SingularQuerySegment, Test, TestFunction, 11 | }; 12 | 13 | use pest::iterators::Pair; 14 | use pest::Parser; 15 | 16 | #[derive(Parser)] 17 | #[grammar = "parser/grammar/json_path_9535.pest"] 18 | pub(super) struct JSPathParser; 19 | // const MAX_VAL: i64 = 9007199254740991; // Maximum safe integer value in JavaScript 20 | // const MIN_VAL: i64 = -9007199254740991; // Minimum safe integer value in JavaScript 21 | 22 | pub type Parsed = Result; 23 | 24 | /// Parses a string into a [JsonPath]. 25 | /// 26 | /// # Errors 27 | /// 28 | /// Returns a variant of [crate::JsonPathParserError] if the parsing operation failed. 29 | pub fn parse_json_path(jp_str: &str) -> Parsed { 30 | JSPathParser::parse(Rule::main, jp_str) 31 | .map_err(Box::new)? 32 | .next() 33 | .ok_or(JsonPathError::UnexpectedPestOutput) 34 | .and_then(next_down) 35 | .and_then(jp_query) 36 | } 37 | 38 | pub fn jp_query(rule: Pair) -> Parsed { 39 | Ok(JpQuery::new(segments(next_down(rule)?)?)) 40 | } 41 | pub fn rel_query(rule: Pair) -> Parsed> { 42 | segments(next_down(rule)?) 43 | } 44 | 45 | pub fn segments(rule: Pair) -> Parsed> { 46 | let mut segments = vec![]; 47 | for r in rule.into_inner() { 48 | segments.push(segment(next_down(r)?)?); 49 | } 50 | Ok(segments) 51 | } 52 | 53 | pub fn child_segment(rule: Pair) -> Parsed { 54 | match rule.as_rule() { 55 | Rule::wildcard_selector => Ok(Segment::Selector(Selector::Wildcard)), 56 | Rule::member_name_shorthand => Ok(Segment::name(rule.as_str().trim())), 57 | Rule::bracketed_selection => { 58 | let mut selectors = vec![]; 59 | for r in rule.into_inner() { 60 | selectors.push(selector(r)?); 61 | } 62 | if selectors.len() == 1 { 63 | Ok(Segment::Selector( 64 | selectors 65 | .into_iter() 66 | .next() 67 | .ok_or(JsonPathError::empty("selector"))?, 68 | )) 69 | } else { 70 | Ok(Segment::Selectors(selectors)) 71 | } 72 | } 73 | _ => Err(rule.into()), 74 | } 75 | } 76 | 77 | pub fn segment(child: Pair) -> Parsed { 78 | match child.as_rule() { 79 | Rule::child_segment => { 80 | let val = child.as_str().strip_prefix(".").unwrap_or_default(); 81 | if val != val.trim_start() { 82 | Err(JsonPathError::InvalidJsonPath(format!( 83 | "Invalid child segment `{}`", 84 | child.as_str() 85 | ))) 86 | } else { 87 | child_segment(next_down(child)?) 88 | } 89 | } 90 | Rule::descendant_segment => { 91 | if child 92 | .as_str() 93 | .chars() 94 | .nth(2) 95 | .ok_or(JsonPathError::empty(child.as_str()))? 96 | .is_whitespace() 97 | { 98 | Err(JsonPathError::InvalidJsonPath(format!( 99 | "Invalid descendant segment `{}`", 100 | child.as_str() 101 | ))) 102 | } else { 103 | Ok(Segment::Descendant(Box::new(child_segment(next_down( 104 | child, 105 | )?)?))) 106 | } 107 | } 108 | _ => Err(child.into()), 109 | } 110 | } 111 | 112 | pub fn selector(rule: Pair) -> Parsed { 113 | let child = next_down(rule)?; 114 | match child.as_rule() { 115 | Rule::name_selector => Ok(Selector::Name( 116 | validate_js_str(child.as_str().trim())?.to_string(), 117 | )), 118 | Rule::wildcard_selector => Ok(Selector::Wildcard), 119 | Rule::index_selector => Ok(Selector::Index( 120 | child 121 | .as_str() 122 | .trim() 123 | .parse::() 124 | .map_err(|e| (e, "wrong integer"))?, 125 | )), 126 | Rule::slice_selector => { 127 | let (start, end, step) = slice_selector(child)?; 128 | Ok(Selector::Slice(start, end, step)) 129 | } 130 | Rule::filter_selector => Ok(Selector::Filter(logical_expr(next_down(child)?)?)), 131 | _ => Err(child.into()), 132 | } 133 | } 134 | 135 | pub fn function_expr(rule: Pair) -> Parsed { 136 | let fn_str = rule.as_str(); 137 | let mut elems = rule.into_inner(); 138 | let name = elems 139 | .next() 140 | .map(|e| e.as_str()) 141 | .ok_or(JsonPathError::empty("function expression"))?; 142 | 143 | // Check if the function name is valid namely nothing between the name and the opening parenthesis 144 | if fn_str 145 | .chars() 146 | .nth(name.len()) 147 | .map(|c| c != '(') 148 | .unwrap_or_default() 149 | { 150 | Err(JsonPathError::InvalidJsonPath(format!( 151 | "Invalid function expression `{}`", 152 | fn_str 153 | ))) 154 | } else { 155 | let mut args = vec![]; 156 | for arg in elems { 157 | let next = next_down(arg)?; 158 | match next.as_rule() { 159 | Rule::literal => args.push(FnArg::Literal(literal(next)?)), 160 | Rule::test => args.push(FnArg::Test(Box::new(test(next)?))), 161 | Rule::logical_expr => args.push(FnArg::Filter(logical_expr(next)?)), 162 | 163 | _ => return Err(next.into()), 164 | } 165 | } 166 | 167 | TestFunction::try_new(name, args) 168 | } 169 | } 170 | 171 | pub fn test(rule: Pair) -> Parsed { 172 | let child = next_down(rule)?; 173 | match child.as_rule() { 174 | Rule::jp_query => Ok(Test::AbsQuery(jp_query(child)?)), 175 | Rule::rel_query => Ok(Test::RelQuery(rel_query(child)?)), 176 | Rule::function_expr => Ok(Test::Function(Box::new(function_expr(child)?))), 177 | _ => Err(child.into()), 178 | } 179 | } 180 | 181 | pub fn logical_expr(rule: Pair) -> Parsed { 182 | let mut ors = vec![]; 183 | for r in rule.into_inner() { 184 | ors.push(logical_expr_and(r)?); 185 | } 186 | if ors.len() == 1 { 187 | Ok(ors 188 | .into_iter() 189 | .next() 190 | .ok_or(JsonPathError::empty("logical expression"))?) 191 | } else { 192 | Ok(Filter::Or(ors)) 193 | } 194 | } 195 | 196 | pub fn logical_expr_and(rule: Pair) -> Parsed { 197 | let mut ands = vec![]; 198 | for r in rule.into_inner() { 199 | ands.push(Filter::Atom(filter_atom(r)?)); 200 | } 201 | if ands.len() == 1 { 202 | Ok(ands 203 | .into_iter() 204 | .next() 205 | .ok_or(JsonPathError::empty("logical expression"))?) 206 | } else { 207 | Ok(Filter::And(ands)) 208 | } 209 | } 210 | 211 | pub fn singular_query_segments(rule: Pair) -> Parsed> { 212 | let mut segments = vec![]; 213 | for r in rule.into_inner() { 214 | match r.as_rule() { 215 | Rule::name_segment => { 216 | segments.push(SingularQuerySegment::Name( 217 | next_down(r)?.as_str().trim().to_string(), 218 | )); 219 | } 220 | Rule::index_segment => { 221 | segments.push(SingularQuerySegment::Index( 222 | next_down(r)? 223 | .as_str() 224 | .trim() 225 | .parse::() 226 | .map_err(|e| (e, "int"))?, 227 | )); 228 | } 229 | _ => return Err(r.into()), 230 | } 231 | } 232 | Ok(segments) 233 | } 234 | 235 | pub fn slice_selector(rule: Pair) -> Parsed<(Option, Option, Option)> { 236 | let mut start = None; 237 | let mut end = None; 238 | let mut step = None; 239 | let get_int = |r: Pair| r.as_str().trim().parse::().map_err(|e| (e, "int")); 240 | 241 | for r in rule.into_inner() { 242 | match r.as_rule() { 243 | Rule::start => start = Some(get_int(r)?), 244 | Rule::end => end = Some(get_int(r)?), 245 | Rule::step => { 246 | step = { 247 | if let Some(int) = r.into_inner().next() { 248 | Some(get_int(int)?) 249 | } else { 250 | None 251 | } 252 | } 253 | } 254 | 255 | _ => return Err(r.into()), 256 | } 257 | } 258 | Ok((start, end, step)) 259 | } 260 | 261 | pub fn singular_query(rule: Pair) -> Parsed { 262 | let query = next_down(rule)?; 263 | let segments = singular_query_segments(next_down(query.clone())?)?; 264 | match query.as_rule() { 265 | Rule::rel_singular_query => Ok(SingularQuery::Current(segments)), 266 | Rule::abs_singular_query => Ok(SingularQuery::Root(segments)), 267 | _ => Err(query.into()), 268 | } 269 | } 270 | 271 | pub fn comp_expr(rule: Pair) -> Parsed { 272 | let mut children = rule.into_inner(); 273 | 274 | let lhs = comparable(children.next().ok_or(JsonPathError::empty("comparison"))?)?; 275 | let op = children 276 | .next() 277 | .ok_or(JsonPathError::empty("comparison"))? 278 | .as_str(); 279 | let rhs = comparable(children.next().ok_or(JsonPathError::empty("comparison"))?)?; 280 | 281 | Comparison::try_new(op, lhs, rhs) 282 | } 283 | 284 | /// Validates a JSONPath string literal according to RFC 9535 285 | /// Control characters (U+0000 through U+001F and U+007F) are not allowed unescaped 286 | /// in string literals, whether single-quoted or double-quoted 287 | fn validate_js_str(s: &str) -> Parsed<&str> { 288 | for (i, c) in s.chars().enumerate() { 289 | if c <= '\u{001F}' { 290 | return Err(JsonPathError::InvalidJsonPath(format!( 291 | "Invalid control character U+{:04X} at position {} in string literal", 292 | c as u32, i 293 | ))); 294 | } 295 | } 296 | 297 | Ok(s) 298 | } 299 | 300 | pub fn literal(rule: Pair) -> Parsed { 301 | fn parse_number(num: &str) -> Parsed { 302 | let num = num.trim(); 303 | 304 | if num.contains('.') || num.contains('e') || num.contains('E') { 305 | Ok(Literal::Float(num.parse::().map_err(|e| (e, num))?)) 306 | } else { 307 | Ok(Literal::Int( 308 | num.trim().parse::().map_err(|e| (e, num))?, 309 | )) 310 | } 311 | } 312 | 313 | fn parse_string(string: &str) -> Parsed { 314 | let string = validate_js_str(string.trim())?; 315 | if string.starts_with('\'') && string.ends_with('\'') { 316 | Ok(Literal::String(string[1..string.len() - 1].to_string())) 317 | } else if string.starts_with('"') && string.ends_with('"') { 318 | Ok(Literal::String(string[1..string.len() - 1].to_string())) 319 | } else { 320 | Err(JsonPathError::InvalidJsonPath(format!( 321 | "Invalid string literal `{}`", 322 | string 323 | ))) 324 | } 325 | } 326 | 327 | let first = next_down(rule)?; 328 | 329 | match first.as_rule() { 330 | Rule::string => parse_string(first.as_str()), 331 | Rule::number => parse_number(first.as_str()), 332 | Rule::bool => Ok(Literal::Bool(first.as_str().parse::()?)), 333 | Rule::null => Ok(Literal::Null), 334 | 335 | _ => Err(first.into()), 336 | } 337 | } 338 | 339 | pub fn filter_atom(pair: Pair) -> Parsed { 340 | let rule = next_down(pair)?; 341 | 342 | match rule.as_rule() { 343 | Rule::paren_expr => { 344 | let mut not = false; 345 | let mut logic_expr = None; 346 | for r in rule.into_inner() { 347 | match r.as_rule() { 348 | Rule::not_op => not = true, 349 | Rule::logical_expr => logic_expr = Some(logical_expr(r)?), 350 | _ => (), 351 | } 352 | } 353 | 354 | logic_expr 355 | .map(|expr| FilterAtom::filter(expr, not)) 356 | .ok_or("Logical expression is absent".into()) 357 | } 358 | Rule::comp_expr => Ok(FilterAtom::cmp(Box::new(comp_expr(rule)?))), 359 | Rule::test_expr => { 360 | let mut not = false; 361 | let mut test_expr = None; 362 | for r in rule.into_inner() { 363 | match r.as_rule() { 364 | Rule::not_op => not = true, 365 | Rule::test => test_expr = Some(test(r)?), 366 | _ => (), 367 | } 368 | } 369 | 370 | test_expr 371 | .map(|expr| FilterAtom::test(expr, not)) 372 | .ok_or("Logical expression is absent".into()) 373 | } 374 | _ => Err(rule.into()), 375 | } 376 | } 377 | 378 | pub fn comparable(rule: Pair) -> Parsed { 379 | let rule = next_down(rule)?; 380 | match rule.as_rule() { 381 | Rule::literal => Ok(Comparable::Literal(literal(rule)?)), 382 | Rule::singular_query => Ok(Comparable::SingularQuery(singular_query(rule)?)), 383 | Rule::function_expr => { 384 | let tf = function_expr(rule)?; 385 | if tf.is_comparable() { 386 | Ok(Comparable::Function(tf)) 387 | } else { 388 | Err(JsonPathError::InvalidJsonPath(format!( 389 | "Function {} is not comparable", 390 | tf.to_string() 391 | ))) 392 | } 393 | } 394 | _ => Err(rule.into()), 395 | } 396 | } 397 | 398 | fn next_down(rule: Pair) -> Parsed> { 399 | let rule_as_str = rule.as_str().to_string(); 400 | rule.into_inner() 401 | .next() 402 | .ok_or(JsonPathError::InvalidJsonPath(rule_as_str)) 403 | } 404 | -------------------------------------------------------------------------------- /src/parser/errors.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::Rule; 2 | use crate::query::queryable::Queryable; 3 | use pest::iterators::Pair; 4 | use std::num::{ParseFloatError, ParseIntError}; 5 | use std::str::ParseBoolError; 6 | use thiserror::Error; 7 | 8 | /// This error type is used to represent errors that can occur during the parsing of JSONPath expressions. 9 | #[derive(Error, Debug, PartialEq, Clone)] 10 | pub enum JsonPathError { 11 | #[error("Failed to parse rule: {0}")] 12 | PestError(#[from] Box>), 13 | #[error("Unexpected rule `{0:?}` when trying to parse `{1}`")] 14 | UnexpectedRuleLogicError(Rule, String), 15 | #[error("Unexpected `none` when trying to parse logic atom: {0} within {1}")] 16 | UnexpectedNoneLogicError(String, String), 17 | #[error("Pest returned successful parsing but did not produce any output, that should be unreachable due to .pest definition file: SOI ~ chain ~ EOI")] 18 | UnexpectedPestOutput, 19 | #[error("expected a `Rule::path` but found nothing")] 20 | NoRulePath, 21 | #[error("expected a `JsonPath::Descent` but found nothing")] 22 | NoJsonPathDescent, 23 | #[error("expected a `JsonPath::Field` but found nothing")] 24 | NoJsonPathField, 25 | #[error("expected a `f64` or `i64`, but got {0}")] 26 | InvalidNumber(String), 27 | #[error("Invalid toplevel rule for JsonPath: {0:?}")] 28 | InvalidTopLevelRule(Rule), 29 | #[error("Failed to get inner pairs for {0}")] 30 | EmptyInner(String), 31 | #[error("Invalid json path: {0}")] 32 | InvalidJsonPath(String), 33 | } 34 | 35 | impl JsonPathError { 36 | pub fn empty(v: &str) -> Self { 37 | JsonPathError::EmptyInner(v.to_string()) 38 | } 39 | } 40 | 41 | impl From for JsonPathError { 42 | fn from(val: T) -> Self { 43 | JsonPathError::InvalidJsonPath(format!("Result '{:?}' is not a reference", val)) 44 | } 45 | } 46 | 47 | impl From<&str> for JsonPathError { 48 | fn from(val: &str) -> Self { 49 | JsonPathError::EmptyInner(val.to_string()) 50 | } 51 | } 52 | 53 | impl From<(ParseIntError, &str)> for JsonPathError { 54 | fn from((err, val): (ParseIntError, &str)) -> Self { 55 | JsonPathError::InvalidNumber(format!("{:?} for `{}`", err, val)) 56 | } 57 | } 58 | 59 | impl From<(JsonPathError, &str)> for JsonPathError { 60 | fn from((err, val): (JsonPathError, &str)) -> Self { 61 | JsonPathError::InvalidJsonPath(format!("{:?} for `{}`", err, val)) 62 | } 63 | } 64 | 65 | impl From<(ParseFloatError, &str)> for JsonPathError { 66 | fn from((err, val): (ParseFloatError, &str)) -> Self { 67 | JsonPathError::InvalidNumber(format!("{:?} for `{}`", err, val)) 68 | } 69 | } 70 | impl From for JsonPathError { 71 | fn from(err: ParseBoolError) -> Self { 72 | JsonPathError::InvalidJsonPath(format!("{:?} ", err)) 73 | } 74 | } 75 | impl From> for JsonPathError { 76 | fn from(rule: Pair) -> Self { 77 | JsonPathError::UnexpectedRuleLogicError(rule.as_rule(), rule.as_str().to_string()) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/parser/grammar/json_path_9535.pest: -------------------------------------------------------------------------------- 1 | main = ${ SOI ~ jp_query ~ EOI } 2 | jp_query = {root ~ segments} 3 | segments = !{(S ~ segment)*} 4 | segment = { child_segment | descendant_segment } 5 | child_segment = { bracketed_selection | ("." ~ (wildcard_selector | member_name_shorthand)) } 6 | bracketed_selection = { "[" ~ S ~ selector ~ (S ~ "," ~ S ~ selector)* ~ S ~ "]" } 7 | descendant_segment = { ".." ~ (bracketed_selection | wildcard_selector | member_name_shorthand)} 8 | selector = {name_selector | wildcard_selector | slice_selector| index_selector | filter_selector} 9 | 10 | root = _{"$"} 11 | name_selector = {string} 12 | wildcard_selector = {"*"} 13 | index_selector = {int} 14 | int = { "0" | ("-"? ~ safe_int) } 15 | step = {":" ~ S ~ int?} 16 | start = {int} 17 | end = {int} 18 | slice_selector = { start? ~ S ~ ":" ~ S ~ end? ~ S ~ step? } 19 | filter_selector = {"?"~ S ~ logical_expr} 20 | logical_expr = {logical_expr_and ~ S ~ ("||" ~ S ~ logical_expr_and)*} 21 | logical_expr_and = {atom_expr ~ S ~ ("&&" ~ S ~ atom_expr)*} 22 | atom_expr = {paren_expr | comp_expr| test_expr} 23 | paren_expr = {not_op? ~ S ~ "(" ~ S ~ logical_expr ~ S ~ ")"} 24 | comp_expr = { comparable ~ S ~ comp_op ~ S ~ comparable } 25 | test_expr = {not_op? ~ S ~ test} 26 | test = {rel_query | jp_query | function_expr} 27 | rel_query = {curr ~ S ~ segments} 28 | function_expr = { ( function_name_one_arg ~ one_arg ) | ( function_name_two_arg ~ two_arg ) } 29 | function_name_one_arg = { "length" | "value" | "count" } 30 | function_name_two_arg = { "search" | "match" | "in" | "nin" | "none_of" | "any_of" | "subset_of" } 31 | function_argument = { literal | test | logical_expr } 32 | one_arg = _{ "(" ~ S ~ function_argument ~ S ~ ")" } 33 | two_arg = _{ "(" ~ S ~ function_argument ~ S ~ "," ~ S ~ function_argument ~ S ~ ")" } 34 | comparable = { literal | singular_query | function_expr } 35 | literal = { number | string | bool | null } 36 | bool = {"true" | "false"} 37 | null = {"null"} 38 | singular_query = { rel_singular_query | abs_singular_query } 39 | rel_singular_query = { curr ~ singular_query_segments } 40 | abs_singular_query = { root ~ singular_query_segments } 41 | singular_query_segments = { (S ~ (name_segment | index_segment))* } 42 | name_segment = { ("[" ~ name_selector ~ "]") | ("." ~ member_name_shorthand) } 43 | index_segment = { "[" ~ index_selector ~ "]" } 44 | comp_op = { "==" | "!=" | "<=" | ">=" | "<" | ">" } 45 | 46 | LCALPHA = { 'a'..'z' } 47 | 48 | 49 | string = { "\"" ~ double_quoted* ~ "\"" | "\'" ~ single_quoted* ~ "\'" } 50 | double_quoted = _{ unescaped | "\'" | ESC ~ "\"" | ESC ~ escapable } 51 | single_quoted = _{ unescaped | "\"" | ESC ~ "\'" | ESC ~ escapable } 52 | escapable = _{ 53 | "b" | "f" | "n" | "r" | "t" | "/" | "\\" | ("u" ~ hexchar) 54 | } 55 | 56 | member_name_shorthand = { name_first ~ name_char* } 57 | name_first = { ALPHA | "_" | '\u{0080}'..'\u{D7FF}' | '\u{E000}'..'\u{10FFFF}' } 58 | name_char = { name_first | DIGIT } 59 | not_op = {"!"} 60 | curr = _{"@"} 61 | ESC = _{ "\\" } 62 | unescaped = _{ 63 | '\u{0020}'..'\u{0021}' | 64 | '\u{0023}'..'\u{0026}' | 65 | '\u{0028}'..'\u{005B}' | 66 | '\u{005D}'..'\u{D7FF}' | 67 | '\u{E000}'..'\u{10FFFF}' 68 | } 69 | 70 | S = _{ WHITESPACE* } 71 | hexchar = _{ non_surrogate | (high_surrogate ~ "\\" ~ "u" ~ low_surrogate) } 72 | number = { (int | "-0") ~ frac? ~ exp? } 73 | frac = { "." ~ DIGIT+ } 74 | exp = { ("e" | "E") ~ ("-" | "+")? ~ DIGIT+ } 75 | non_surrogate = _{ (DIGIT | "A" | "B" | "C" | "E" | "F") ~ HEXDIG{3} | ("D" ~ ('0'..'7') ~ HEXDIG{2}) } 76 | 77 | high_surrogate = _{ "D" ~ ("8" | "9" | "A" | "B") ~ HEXDIG{2} } 78 | 79 | low_surrogate = _{ "D" ~ ("C" | "D" | "E" | "F") ~ HEXDIG{2} } 80 | 81 | HEXDIG = _{ DIGIT | "A" | "B" | "C" | "D" | "E" | "F" } 82 | DIGIT = _{ ASCII_DIGIT } 83 | DIGIT1 = _{ ASCII_NONZERO_DIGIT} 84 | ALPHA = { ASCII_ALPHA } 85 | WHITESPACE = _{ " " | "\t" | "\r\n" | "\n" | "\r"} 86 | 87 | // Matches any number less than 9007199254740991 early escape for any number <= 999999999999999 88 | safe_int = _{ 89 | ( 90 | DIGIT1 ~ DIGIT{0,14} ~ !ASCII_DIGIT // 1 to 15 digits (well below the max) 91 | | '1'..'8' ~ ASCII_DIGIT{15} 92 | | "900" ~ '0'..'6' ~ ASCII_DIGIT{12} 93 | | "90070" ~ ASCII_DIGIT{11} 94 | | "90071" ~ '0'..'8' ~ ASCII_DIGIT{10} 95 | | "900719" ~ '0'..'8' ~ ASCII_DIGIT{9} 96 | | "9007199" ~ '0'..'1' ~ ASCII_DIGIT{8} 97 | | "90071992" ~ '0'..'4' ~ ASCII_DIGIT{7} 98 | | "900719925" ~ '0'..'3' ~ ASCII_DIGIT{6} 99 | | "9007199254" ~ '0'..'6' ~ ASCII_DIGIT{5} 100 | | "90071992547" ~ '0'..'3' ~ ASCII_DIGIT{4} 101 | | "9007199254740" ~ '0'..'8' ~ ASCII_DIGIT{2} 102 | | "90071992547409" ~ '0'..'8' ~ ASCII_DIGIT 103 | | "900719925474099" ~ '0'..'1' 104 | ) ~ !ASCII_DIGIT 105 | } 106 | 107 | -------------------------------------------------------------------------------- /src/parser/macros.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::model::{ 2 | Comparable, Filter, FilterAtom, FnArg, Literal, Segment, Selector, SingularQuery, Test, 3 | }; 4 | 5 | #[macro_export] 6 | macro_rules! lit { 7 | () => { 8 | Literal::Null 9 | }; 10 | (b$b:expr ) => { 11 | Literal::Bool($b) 12 | }; 13 | (s$s:expr) => { 14 | Literal::String($s.to_string()) 15 | }; 16 | (i$n:expr) => { 17 | Literal::Int($n) 18 | }; 19 | (f$n:expr) => { 20 | Literal::Float($n) 21 | }; 22 | } 23 | 24 | #[macro_export] 25 | macro_rules! q_segments { 26 | ($segment:tt) => { 27 | vec![q_segment!($segment)] 28 | }; 29 | // Recursive case: multiple segments 30 | ($segment:tt $($rest:tt)*) => {{ 31 | let mut segments = q_segments!($($rest)*); 32 | segments.insert(0, q_segment!($segment)); 33 | segments 34 | }}; 35 | } 36 | 37 | #[macro_export] 38 | macro_rules! q_segment { 39 | ($name:ident) => { 40 | SingularQuerySegment::Name(stringify!($name).to_string()) 41 | }; 42 | ([$name:ident]) => { 43 | SingularQuerySegment::Name(format!("\"{}\"", stringify!($name))) 44 | }; 45 | ([$index:expr]) => { 46 | SingularQuerySegment::Index($index) 47 | }; 48 | } 49 | #[macro_export] 50 | macro_rules! singular_query { 51 | (@ $($segment:tt)*) => { 52 | SingularQuery::Current(q_segments!($($segment)*)) 53 | }; 54 | ($($segment:tt)*) => { 55 | SingularQuery::Root(q_segments!($($segment)*)) 56 | }; 57 | } 58 | 59 | #[macro_export] 60 | macro_rules! slice { 61 | () => { 62 | (None, None, None) 63 | }; 64 | ($start:expr) => { 65 | (Some($start), None, None) 66 | }; 67 | ($start:expr, $end:expr) => { 68 | (Some($start), Some($end), None) 69 | }; 70 | ($start:expr,, $step:expr) => { 71 | (Some($start), None, Some($step)) 72 | }; 73 | (,, $step:expr) => { 74 | (None, None, Some($step)) 75 | }; 76 | (, $end:expr) => { 77 | (None, Some($end), None) 78 | }; 79 | (, $end:expr,$step:expr ) => { 80 | (None, Some($end), Some($step)) 81 | }; 82 | ($start:expr, $end:expr, $step:expr) => { 83 | (Some($start), Some($end), Some($step)) 84 | }; 85 | } 86 | 87 | #[macro_export] 88 | macro_rules! test_fn { 89 | ($name:ident $arg:expr) => { 90 | TestFunction::try_new(stringify!($name), vec![$arg]).unwrap() 91 | }; 92 | 93 | ($name:ident $arg1:expr, $arg2:expr ) => { 94 | TestFunction::try_new(stringify!($name), vec![$arg1, $arg2]).unwrap() 95 | }; 96 | } 97 | 98 | #[macro_export] 99 | macro_rules! arg { 100 | ($arg:expr) => { 101 | FnArg::Literal($arg) 102 | }; 103 | (t $arg:expr) => { 104 | FnArg::Test(Box::new($arg)) 105 | }; 106 | (f $arg:expr) => { 107 | FnArg::Filter($arg) 108 | }; 109 | } 110 | 111 | #[macro_export] 112 | macro_rules! test { 113 | (@ $($segments:expr)*) => { Test::RelQuery(vec![$($segments),*]) }; 114 | (S $jq:expr) => { Test::AbsQuery($jq) }; 115 | ($tf:expr) => { Test::Function(Box::new($tf)) }; 116 | 117 | } 118 | 119 | #[macro_export] 120 | macro_rules! or { 121 | ($($items:expr),*) => { 122 | crate::parser::model::Filter::Or(vec![ $($items),* ]) 123 | }; 124 | } 125 | 126 | #[macro_export] 127 | macro_rules! and { 128 | ($($items:expr),*) => { 129 | crate::parser::model::Filter::And(vec![ $($items),* ]) 130 | }; 131 | } 132 | 133 | #[macro_export] 134 | macro_rules! filter_ { 135 | ($item:expr) => { 136 | crate::parser::model::Filter::Atom($item) 137 | }; 138 | 139 | (or $($items:expr),*) => { 140 | crate::parser::model::Filter::Or(vec![ $($items),* ]) 141 | }; 142 | 143 | (and $($items:expr),*) => { 144 | crate::parser::model::Filter::And(vec![ $($items),* ]) 145 | }; 146 | } 147 | 148 | #[macro_export] 149 | macro_rules! atom { 150 | (! $filter:expr) => { 151 | FilterAtom::filter($filter, true) 152 | }; 153 | ($filter:expr) => { 154 | FilterAtom::filter($filter, false) 155 | }; 156 | (t! $filter:expr) => { 157 | FilterAtom::test($filter, true) 158 | }; 159 | (t $filter:expr) => { 160 | FilterAtom::filter($filter, false) 161 | }; 162 | ($lhs:expr, $s:expr, $rhs:expr) => { 163 | FilterAtom::Comparison(Box::new(cmp!($lhs, $s, $rhs))) 164 | }; 165 | } 166 | 167 | #[macro_export] 168 | macro_rules! cmp { 169 | ($lhs:expr, $op:expr , $rhs:expr) => { 170 | Comparison::try_new($op, $lhs, $rhs).unwrap() 171 | }; 172 | } 173 | 174 | #[macro_export] 175 | macro_rules! comparable { 176 | ($lit:expr) => { 177 | Comparable::Literal($lit) 178 | }; 179 | (f $func:expr) => { 180 | Comparable::Function($func) 181 | }; 182 | (> $sq:expr) => { 183 | Comparable::SingularQuery($sq) 184 | }; 185 | } 186 | 187 | #[macro_export] 188 | macro_rules! selector { 189 | (*) => { 190 | Selector::Wildcard 191 | }; 192 | (?$filter:expr) => { 193 | Selector::Filter($filter) 194 | }; 195 | (slice $slice:expr) => { 196 | slice_from($slice) 197 | }; 198 | ($name:ident) => { 199 | Selector::Name(stringify!($name).to_string()) 200 | }; 201 | ([$name:ident]) => { 202 | Selector::Name(format!("\"{}\"", stringify!($name))) 203 | }; 204 | ([$index:expr]) => { 205 | Selector::Index($index) 206 | }; 207 | } 208 | 209 | #[macro_export] 210 | macro_rules! segment { 211 | (..$segment:expr) => { 212 | Segment::Descendant(Box::new($segment)) 213 | }; 214 | ($selector:expr) => { 215 | Segment::Selector($selector) 216 | }; 217 | ($($selectors:expr),*) => { 218 | Segment::Selectors(vec![$($selectors),*]) 219 | }; 220 | } 221 | 222 | #[macro_export] 223 | macro_rules! jq { 224 | ($($segment:expr),*) => { 225 | JpQuery::new(vec![$($segment),*]) 226 | }; 227 | } 228 | -------------------------------------------------------------------------------- /src/parser/model.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::errors::JsonPathError; 2 | use crate::parser::Parsed; 3 | use std::fmt::{Display, Formatter}; 4 | 5 | /// Represents a JSONPath query with a list of segments. 6 | #[derive(Debug, Clone, PartialEq)] 7 | pub struct JpQuery { 8 | pub segments: Vec, 9 | } 10 | 11 | impl JpQuery { 12 | pub fn new(segments: Vec) -> Self { 13 | JpQuery { segments } 14 | } 15 | } 16 | 17 | impl Display for JpQuery { 18 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 19 | write!( 20 | f, 21 | "${}", 22 | self.segments 23 | .iter() 24 | .map(|s| s.to_string()) 25 | .collect::() 26 | ) 27 | } 28 | } 29 | /// Enum representing different types of segments in a JSONPath query. 30 | #[derive(Debug, Clone, PartialEq)] 31 | pub enum Segment { 32 | /// Represents a descendant segment. 33 | Descendant(Box), 34 | /// Represents a selector segment. 35 | Selector(Selector), 36 | /// Represents multiple selectors. 37 | Selectors(Vec), 38 | } 39 | 40 | impl Segment { 41 | pub fn name(name: &str) -> Self { 42 | Segment::Selector(Selector::Name(name.to_string())) 43 | } 44 | } 45 | 46 | impl Display for Segment { 47 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 48 | match self { 49 | Segment::Descendant(s) => write!(f, "..{}", s), 50 | Segment::Selector(selector) => write!(f, "{}", selector), 51 | Segment::Selectors(selectors) => write!( 52 | f, 53 | "{}", 54 | selectors.iter().map(|s| s.to_string()).collect::() 55 | ), 56 | } 57 | } 58 | } 59 | /// Enum representing different types of selectors in a JSONPath query. 60 | #[derive(Debug, Clone, PartialEq)] 61 | pub enum Selector { 62 | /// Represents a name selector. 63 | Name(String), 64 | /// Represents a wildcard selector. 65 | Wildcard, 66 | /// Represents an index selector. 67 | Index(i64), 68 | /// Represents a slice selector. 69 | Slice(Option, Option, Option), 70 | /// Represents a filter selector. 71 | Filter(Filter), 72 | } 73 | 74 | pub fn slice_from((start, end, step): (Option, Option, Option)) -> Selector { 75 | Selector::Slice(start, end, step) 76 | } 77 | 78 | impl Display for Selector { 79 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 80 | match self { 81 | Selector::Name(name) => write!(f, "{}", name), 82 | Selector::Wildcard => write!(f, "*"), 83 | Selector::Index(index) => write!(f, "{}", index), 84 | Selector::Slice(start, end, step) => write!( 85 | f, 86 | "{}:{}:{}", 87 | start.unwrap_or(0), 88 | end.unwrap_or(0), 89 | step.unwrap_or(1) 90 | ), 91 | Selector::Filter(filter) => write!(f, "[?{}]", filter), 92 | } 93 | } 94 | } 95 | /// Enum representing different types of filters in a JSONPath query. 96 | #[derive(Debug, Clone, PartialEq)] 97 | pub enum Filter { 98 | /// Represents a logical OR filter. 99 | Or(Vec), 100 | /// Represents a logical AND filter. 101 | And(Vec), 102 | /// Represents an atomic filter. 103 | Atom(FilterAtom), 104 | } 105 | 106 | impl Display for Filter { 107 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 108 | let items_to_str = |items: &Vec, sep: &str| { 109 | items 110 | .iter() 111 | .map(|f| f.to_string()) 112 | .collect::>() 113 | .join(sep) 114 | }; 115 | 116 | match self { 117 | Filter::Or(filters) => write!(f, "{}", items_to_str(filters, " || ")), 118 | Filter::And(filters) => write!(f, "{}", items_to_str(filters, " && ")), 119 | Filter::Atom(atom) => write!(f, "{}", atom), 120 | } 121 | } 122 | } 123 | 124 | /// Enum representing different types of atomic filters in a JSONPath query. 125 | #[derive(Debug, Clone, PartialEq)] 126 | pub enum FilterAtom { 127 | /// Represents a nested filter with an optional NOT flag. 128 | Filter { expr: Box, not: bool }, 129 | /// Represents a test filter with an optional NOT flag. 130 | Test { expr: Box, not: bool }, 131 | /// Represents a comparison filter. 132 | Comparison(Box), 133 | } 134 | 135 | impl FilterAtom { 136 | pub fn filter(expr: Filter, not: bool) -> Self { 137 | FilterAtom::Filter { 138 | expr: Box::new(expr), 139 | not, 140 | } 141 | } 142 | 143 | pub fn test(expr: Test, not: bool) -> Self { 144 | FilterAtom::Test { 145 | expr: Box::new(expr), 146 | not, 147 | } 148 | } 149 | 150 | pub fn cmp(cmp: Box) -> Self { 151 | FilterAtom::Comparison(cmp) 152 | } 153 | } 154 | 155 | impl Display for FilterAtom { 156 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 157 | match self { 158 | FilterAtom::Filter { expr, not } => { 159 | if *not { 160 | write!(f, "!{}", expr) 161 | } else { 162 | write!(f, "{}", expr) 163 | } 164 | } 165 | FilterAtom::Test { expr, not } => { 166 | if *not { 167 | write!(f, "!{}", expr) 168 | } else { 169 | write!(f, "{}", expr) 170 | } 171 | } 172 | FilterAtom::Comparison(cmp) => write!(f, "{}", cmp), 173 | } 174 | } 175 | } 176 | /// Enum representing different types of comparisons in a JSONPath query. 177 | #[derive(Debug, Clone, PartialEq)] 178 | pub enum Comparison { 179 | /// Represents an equality comparison. 180 | Eq(Comparable, Comparable), 181 | /// Represents a non-equality comparison. 182 | Ne(Comparable, Comparable), 183 | /// Represents a greater-than comparison. 184 | Gt(Comparable, Comparable), 185 | /// Represents a greater-than-or-equal-to comparison. 186 | Gte(Comparable, Comparable), 187 | /// Represents a less-than comparison. 188 | Lt(Comparable, Comparable), 189 | /// Represents a less-than-or-equal-to comparison. 190 | Lte(Comparable, Comparable), 191 | } 192 | 193 | impl Comparison { 194 | pub fn try_new(op: &str, left: Comparable, right: Comparable) -> Parsed { 195 | match op { 196 | "==" => Ok(Comparison::Eq(left, right)), 197 | "!=" => Ok(Comparison::Ne(left, right)), 198 | ">" => Ok(Comparison::Gt(left, right)), 199 | ">=" => Ok(Comparison::Gte(left, right)), 200 | "<" => Ok(Comparison::Lt(left, right)), 201 | "<=" => Ok(Comparison::Lte(left, right)), 202 | _ => Err(JsonPathError::InvalidJsonPath(format!( 203 | "Invalid comparison operator: {}", 204 | op 205 | ))), 206 | } 207 | } 208 | 209 | pub fn vals(&self) -> (&Comparable, &Comparable) { 210 | match self { 211 | Comparison::Eq(left, right) => (left, right), 212 | Comparison::Ne(left, right) => (left, right), 213 | Comparison::Gt(left, right) => (left, right), 214 | Comparison::Gte(left, right) => (left, right), 215 | Comparison::Lt(left, right) => (left, right), 216 | Comparison::Lte(left, right) => (left, right), 217 | } 218 | } 219 | } 220 | 221 | impl Display for Comparison { 222 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 223 | match self { 224 | Comparison::Eq(left, right) => write!(f, "{} == {}", left, right), 225 | Comparison::Ne(left, right) => write!(f, "{} != {}", left, right), 226 | Comparison::Gt(left, right) => write!(f, "{} > {}", left, right), 227 | Comparison::Gte(left, right) => write!(f, "{} >= {}", left, right), 228 | Comparison::Lt(left, right) => write!(f, "{} < {}", left, right), 229 | Comparison::Lte(left, right) => write!(f, "{} <= {}", left, right), 230 | } 231 | } 232 | } 233 | 234 | /// Enum representing different types of comparable values in a JSONPath query. 235 | #[derive(Debug, Clone, PartialEq)] 236 | pub enum Comparable { 237 | /// Represents a literal value. 238 | Literal(Literal), 239 | /// Represents a function. 240 | Function(TestFunction), 241 | /// Represents a singular query. 242 | SingularQuery(SingularQuery), 243 | } 244 | 245 | impl Display for Comparable { 246 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 247 | match self { 248 | Comparable::Literal(literal) => write!(f, "{}", literal), 249 | Comparable::Function(func) => write!(f, "{}", func), 250 | Comparable::SingularQuery(query) => write!(f, "{}", query), 251 | } 252 | } 253 | } 254 | 255 | /// Enum representing different types of singular queries in a JSONPath query. 256 | #[derive(Debug, Clone, PartialEq)] 257 | pub enum SingularQuery { 258 | /// Represents a current node query. 259 | Current(Vec), 260 | /// Represents a root node query. 261 | Root(Vec), 262 | } 263 | 264 | impl Display for SingularQuery { 265 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 266 | match self { 267 | SingularQuery::Current(segments) => write!( 268 | f, 269 | "@.{}", 270 | segments.iter().map(|s| s.to_string()).collect::() 271 | ), 272 | SingularQuery::Root(segments) => write!( 273 | f, 274 | "$.{}", 275 | segments.iter().map(|s| s.to_string()).collect::() 276 | ), 277 | } 278 | } 279 | } 280 | 281 | /// Enum representing different types of singular query segments in a JSONPath query. 282 | #[derive(Debug, Clone, PartialEq)] 283 | pub enum SingularQuerySegment { 284 | /// Represents an index segment. 285 | Index(i64), 286 | /// Represents a name segment. 287 | Name(String), 288 | } 289 | 290 | impl Display for SingularQuerySegment { 291 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 292 | match self { 293 | SingularQuerySegment::Index(index) => write!(f, "{}", index), 294 | SingularQuerySegment::Name(name) => write!(f, "{}", name), 295 | } 296 | } 297 | } 298 | 299 | /// Enum representing different types of tests in a JSONPath query. 300 | #[derive(Debug, Clone, PartialEq)] 301 | pub enum Test { 302 | /// Represents a relative query. 303 | RelQuery(Vec), 304 | /// Represents an absolute query. 305 | AbsQuery(JpQuery), 306 | /// Represents a function test. 307 | Function(Box), 308 | } 309 | 310 | impl Test { 311 | pub fn is_res_bool(&self) -> bool { 312 | match self { 313 | Test::RelQuery(_) => false, 314 | Test::AbsQuery(_) => false, 315 | Test::Function(func) => match **func { 316 | TestFunction::Length(_) => false, 317 | TestFunction::Value(_) => false, 318 | TestFunction::Count(_) => false, 319 | TestFunction::Custom(_, _) => true, 320 | TestFunction::Search(_, _) => true, 321 | TestFunction::Match(_, _) => true, 322 | }, 323 | } 324 | } 325 | } 326 | 327 | impl Display for Test { 328 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 329 | match self { 330 | Test::RelQuery(segments) => write!( 331 | f, 332 | "{}", 333 | segments.iter().map(|s| s.to_string()).collect::() 334 | ), 335 | Test::AbsQuery(query) => write!(f, "{}", query), 336 | Test::Function(func) => write!(f, "{}", func), 337 | } 338 | } 339 | } 340 | 341 | /// Enum representing different types of test functions in a JSONPath query. 342 | #[derive(Debug, Clone, PartialEq)] 343 | pub enum TestFunction { 344 | /// Represents a custom function. 345 | Custom(String, Vec), 346 | /// Represents a length function. 347 | Length(Box), 348 | /// Represents a value function. 349 | Value(FnArg), 350 | /// Represents a count function. 351 | Count(FnArg), 352 | /// Represents a search function. 353 | Search(FnArg, FnArg), 354 | /// Represents a match function. 355 | Match(FnArg, FnArg), 356 | } 357 | 358 | impl TestFunction { 359 | pub fn try_new(name: &str, args: Vec) -> Parsed { 360 | fn with_node_type_validation<'a>( 361 | a: &'a FnArg, 362 | name: &str, 363 | ) -> Result<&'a FnArg, JsonPathError> { 364 | if a.is_lit() { 365 | Err(JsonPathError::InvalidJsonPath(format!( 366 | "Invalid argument for the function `{}`: expected a node, got a literal", 367 | name 368 | ))) 369 | } else if a.is_filter() { 370 | Err(JsonPathError::InvalidJsonPath(format!( 371 | "Invalid argument for the function `{}`: expected a node, got a filter", 372 | name 373 | ))) 374 | } else { 375 | Ok(a) 376 | } 377 | } 378 | 379 | match (name, args.as_slice()) { 380 | ("length", [a]) => Ok(TestFunction::Length(Box::new(a.clone()))), 381 | ("value", [a]) => Ok(TestFunction::Value(a.clone())), 382 | ("count", [a]) => Ok(TestFunction::Count( 383 | with_node_type_validation(a, name)?.clone(), 384 | )), 385 | ("search", [a, b]) => Ok(TestFunction::Search(a.clone(), b.clone())), 386 | ("match", [a, b]) => Ok(TestFunction::Match(a.clone(), b.clone())), 387 | ("length" | "value" | "count" | "match" | "search", args) => { 388 | Err(JsonPathError::InvalidJsonPath(format!( 389 | "Invalid number of arguments for the function `{}`: got {}", 390 | name, 391 | args.len() 392 | ))) 393 | } 394 | (custom, _) => Ok(TestFunction::Custom(custom.to_string(), args)), 395 | } 396 | } 397 | 398 | pub fn is_comparable(&self) -> bool { 399 | match self { 400 | TestFunction::Length(_) => true, 401 | TestFunction::Value(_) => true, 402 | TestFunction::Count(_) => true, 403 | TestFunction::Custom(_, _) => false, 404 | TestFunction::Search(_, _) => false, 405 | TestFunction::Match(_, _) => false, 406 | } 407 | } 408 | } 409 | 410 | impl Display for TestFunction { 411 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 412 | match self { 413 | TestFunction::Custom(name, args) => write!( 414 | f, 415 | "{}({})", 416 | name, 417 | args.iter().map(|a| a.to_string()).collect::() 418 | ), 419 | TestFunction::Length(arg) => write!(f, "length({})", arg), 420 | TestFunction::Value(arg) => write!(f, "value({})", arg), 421 | TestFunction::Count(arg) => write!(f, "count({})", arg), 422 | TestFunction::Search(arg1, arg2) => write!(f, "search({}, {})", arg1, arg2), 423 | TestFunction::Match(arg1, arg2) => write!(f, "match({}, {})", arg1, arg2), 424 | } 425 | } 426 | } 427 | 428 | /// Enum representing different types of function arguments in a JSONPath query. 429 | #[derive(Debug, Clone, PartialEq)] 430 | pub enum FnArg { 431 | /// Represents a literal argument. 432 | Literal(Literal), 433 | /// Represents a test argument. 434 | Test(Box), 435 | /// Represents a filter argument. 436 | Filter(Filter), 437 | } 438 | 439 | impl FnArg { 440 | pub fn is_lit(&self) -> bool { 441 | matches!(self, FnArg::Literal(_)) 442 | } 443 | pub fn is_filter(&self) -> bool { 444 | matches!(self, FnArg::Filter(_)) 445 | } 446 | } 447 | 448 | impl Display for FnArg { 449 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 450 | match self { 451 | FnArg::Literal(literal) => write!(f, "{}", literal), 452 | FnArg::Test(test) => write!(f, "{}", test), 453 | FnArg::Filter(filter) => write!(f, "{}", filter), 454 | } 455 | } 456 | } 457 | 458 | /// Enum representing different types of literal values in a JSONPath query. 459 | #[derive(Debug, Clone, PartialEq)] 460 | pub enum Literal { 461 | /// Represents an integer literal. 462 | Int(i64), 463 | /// Represents a floating-point literal. 464 | Float(f64), 465 | /// Represents a string literal. 466 | String(String), 467 | /// Represents a boolean literal. 468 | Bool(bool), 469 | /// Represents a null literal. 470 | Null, 471 | } 472 | 473 | impl Display for Literal { 474 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 475 | match self { 476 | Literal::Int(val) => write!(f, "{}", val), 477 | Literal::Float(val) => write!(f, "{}", val), 478 | Literal::String(val) => write!(f, "\"{}\"", val), 479 | Literal::Bool(val) => write!(f, "{}", val), 480 | Literal::Null => write!(f, "null"), 481 | } 482 | } 483 | } 484 | -------------------------------------------------------------------------------- /src/parser/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::model::slice_from; 2 | use crate::parser::model::Comparison; 3 | use crate::parser::model::FilterAtom; 4 | use crate::parser::model::FnArg; 5 | use crate::parser::model::JpQuery; 6 | use crate::parser::model::Literal; 7 | use crate::parser::model::Segment; 8 | use crate::parser::model::Selector; 9 | use crate::parser::model::SingularQuery; 10 | use crate::parser::model::SingularQuerySegment; 11 | use crate::parser::model::TestFunction; 12 | use crate::parser::model::{Comparable, Filter}; 13 | use crate::parser::Test; 14 | use crate::parser::{ 15 | comp_expr, comparable, filter_atom, function_expr, jp_query, literal, parse_json_path, segment, 16 | selector, singular_query, singular_query_segments, slice_selector, test, JSPathParser, Parsed, 17 | Rule, 18 | }; 19 | use crate::{ 20 | arg, atom, cmp, comparable, jq, lit, or, q_segment, q_segments, segment, selector, 21 | singular_query, slice, test, test_fn, 22 | }; 23 | use pest::error::Error; 24 | use pest::iterators::Pair; 25 | use pest::Parser; 26 | use std::fmt::Debug; 27 | use std::{panic, vec}; 28 | 29 | struct TestPair { 30 | rule: Rule, 31 | parse_fn: fn(Pair) -> Parsed, 32 | } 33 | 34 | impl TestPair { 35 | fn new(rule: Rule, parse_fn: fn(Pair) -> Parsed) -> Self { 36 | Self { rule, parse_fn } 37 | } 38 | fn assert(self, input: &str, expected: T) -> Self { 39 | match parse(input, self.rule) { 40 | Ok(e) => { 41 | assert((self.parse_fn)(e), expected); 42 | } 43 | Err(e) => { 44 | panic!("parsing error `{}`", e); 45 | } 46 | } 47 | self 48 | } 49 | fn assert_fail(self, input: &str) -> Self { 50 | match parse(input, self.rule) { 51 | Ok(e) => { 52 | if let Ok(r) = (self.parse_fn)(e) { 53 | panic!("expected error, got {:?}", r); 54 | } 55 | } 56 | Err(e) => { 57 | println!("parsing error `{}`", e); 58 | } 59 | } 60 | self 61 | } 62 | } 63 | 64 | fn parse(input: &str, rule: Rule) -> Result, Error> { 65 | match JSPathParser::parse(rule, input) { 66 | Ok(e) => Ok(e.into_iter().next().expect("no pairs found")), 67 | Err(e) => Err(e), 68 | } 69 | } 70 | 71 | fn assert(result: Parsed, expected: T) 72 | where 73 | T: PartialEq + Debug, 74 | { 75 | match result { 76 | Ok(e) => assert_eq!(e, expected), 77 | Err(e) => { 78 | panic!("parsing error `{}`", e); 79 | } 80 | } 81 | } 82 | 83 | #[test] 84 | fn singular_query_segment_test() { 85 | TestPair::new(Rule::singular_query_segments, singular_query_segments) 86 | .assert("[\"b\"][\"b\"]", q_segments!([b][b])) 87 | .assert("[2][1]", q_segments!([2][1])) 88 | .assert("[2][\"a\"]", q_segments!([2][a])) 89 | .assert(".a.b", q_segments!(a b)) 90 | .assert(".a.b[\"c\"][1]", q_segments!(a b [c][1])); 91 | } 92 | #[test] 93 | fn singular_query_test() { 94 | TestPair::new(Rule::singular_query, singular_query) 95 | .assert("@.a.b", singular_query!(@ a b)) 96 | .assert("@", SingularQuery::Current(vec![])) 97 | .assert("$", SingularQuery::Root(vec![])) 98 | .assert("$.a.b.c", singular_query!(a b c)) 99 | .assert("$[\"a\"].b[3]", singular_query!([a] b [3])); 100 | } 101 | 102 | #[test] 103 | fn slice_selector_test() { 104 | TestPair::new(Rule::slice_selector, slice_selector) 105 | .assert(":", slice!()) 106 | .assert("::", slice!()) 107 | .assert("1:", slice!(1)) 108 | .assert("1:1", slice!(1, 1)) 109 | .assert("1:1:1", slice!(1, 1, 1)) 110 | .assert(":1:1", slice!(,1,1)) 111 | .assert("::1", slice!(,,1)) 112 | .assert("1::1", slice!(1,,1)) 113 | .assert_fail("-0:") 114 | .assert_fail("9007199254740995"); 115 | } 116 | 117 | #[test] 118 | fn function_expr_test() { 119 | TestPair::new(Rule::function_expr, function_expr) 120 | .assert("length(1)", test_fn!(length arg!(lit!(i 1)))) 121 | .assert("length(true)", test_fn!(length arg!(lit!(b true)))) 122 | .assert( 123 | "search(@, \"abc\")", 124 | test_fn!(search arg!(t test!(@ ) ), arg!(lit!(s "abc"))), 125 | ) 126 | .assert( 127 | "count(@.a)", 128 | test_fn!(count arg!(t test!(@ segment!(selector!(a))))), 129 | ) 130 | .assert_fail("count\t(@.*)"); 131 | } 132 | 133 | #[test] 134 | fn jq_test() { 135 | let atom = Filter::Atom(atom!( 136 | comparable!(> singular_query!(@ a b)), 137 | ">", 138 | comparable!(lit!(i 1)) 139 | )); 140 | TestPair::new(Rule::jp_query, jp_query).assert( 141 | "$.a.b[?@.a.b > 1]", 142 | jq!( 143 | segment!(selector!(a)), 144 | segment!(selector!(b)), 145 | segment!(selector!(?atom)) 146 | ), 147 | ); 148 | } 149 | 150 | #[test] 151 | fn comp_expr_test() { 152 | TestPair::new(Rule::comp_expr, comp_expr).assert( 153 | "@.a.b.c == 1", 154 | cmp!( 155 | comparable!(> singular_query!(@ a b c)), 156 | "==", 157 | comparable!(lit!(i 1)) 158 | ), 159 | ); 160 | } 161 | 162 | #[test] 163 | fn literal_test() { 164 | TestPair::new(Rule::literal, literal) 165 | .assert("'☺'", lit!(s "☺")) 166 | .assert_fail("\"\n\"") 167 | .assert("' '", lit!(s " ")) 168 | .assert("'\"'", lit!(s "\"")) 169 | .assert("null", lit!()) 170 | .assert("false", lit!(b false)) 171 | .assert("true", lit!(b true)) 172 | .assert("\"hello\"", lit!(s "hello")) 173 | .assert("\'hello\'", lit!(s "hello")) 174 | .assert("\'hel\\'lo\'", lit!(s "hel\\'lo")) 175 | .assert("\'hel\"lo\'", lit!(s "hel\"lo")) 176 | .assert("\'hel\\nlo\'", lit!(s "hel\\nlo")) 177 | .assert("1", lit!(i 1)) 178 | .assert("0", lit!(i 0)) 179 | .assert("-0", lit!(i 0)) 180 | .assert("1.2", lit!(f 1.2)) 181 | .assert("9007199254740990", lit!(i 9007199254740990)) 182 | .assert_fail("hel\\\"lo") 183 | .assert_fail("9007199254740995"); 184 | } 185 | 186 | #[test] 187 | fn filter_atom_test() { 188 | TestPair::new(Rule::atom_expr, filter_atom) 189 | .assert( 190 | "1 > 2", 191 | atom!(comparable!(lit!(i 1)), ">", comparable!(lit!(i 2))), 192 | ) 193 | .assert( 194 | "!(@.a ==1 || @.b == 2)", 195 | atom!(!or!( 196 | Filter::Atom(atom!( 197 | comparable!(> singular_query!(@ a)), 198 | "==", 199 | comparable!(lit!(i 1)) 200 | )), 201 | Filter::Atom(atom!( 202 | comparable!(> singular_query!(@ b)), 203 | "==", 204 | comparable!(lit!(i 2)) 205 | )) 206 | )), 207 | ); 208 | } 209 | #[test] 210 | fn comparable_test() { 211 | TestPair::new(Rule::comparable, comparable) 212 | .assert("1", comparable!(lit!(i 1))) 213 | .assert("\"a\"", comparable!(lit!(s "a"))) 214 | .assert("@.a.b.c", comparable!(> singular_query!(@ a b c))) 215 | .assert("$.a.b.c", comparable!(> singular_query!(a b c))) 216 | .assert("$[1]", comparable!(> singular_query!([1]))) 217 | .assert("length(1)", comparable!(f test_fn!(length arg!(lit!(i 1))))); 218 | } 219 | 220 | #[test] 221 | fn parse_path() { 222 | let result = parse_json_path("$"); 223 | assert!(result.is_ok()); 224 | assert_eq!(result.unwrap(), JpQuery::new(vec![])); 225 | } 226 | 227 | #[test] 228 | fn parse_i64() { 229 | TestPair::new(Rule::literal, literal).assert("1e2", lit!(f 100.0)); 230 | } 231 | #[test] 232 | fn parse_selector() { 233 | TestPair::new(Rule::selector, selector).assert("1:1", Selector::Slice(Some(1), Some(1), None)); 234 | } 235 | #[test] 236 | fn parse_global() { 237 | let sel_a = segment!(selector!(a)); 238 | TestPair::new(Rule::jp_query, jp_query) 239 | // .assert("$", JpQuery::new(vec![])) 240 | // .assert("$.a", JpQuery::new(vec![sel_a.clone()])) 241 | // .assert("$..a", JpQuery::new(vec![segment!(..sel_a)])) 242 | // .assert( 243 | // "$..*", 244 | // JpQuery::new(vec![segment!(..segment!(selector!(*)))]), 245 | // ) 246 | // .assert( 247 | // "$[1 :5:2]", 248 | // JpQuery::new(vec![segment!(selector!(slice slice!(1, 5, 2)))]), 249 | // ) 250 | // .assert( 251 | // "$['a']['b']", 252 | // JpQuery::new(vec![segment!(Selector::Name("'a'".to_string())), segment!(Selector::Name("'b'".to_string()))]), 253 | // ) 254 | // 255 | // .assert( 256 | // "$[1, 1:1]", 257 | // JpQuery::new(vec![Segment::Selectors(vec![ 258 | // Selector::Index(1), 259 | // Selector::Slice(Some(1), Some(1), None), 260 | // ])]), 261 | // ) 262 | // .assert_fail("$..\ra") 263 | ; 264 | } 265 | -------------------------------------------------------------------------------- /src/query.rs: -------------------------------------------------------------------------------- 1 | mod atom; 2 | mod comparable; 3 | mod comparison; 4 | mod filter; 5 | mod jp_query; 6 | pub mod queryable; 7 | mod segment; 8 | mod selector; 9 | pub mod state; 10 | mod test; 11 | mod test_function; 12 | 13 | use crate::parser::errors::JsonPathError; 14 | use crate::parser::model::JpQuery; 15 | use crate::parser::{parse_json_path, Parsed}; 16 | use crate::query::queryable::Queryable; 17 | use crate::query::state::{Data, Pointer}; 18 | use state::State; 19 | use std::borrow::Cow; 20 | 21 | /// A type that can be queried with JSONPath, typically string 22 | pub type QueryPath = String; 23 | 24 | /// A type that can be queried with JSONPath, typically Result 25 | pub type Queried = Result; 26 | 27 | /// Main internal trait to implement the logic of processing jsonpath. 28 | pub trait Query { 29 | fn process<'a, T: Queryable>(&self, state: State<'a, T>) -> State<'a, T>; 30 | } 31 | 32 | /// The resulting type of JSONPath query. 33 | /// It can either be a value or a reference to a value with its path. 34 | #[derive(Debug, Clone, PartialEq)] 35 | pub struct QueryRef<'a, T: Queryable>(&'a T, QueryPath); 36 | 37 | impl<'a, T: Queryable> From<(&'a T, QueryPath)> for QueryRef<'a, T> { 38 | fn from((inner, path): (&'a T, QueryPath)) -> Self { 39 | QueryRef(inner, path) 40 | } 41 | } 42 | impl<'a, T: Queryable> From<(&'a T, &str)> for QueryRef<'a, T> { 43 | fn from((inner, path): (&'a T, &str)) -> Self { 44 | QueryRef(inner, path.to_string()) 45 | } 46 | } 47 | 48 | impl<'a, T: Queryable> QueryRef<'a, T> { 49 | pub fn val(self) -> &'a T { 50 | self.0 51 | } 52 | pub fn path(self) -> QueryPath { 53 | self.1 54 | } 55 | } 56 | 57 | impl<'a, T: Queryable> From> for QueryRef<'a, T> { 58 | fn from(pointer: Pointer<'a, T>) -> Self { 59 | QueryRef(pointer.inner, pointer.path) 60 | } 61 | } 62 | 63 | /// The main function to process a JSONPath query. 64 | /// It takes a path and a value, and returns a vector of `QueryResult` thus values + paths. 65 | pub fn js_path<'a, T: Queryable>(path: &str, value: &'a T) -> Queried>> { 66 | js_path_process(&parse_json_path(path)?, value) 67 | } 68 | 69 | /// A convenience function to process a JSONPath query 70 | /// and return a vector of values, omitting the path. 71 | pub fn js_path_process<'a, 'b, T: Queryable>( 72 | path: &'b JpQuery, 73 | value: &'a T, 74 | ) -> Queried>> { 75 | match path.process(State::root(value)).data { 76 | Data::Ref(p) => Ok(vec![p.into()]), 77 | Data::Refs(refs) => Ok(refs.into_iter().map(Into::into).collect()), 78 | Data::Value(v) => Err(v.into()), 79 | Data::Nothing => Ok(vec![]), 80 | } 81 | } 82 | 83 | /// A convenience function to process a JSONPath query and return a vector of values, omitting the path. 84 | pub fn js_path_vals<'a, T: Queryable>(path: &str, value: &'a T) -> Queried> { 85 | Ok(js_path(path, value)? 86 | .into_iter() 87 | .map(|r| r.val()) 88 | .collect::>()) 89 | } 90 | 91 | /// A convenience function to process a JSONPath query and return a vector of paths, omitting the values. 92 | pub fn js_path_path(path: &str, value: &T) -> Queried> { 93 | Ok(js_path(path, value)? 94 | .into_iter() 95 | .map(|r| r.path()) 96 | .collect::>()) 97 | } 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use crate::parser::errors::JsonPathError; 102 | use crate::parser::{parse_json_path, Parsed}; 103 | use crate::query::queryable::Queryable; 104 | use crate::query::{js_path, js_path_process, Queried, QueryRef}; 105 | use crate::JsonPath; 106 | use serde_json::{json, Value}; 107 | 108 | fn test<'a, R>(json: &'a str, path: &str, expected: Vec) -> Parsed<()> 109 | where 110 | R: Into>, 111 | { 112 | let json: Value = serde_json::from_str(json).map_err(|v| JsonPathError::NoRulePath)?; 113 | let expected: Vec> = expected.into_iter().map(|v| v.into()).collect(); 114 | assert_eq!(json.query_with_path(path)?, expected); 115 | 116 | Ok(()) 117 | } 118 | 119 | fn template_json<'a>() -> &'a str { 120 | r#" {"store": { "book": [ 121 | { 122 | "category": "reference", 123 | "author": "Nigel Rees", 124 | "title": "Sayings of the Century", 125 | "price": 8.95 126 | }, 127 | { 128 | "category": "fiction", 129 | "author": "Evelyn Waugh", 130 | "title": "Sword of Honour", 131 | "price": 12.99 132 | }, 133 | { 134 | "category": "fiction", 135 | "author": "Herman Melville", 136 | "title": "Moby Dick", 137 | "isbn": "0-553-21311-3", 138 | "price": 8.99 139 | }, 140 | { 141 | "category": "fiction", 142 | "author": "J. R. R. Tolkien", 143 | "title": "The Lord of the Rings", 144 | "isbn": "0-395-19395-8", 145 | "price": 22.99 146 | } 147 | ], 148 | "bicycle": { 149 | "color": "red", 150 | "price": 19.95 151 | } 152 | }, 153 | "array":[0,1,2,3,4,5,6,7,8,9], 154 | "orders":[ 155 | { 156 | "ref":[1,2,3], 157 | "id":1, 158 | "filled": true 159 | }, 160 | { 161 | "ref":[4,5,6], 162 | "id":2, 163 | "filled": false 164 | }, 165 | { 166 | "ref":[7,8,9], 167 | "id":3, 168 | "filled": null 169 | } 170 | ], 171 | "expensive": 10 }"# 172 | } 173 | fn update_by_path_test() -> Queried<()> { 174 | let mut json = json!([ 175 | {"verb": "RUN","distance":[1]}, 176 | {"verb": "TEST"}, 177 | {"verb": "DO NOT RUN"} 178 | ]); 179 | 180 | let path = json.query_only_path("$.[?(@.verb == 'RUN')]")?; 181 | let elem = path.first().cloned().unwrap_or_default(); 182 | 183 | if let Some(v) = json 184 | .reference_mut(elem) 185 | .and_then(|v| v.as_object_mut()) 186 | .and_then(|v| v.get_mut("distance")) 187 | .and_then(|v| v.as_array_mut()) 188 | { 189 | v.push(json!(2)) 190 | } 191 | 192 | assert_eq!( 193 | json, 194 | json!([ 195 | {"verb": "RUN","distance":[1,2]}, 196 | {"verb": "TEST"}, 197 | {"verb": "DO NOT RUN"} 198 | ]) 199 | ); 200 | 201 | Ok(()) 202 | } 203 | 204 | #[test] 205 | fn simple_test() { 206 | let j1 = json!(2); 207 | let _ = test("[1,2,3]", "$[1]", vec![(&j1, "$[1]".to_string())]); 208 | } 209 | 210 | #[test] 211 | fn root_test() { 212 | let js = serde_json::from_str(template_json()).unwrap(); 213 | let _ = test(template_json(), "$", vec![(&js, "$")]); 214 | } 215 | 216 | #[test] 217 | fn descent_test() { 218 | let v1 = json!("reference"); 219 | let v2 = json!("fiction"); 220 | let _ = test( 221 | template_json(), 222 | "$..category", 223 | vec![ 224 | (&v1, "$['store']['book'][0]['category']"), 225 | (&v2, "$['store']['book'][1]['category']"), 226 | (&v2, "$['store']['book'][2]['category']"), 227 | (&v2, "$['store']['book'][3]['category']"), 228 | ], 229 | ); 230 | let js1 = json!(19.95); 231 | let js2 = json!(8.95); 232 | let js3 = json!(12.99); 233 | let js4 = json!(8.99); 234 | let js5 = json!(22.99); 235 | let _ = test( 236 | template_json(), 237 | "$.store..price", 238 | vec![ 239 | (&js1, "$['store']['bicycle']['price']"), 240 | (&js2, "$['store']['book'][0]['price']"), 241 | (&js3, "$['store']['book'][1]['price']"), 242 | (&js4, "$['store']['book'][2]['price']"), 243 | (&js5, "$['store']['book'][3]['price']"), 244 | ], 245 | ); 246 | let js1 = json!("Nigel Rees"); 247 | let js2 = json!("Evelyn Waugh"); 248 | let js3 = json!("Herman Melville"); 249 | let js4 = json!("J. R. R. Tolkien"); 250 | let _ = test( 251 | template_json(), 252 | "$..author", 253 | vec![ 254 | (&js1, "$['store']['book'][0]['author']"), 255 | (&js2, "$['store']['book'][1]['author']"), 256 | (&js3, "$['store']['book'][2]['author']"), 257 | (&js4, "$['store']['book'][3]['author']"), 258 | ], 259 | ); 260 | } 261 | 262 | #[test] 263 | fn wildcard_test() { 264 | let js1 = json!("reference"); 265 | let js2 = json!("fiction"); 266 | let _ = test( 267 | template_json(), 268 | "$..book.[*].category", 269 | vec![ 270 | (&js1, "$['store']['book'][0].['category']"), 271 | (&js2, "$['store']['book'][1].['category']"), 272 | (&js2, "$['store']['book'][2].['category']"), 273 | (&js2, "$['store']['book'][3].['category']"), 274 | ], 275 | ); 276 | let js1 = json!("Nigel Rees"); 277 | let js2 = json!("Evelyn Waugh"); 278 | let js3 = json!("Herman Melville"); 279 | let js4 = json!("J. R. R. Tolkien"); 280 | let _ = test( 281 | template_json(), 282 | "$.store.book[*].author", 283 | vec![ 284 | (&js1, "$['store']['book'][0]['author']"), 285 | (&js2, "$['store']['book'][1]['author']"), 286 | (&js3, "$['store']['book'][2]['author']"), 287 | (&js4, "$['store']['book'][3]['author']"), 288 | ], 289 | ); 290 | } 291 | 292 | #[test] 293 | fn descendent_wildcard_test() { 294 | let js1 = json!("0-553-21311-3"); 295 | let js2 = json!("0-395-19395-8"); 296 | let _ = test( 297 | template_json(), 298 | "$..*.[?@].isbn", 299 | vec![ 300 | (&js1, "$['store']['book'][2]['isbn']"), 301 | (&js2, "$['store']['book'][3]['isbn']"), 302 | ], 303 | ); 304 | } 305 | 306 | #[test] 307 | fn field_test() { 308 | let value = json!({"active":1}); 309 | let _ = test( 310 | r#"{"field":{"field":[{"active":1},{"passive":1}]}}"#, 311 | "$.field.field[?@.active]", 312 | vec![(&value, "$['field']['field'][0]")], 313 | ); 314 | } 315 | 316 | #[test] 317 | fn index_index_test() { 318 | let value = json!("0-553-21311-3"); 319 | let _ = test( 320 | template_json(), 321 | "$..book[2].isbn", 322 | vec![(&value, "$['store']['book'][2]['isbn']")], 323 | ); 324 | } 325 | 326 | #[test] 327 | fn index_unit_index_test() { 328 | let value = json!("0-553-21311-3"); 329 | let _ = test( 330 | template_json(), 331 | "$..book[2,4].isbn", 332 | vec![(&value, "$['store']['book'][2]['isbn']")], 333 | ); 334 | let value1 = json!("0-395-19395-8"); 335 | let _ = test( 336 | template_json(), 337 | "$..book[2,3].isbn", 338 | vec![ 339 | (&value, "$['store']['book'][2]['isbn']"), 340 | (&value1, "$['store']['book'][3]['isbn']"), 341 | ], 342 | ); 343 | } 344 | 345 | #[test] 346 | fn index_unit_keys_test() { 347 | let js1 = json!("Moby Dick"); 348 | let js2 = json!(8.99); 349 | let js3 = json!("The Lord of the Rings"); 350 | let js4 = json!(22.99); 351 | let _ = test( 352 | template_json(), 353 | "$..book[2,3]['title','price']", 354 | vec![ 355 | (&js1, "$['store']['book'][2]['title']"), 356 | (&js3, "$['store']['book'][3]['title']"), 357 | (&js2, "$['store']['book'][2]['price']"), 358 | (&js4, "$['store']['book'][3]['price']"), 359 | ], 360 | ); 361 | } 362 | 363 | #[test] 364 | fn index_slice_test() -> Parsed<()> { 365 | let i0 = "$['array'][0]"; 366 | let i1 = "$['array'][1]"; 367 | let i2 = "$['array'][2]"; 368 | let i3 = "$['array'][3]"; 369 | let i4 = "$['array'][4]"; 370 | let i5 = "$['array'][5]"; 371 | let i6 = "$['array'][6]"; 372 | let i7 = "$['array'][7]"; 373 | let i8 = "$['array'][8]"; 374 | let i9 = "$['array'][9]"; 375 | 376 | let j0 = json!(0); 377 | let j1 = json!(1); 378 | let j2 = json!(2); 379 | let j3 = json!(3); 380 | let j4 = json!(4); 381 | let j5 = json!(5); 382 | let j6 = json!(6); 383 | let j7 = json!(7); 384 | let j8 = json!(8); 385 | let j9 = json!(9); 386 | test( 387 | template_json(), 388 | "$.array[:]", 389 | vec![ 390 | (&j0, i0), 391 | (&j1, i1), 392 | (&j2, i2), 393 | (&j3, i3), 394 | (&j4, i4), 395 | (&j5, i5), 396 | (&j6, i6), 397 | (&j7, i7), 398 | (&j8, i8), 399 | (&j9, i9), 400 | ], 401 | )?; 402 | test( 403 | template_json(), 404 | "$.array[1:4:2]", 405 | vec![(&j1, i1), (&j3, i3)], 406 | )?; 407 | test( 408 | template_json(), 409 | "$.array[::3]", 410 | vec![(&j0, i0), (&j3, i3), (&j6, i6), (&j9, i9)], 411 | )?; 412 | test(template_json(), "$.array[-1:]", vec![(&j9, i9)])?; 413 | test(template_json(), "$.array[-2:-1]", vec![(&j8, i8)])?; 414 | 415 | Ok(()) 416 | } 417 | 418 | #[test] 419 | fn index_filter_test() -> Parsed<()> { 420 | let moby = json!("Moby Dick"); 421 | let rings = json!("The Lord of the Rings"); 422 | test( 423 | template_json(), 424 | "$..book[?@.isbn].title", 425 | vec![ 426 | (&moby, "$['store']['book'][2]['title']"), 427 | (&rings, "$['store']['book'][3]['title']"), 428 | ], 429 | )?; 430 | let sword = json!("Sword of Honour"); 431 | test( 432 | template_json(), 433 | "$..book[?(@.price != 8.95)].title", 434 | vec![ 435 | (&sword, "$['store']['book'][1]['title']"), 436 | (&moby, "$['store']['book'][2]['title']"), 437 | (&rings, "$['store']['book'][3]['title']"), 438 | ], 439 | )?; 440 | let sayings = json!("Sayings of the Century"); 441 | test( 442 | template_json(), 443 | "$..book[?(@.price == 8.95)].title", 444 | vec![(&sayings, "$['store']['book'][0]['title']")], 445 | )?; 446 | 447 | let js12 = json!(12.99); 448 | let js899 = json!(8.99); 449 | let js2299 = json!(22.99); 450 | test( 451 | template_json(), 452 | "$..book[?@.price >= 8.99].price", 453 | vec![ 454 | (&js12, "$['store']['book'][1]['price']"), 455 | (&js899, "$['store']['book'][2]['price']"), 456 | (&js2299, "$['store']['book'][3]['price']"), 457 | ], 458 | )?; 459 | 460 | test( 461 | template_json(), 462 | "$..book[?(@.price >= $.expensive)].price", 463 | vec![ 464 | (&js12, "$['store']['book'][1]['price']"), 465 | (&js2299, "$['store']['book'][3]['price']"), 466 | ], 467 | )?; 468 | Ok(()) 469 | } 470 | 471 | #[test] 472 | fn union_quotes() -> Queried<()> { 473 | let json = json!({ 474 | "a": "ab", 475 | "b": "bc" 476 | }); 477 | 478 | let vec = js_path("$['a',\r'b']", &json)?; 479 | 480 | assert_eq!( 481 | vec, 482 | vec![ 483 | (&json!("ab"), "$['a']".to_string()).into(), 484 | (&json!("bc"), "$['b']".to_string()).into(), 485 | ] 486 | ); 487 | 488 | Ok(()) 489 | } 490 | 491 | #[test] 492 | fn space_between_selectors() -> Queried<()> { 493 | let json = json!({ 494 | "a": { 495 | "b": "ab" 496 | } 497 | }); 498 | 499 | let vec = js_path("$['a'] \r['b']", &json)?; 500 | 501 | assert_eq!(vec, vec![(&json!("ab"), "$['a']['b']".to_string()).into(),]); 502 | 503 | Ok(()) 504 | } 505 | #[test] 506 | fn space_in_search() -> Queried<()> { 507 | let json = json!(["foo", "123"]); 508 | 509 | let vec = js_path("$[?search(@\n,'[a-z]+')]", &json)?; 510 | 511 | assert_eq!(vec, vec![(&json!("foo"), "$[0]".to_string()).into(),]); 512 | 513 | Ok(()) 514 | } 515 | #[test] 516 | fn filter_key() -> Queried<()> { 517 | let json = json!([ 518 | { 519 | "a": "b", 520 | "d": "e" 521 | }, 522 | { 523 | "a": 1, 524 | "d": "f" 525 | } 526 | ]); 527 | 528 | let vec = js_path("$[?@.a!=\"b\"]", &json)?; 529 | 530 | assert_eq!( 531 | vec, 532 | vec![(&json!({"a":1, "d":"f"}), "$[1]".to_string()).into(),] 533 | ); 534 | 535 | Ok(()) 536 | } 537 | 538 | #[test] 539 | fn regex_key() -> Queried<()> { 540 | let json = json!({ 541 | "regex": "b.?b", 542 | "values": [ 543 | "abc", 544 | "bcd", 545 | "bab", 546 | "bba", 547 | "bbab", 548 | "b", 549 | true, 550 | [], 551 | {} 552 | ] 553 | }); 554 | 555 | let vec = js_path("$.values[?match(@, $.regex)]", &json)?; 556 | 557 | assert_eq!( 558 | vec, 559 | vec![(&json!("bab"), "$['values'][2]".to_string()).into(),] 560 | ); 561 | 562 | Ok(()) 563 | } 564 | #[test] 565 | fn name_sel() -> Queried<()> { 566 | let json = json!({ 567 | "/": "A" 568 | }); 569 | 570 | let vec = js_path("$['\\/']", &json)?; 571 | 572 | assert_eq!(vec, vec![(&json!("A"), "$['\\/']".to_string()).into(),]); 573 | 574 | Ok(()) 575 | } 576 | #[test] 577 | fn unicode_fns() -> Queried<()> { 578 | let json = json!(["ж", "Ж", "1", "жЖ", true, [], {}]); 579 | 580 | let vec = js_path("$[?match(@, '\\\\p{Lu}')]", &json)?; 581 | 582 | assert_eq!(vec, vec![(&json!("Ж"), "$[1]".to_string()).into(),]); 583 | 584 | Ok(()) 585 | } 586 | #[test] 587 | fn fn_res_can_not_compare() -> Queried<()> { 588 | let json = json!({}); 589 | 590 | let vec = js_path("$[?match(@.a, 'a.*')==true]", &json); 591 | 592 | assert!(vec.is_err()); 593 | 594 | Ok(()) 595 | } 596 | #[test] 597 | fn too_small() -> Queried<()> { 598 | let json = json!({}); 599 | 600 | let vec = js_path("$[-9007199254740992]", &json); 601 | 602 | assert!(vec.is_err()); 603 | 604 | Ok(()) 605 | } 606 | #[test] 607 | fn filter_data() -> Queried<()> { 608 | let json = json!({ 609 | "a": 1, 610 | "b": 2, 611 | "c": 3 612 | }); 613 | 614 | let vec: Vec = json.query_only_path("$[?@<3]")?.into_iter().collect(); 615 | 616 | assert_eq!(vec, vec!["$['a']".to_string(), "$['b']".to_string()]); 617 | 618 | Ok(()) 619 | } 620 | #[test] 621 | fn exp_no_error() -> Queried<()> { 622 | let json = json!([ 623 | { 624 | "a": 100, 625 | "d": "e" 626 | }, 627 | { 628 | "a": 100.1, 629 | "d": "f" 630 | }, 631 | { 632 | "a": "100", 633 | "d": "g" 634 | } 635 | ]); 636 | 637 | let vec: Vec<&Value> = json.query("$[?@.a==1E2]")?; 638 | assert_eq!(vec, vec![&json!({"a":100, "d":"e"})]); 639 | 640 | Ok(()) 641 | } 642 | #[test] 643 | fn single_quote() -> Queried<()> { 644 | let json = json!({ 645 | "a'": "A", 646 | "b": "B" 647 | }); 648 | 649 | let vec = js_path("$[\"a'\"]", &json)?; 650 | assert_eq!(vec, vec![(&json!("A"), "$['\"a\'\"']".to_string()).into(),]); 651 | 652 | Ok(()) 653 | } 654 | #[test] 655 | fn union() -> Queried<()> { 656 | let json = json!([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); 657 | 658 | let vec: Vec> = json.query_with_path("$[1,5:7]")?; 659 | assert_eq!( 660 | vec, 661 | vec![ 662 | (&json!(1), "$[1]".to_string()).into(), 663 | (&json!(5), "$[5]".to_string()).into(), 664 | (&json!(6), "$[6]".to_string()).into(), 665 | ] 666 | ); 667 | 668 | Ok(()) 669 | } 670 | 671 | #[test] 672 | fn basic_descendent() -> Queried<()> { 673 | let json = json!({ 674 | "o": [ 675 | 0, 676 | 1, 677 | [ 678 | 2, 679 | 3 680 | ] 681 | ] 682 | }); 683 | 684 | let vec = js_path("$..[1]", &json)?; 685 | assert_eq!( 686 | vec, 687 | vec![ 688 | (&json!(1), "$['o'][1]".to_string()).into(), 689 | (&json!(3), "$['o'][2][1]".to_string()).into(), 690 | ] 691 | ); 692 | 693 | Ok(()) 694 | } 695 | #[test] 696 | fn filter_absent() -> Queried<()> { 697 | let json = json!([ 698 | { 699 | "list": [ 700 | 1 701 | ] 702 | } 703 | ]); 704 | 705 | let vec = js_path("$[?@.absent==@.list[9]]", &json)?; 706 | assert_eq!( 707 | vec, 708 | vec![(&json!({"list": [1]}), "$[0]".to_string()).into(),] 709 | ); 710 | 711 | Ok(()) 712 | } 713 | 714 | #[test] 715 | fn filter_star() -> Queried<()> { 716 | let json = json!([1,[],[2],{},{"a": 3}]); 717 | 718 | let vec = json.query_with_path("$[?@.*]")?; 719 | assert_eq!( 720 | vec, 721 | vec![ 722 | (&json!([2]), "$[2]".to_string()).into(), 723 | (&json!({"a": 3}), "$[4]".to_string()).into(), 724 | ] 725 | ); 726 | 727 | Ok(()) 728 | } 729 | 730 | #[test] 731 | fn space_test() -> Queried<()> { 732 | let json = json!({ " ": "A"}); 733 | 734 | let vec = json.query_with_path("$[' ']")?; 735 | assert_eq!(vec, vec![(&json!("A"), "$[\' \']".to_string()).into(),]); 736 | 737 | Ok(()) 738 | } 739 | #[test] 740 | fn neg_idx() -> Queried<()> { 741 | let json = json!(["first", "second"]); 742 | 743 | let vec = json.query_with_path("$[-2]")?; 744 | assert_eq!(vec, vec![(&json!("first"), "$[0]".to_string()).into(),]); 745 | 746 | Ok(()) 747 | } 748 | 749 | #[test] 750 | fn filter_slice() -> Queried<()> { 751 | let json = json!([ 752 | 1, 753 | [], 754 | [ 755 | 2 756 | ], 757 | [ 758 | 2, 759 | 3, 760 | 4 761 | ], 762 | {}, 763 | { 764 | "a": 3 765 | } 766 | ]); 767 | 768 | let vec = json.query_with_path("$[?@[0:2]]")?; 769 | assert_eq!( 770 | vec, 771 | vec![ 772 | (&json!([2]), "$[2]").into(), 773 | (&json!([2, 3, 4]), "$[3]").into(), 774 | ] 775 | ); 776 | 777 | Ok(()) 778 | } 779 | 780 | #[test] 781 | fn surr_pairs() -> Queried<()> { 782 | let json = json!({ 783 | "𝄞": "A" 784 | }); 785 | let vec = json.query_with_path("$['𝄞']")?; 786 | assert_eq!(vec, vec![(&json!("A"), "$['𝄞']".to_string()).into()]); 787 | 788 | Ok(()) 789 | } 790 | #[test] 791 | fn tab_key() -> Queried<()> { 792 | let json = json!({ 793 | "\\t": "A" 794 | }); 795 | let vec = json.query_with_path("$['\\t']")?; 796 | assert_eq!(vec, vec![(&json!("A"), "$['\\t']".to_string()).into()]); 797 | 798 | Ok(()) 799 | } 800 | #[test] 801 | fn escaped_up_hex() -> Queried<()> { 802 | let json = json!({ 803 | "☺": "A" 804 | }); 805 | let vec = json.query_with_path("$['☺']")?; 806 | assert_eq!(vec, vec![(&json!("A"), "$['☺']".to_string()).into()]); 807 | 808 | Ok(()) 809 | } 810 | #[test] 811 | fn carr_return() -> Queried<()> { 812 | let json = json!({ 813 | "\\r": "A" 814 | }); 815 | let vec = json.query_with_path("$['\\r']")?; 816 | assert_eq!(vec, vec![(&json!("A"), "$['\\r']".to_string()).into()]); 817 | 818 | Ok(()) 819 | } 820 | 821 | #[test] 822 | fn prepared_query() -> Queried<()> { 823 | let json1 = json!({ 824 | "a": 1, 825 | "b": 2, 826 | "c": 3 827 | }); 828 | let json2 = json!({ 829 | "a": 1, 830 | "b": 2, 831 | "c": 3 832 | }); 833 | 834 | let jq = parse_json_path("$[?@<3]")?; 835 | 836 | let v1 = js_path_process(&jq, &json1)?; 837 | let v2 = js_path_process(&jq, &json2)?; 838 | 839 | assert_eq!(v1, v2); 840 | 841 | Ok(()) 842 | } 843 | } 844 | -------------------------------------------------------------------------------- /src/query/atom.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::model::FilterAtom; 2 | use crate::query::queryable::Queryable; 3 | use crate::query::state::{Data, State}; 4 | use crate::query::Query; 5 | 6 | impl Query for FilterAtom { 7 | fn process<'a, T: Queryable>(&self, state: State<'a, T>) -> State<'a, T> { 8 | match self { 9 | FilterAtom::Filter { expr, not } => { 10 | let bool_res = expr.process(state); 11 | if *not { 12 | invert_bool(bool_res) 13 | } else { 14 | bool_res 15 | } 16 | } 17 | FilterAtom::Test { expr, not } => { 18 | let new_state = |b| State::bool(b, state.root); 19 | let res = expr.process(state.clone()); 20 | if expr.is_res_bool() { 21 | if *not { 22 | invert_bool(res) 23 | } else { 24 | res 25 | } 26 | } else { 27 | let struct_check = |s: &T| { 28 | if let Some(arr) = s.as_array() { 29 | !arr.is_empty() 30 | } else if let Some(obj) = s.as_object() { 31 | !obj.is_empty() 32 | } else if let Some(str) = s.as_str() { 33 | !str.is_empty() 34 | } else { 35 | true 36 | } 37 | }; 38 | 39 | let struct_presented = match res.data { 40 | Data::Ref(v) => struct_check(v.inner), 41 | Data::Refs(e) if e.is_empty() => false, 42 | Data::Refs(elems) => elems.iter().map(|v| v.inner).all(struct_check), 43 | _ => false, 44 | }; 45 | 46 | if struct_presented { 47 | new_state(!*not) 48 | } else { 49 | new_state(*not) 50 | } 51 | } 52 | } 53 | FilterAtom::Comparison(cmp) => cmp.process(state), 54 | } 55 | } 56 | } 57 | 58 | fn invert_bool(state: State) -> State { 59 | let root = state.root; 60 | State::bool( 61 | !state.ok_val().and_then(|v| v.as_bool()).unwrap_or_default(), 62 | root, 63 | ) 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use crate::parser::model::Comparable; 69 | use crate::parser::model::Literal; 70 | use crate::parser::model::SingularQuery; 71 | use crate::parser::model::SingularQuerySegment; 72 | use crate::parser::model::{Comparison, FilterAtom}; 73 | use crate::q_segment; 74 | use crate::query::queryable::Queryable; 75 | use crate::query::state::State; 76 | use crate::query::Query; 77 | use crate::{atom, comparable, lit}; 78 | use crate::{cmp, singular_query}; 79 | use crate::{filter_, q_segments}; 80 | use serde_json::json; 81 | 82 | #[test] 83 | fn test_comparison() { 84 | let json = json!({"i": 1}); 85 | let atom = atom!(comparable!(lit!(i 1)), ">=", comparable!(lit!(i 1))); 86 | let state = State::root(&json); 87 | let res = atom.process(state); 88 | assert_eq!(res.ok_val().and_then(|v| v.as_bool()), Some(true)); 89 | } 90 | 91 | #[test] 92 | fn test_not_filter_atom() { 93 | let json = json!({"a": 1 , "b": 2}); 94 | let state = State::root(&json); 95 | 96 | let f1 = filter_!(atom!( 97 | comparable!(> SingularQuery::Current(vec![])), 98 | ">", 99 | comparable!(lit!(i 2)) 100 | )); 101 | let f2 = filter_!(atom!( 102 | comparable!(> singular_query!(b)), 103 | "!=", 104 | comparable!(> singular_query!(a)) 105 | )); 106 | 107 | let atom_or = atom!(!filter_!(or f1.clone(), f2.clone())); 108 | let atom_and = atom!(!filter_!(and f1, f2)); 109 | 110 | assert_eq!( 111 | atom_or 112 | .process(state.clone()) 113 | .ok_val() 114 | .and_then(|v| v.as_bool()), 115 | Some(true) 116 | ); 117 | assert_eq!( 118 | atom_and.process(state).ok_val().and_then(|v| v.as_bool()), 119 | Some(true) 120 | ); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/query/comparable.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::model::{Comparable, Literal, SingularQuery, SingularQuerySegment}; 2 | use crate::query::queryable::Queryable; 3 | use crate::query::selector::{process_index, process_key}; 4 | use crate::query::state::{Data, State}; 5 | use crate::query::Query; 6 | 7 | impl Query for Comparable { 8 | fn process<'a, T: Queryable>(&self, step: State<'a, T>) -> State<'a, T> { 9 | match self { 10 | Comparable::Literal(lit) => lit.process(step), 11 | Comparable::Function(tf) => tf.process(step), 12 | Comparable::SingularQuery(query) => query.process(step), 13 | } 14 | } 15 | } 16 | 17 | impl Query for Literal { 18 | fn process<'a, T: Queryable>(&self, state: State<'a, T>) -> State<'a, T> { 19 | let val = match self { 20 | Literal::Int(v) => (*v).into(), 21 | Literal::Float(v) => (*v).into(), 22 | Literal::String(v) => v.as_str().into(), 23 | Literal::Bool(v) => (*v).into(), 24 | Literal::Null => T::null(), 25 | }; 26 | 27 | State::data(state.root, Data::Value(val)) 28 | } 29 | } 30 | 31 | impl Query for SingularQuery { 32 | fn process<'a, T: Queryable>(&self, step: State<'a, T>) -> State<'a, T> { 33 | match self { 34 | SingularQuery::Current(segments) => segments.process(step), 35 | SingularQuery::Root(segments) => segments.process(step.shift_to_root()), 36 | } 37 | } 38 | } 39 | 40 | impl Query for SingularQuerySegment { 41 | fn process<'a, T: Queryable>(&self, step: State<'a, T>) -> State<'a, T> { 42 | match self { 43 | SingularQuerySegment::Index(idx) => step.flat_map(|d| process_index(d, idx)), 44 | SingularQuerySegment::Name(key) => step.flat_map(|d| process_key(d, key)), 45 | } 46 | } 47 | } 48 | 49 | impl Query for Vec { 50 | fn process<'a, T: Queryable>(&self, state: State<'a, T>) -> State<'a, T> { 51 | self.iter() 52 | .fold(state, |next, segment| segment.process(next)) 53 | } 54 | } 55 | 56 | #[cfg(test)] 57 | mod tests { 58 | use crate::parser::model::{Comparable, Literal, SingularQuery, SingularQuerySegment}; 59 | use crate::query::state::{Data, Pointer, State}; 60 | use crate::query::Query; 61 | use serde_json::json; 62 | 63 | #[test] 64 | fn singular_query() { 65 | let value = json!({ 66 | "result": [ 67 | { 68 | "message": "Hello, Emmy! Your order number is: #100", 69 | "phoneNumber": "255-301-9429", 70 | "phoneVariation": "+90 398 588 10 73", 71 | "status": "active", 72 | "name": { 73 | "first": "Blaise", 74 | "middle": "Kyle", 75 | "last": "Fadel" 76 | } 77 | } 78 | ] 79 | }); 80 | 81 | let query = SingularQuery::Current(vec![ 82 | SingularQuerySegment::Name("result".to_string()), 83 | SingularQuerySegment::Index(0), 84 | SingularQuerySegment::Name("name".to_string()), 85 | SingularQuerySegment::Name("first".to_string()), 86 | ]); 87 | 88 | let state = State::root(&value); 89 | 90 | let result = query.process(state); 91 | assert_eq!( 92 | result.ok_ref(), 93 | Some(vec![Pointer::new( 94 | &json!("Blaise"), 95 | "$['result'][0]['name']['first']".to_string() 96 | )]) 97 | ); 98 | } 99 | 100 | #[test] 101 | fn singular_query_root() { 102 | let value = json!({ 103 | "result": [ 104 | { 105 | "message": "Hello, Emmy! Your order number is: #100", 106 | "phoneNumber": "255-301-9429", 107 | "phoneVariation": "+90 398 588 10 73", 108 | "status": "active", 109 | "name": { 110 | "first": "Blaise", 111 | "middle": "Kyle", 112 | "last": "Fadel" 113 | } 114 | } 115 | ] 116 | }); 117 | 118 | let query = SingularQuery::Root(vec![ 119 | SingularQuerySegment::Name("result".to_string()), 120 | SingularQuerySegment::Index(0), 121 | SingularQuerySegment::Name("name".to_string()), 122 | SingularQuerySegment::Name("first".to_string()), 123 | ]); 124 | 125 | let state = State::data( 126 | &value, 127 | Data::new_ref(Pointer::new(&value, "$.name".to_string())), 128 | ); 129 | 130 | let result = query.process(state); 131 | assert_eq!( 132 | result.ok_ref(), 133 | Some(vec![Pointer::new( 134 | &json!("Blaise"), 135 | "$['result'][0]['name']['first']".to_string() 136 | )]) 137 | ); 138 | } 139 | 140 | #[test] 141 | fn literal() { 142 | let value = json!({ 143 | "result": [ 144 | { 145 | "message": "Hello, Emmy! Your order number is: #100", 146 | "phoneNumber": "255-301-9429", 147 | "phoneVariation": "+90 398 588 10 73", 148 | "status": "active", 149 | "name": { 150 | "first": "Blaise", 151 | "middle": "Kyle", 152 | "last": "Fadel" 153 | } 154 | } 155 | ] 156 | }); 157 | 158 | let query = Comparable::Literal(Literal::String("Hello".to_string())); 159 | 160 | let state = State::root(&value); 161 | 162 | let result = query.process(state); 163 | assert_eq!(result.ok_val(), Some(json!("Hello"))); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/query/comparison.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::model::{Comparable, Comparison, Literal, SingularQuery, SingularQuerySegment}; 2 | use crate::query::queryable::Queryable; 3 | use crate::query::state::{Data, Pointer, State}; 4 | use crate::query::Query; 5 | 6 | impl Query for Comparison { 7 | fn process<'a, T: Queryable>(&self, state: State<'a, T>) -> State<'a, T> { 8 | let root = state.root; 9 | let (lhs, rhs) = self.vals(); 10 | let lhs = lhs.process(state.clone()); 11 | let rhs = rhs.process(state); 12 | match self { 13 | Comparison::Eq(..) => State::bool(eq(lhs, rhs), root), 14 | Comparison::Ne(..) => State::bool(!eq(lhs, rhs), root), 15 | Comparison::Gt(..) => State::bool(lt(rhs, lhs), root), 16 | Comparison::Gte(..) => State::bool(lt(rhs.clone(), lhs.clone()) || eq(lhs, rhs), root), 17 | Comparison::Lt(..) => State::bool(lt(lhs, rhs), root), 18 | Comparison::Lte(..) => State::bool(lt(lhs.clone(), rhs.clone()) || eq(lhs, rhs), root), 19 | } 20 | } 21 | } 22 | 23 | fn lt<'a, T: Queryable>(lhs: State<'a, T>, rhs: State<'a, T>) -> bool { 24 | let cmp = |lhs: &T, rhs: &T| { 25 | let lhs_f64 = lhs.as_f64().or_else(|| lhs.as_i64().map(|v| v as f64)); 26 | let rhs_f64 = rhs.as_f64().or_else(|| rhs.as_i64().map(|v| v as f64)); 27 | if let (Some(lhs_num), Some(rhs_num)) = (lhs_f64, rhs_f64) { 28 | lhs_num < rhs_num 29 | } else if let (Some(lhs), Some(rhs)) = (lhs.as_str(), rhs.as_str()) { 30 | lhs < rhs 31 | } else { 32 | false 33 | } 34 | }; 35 | 36 | match (lhs.data, rhs.data) { 37 | (Data::Value(lhs), Data::Value(rhs)) => cmp(&lhs, &rhs), 38 | (Data::Value(v), Data::Ref(p)) => cmp(&v, p.inner), 39 | (Data::Ref(p), Data::Value(v)) => cmp(p.inner, &v), 40 | (Data::Ref(lhs), Data::Ref(rhs)) => cmp(lhs.inner, rhs.inner), 41 | _ => false, 42 | } 43 | } 44 | 45 | fn eq<'a, T: Queryable>(lhs_state: State<'a, T>, rhs_state: State<'a, T>) -> bool { 46 | match (lhs_state.data, rhs_state.data) { 47 | (Data::Value(lhs), Data::Value(rhs)) => eq_json(&lhs, &rhs), 48 | (Data::Value(v), Data::Ref(p)) => eq_json(&v, p.inner), 49 | (Data::Ref(p), Data::Value(v)) => eq_json(&v, p.inner), 50 | (Data::Ref(lhs), Data::Ref(rhs)) => eq_json(lhs.inner, rhs.inner), 51 | (Data::Refs(lhs), Data::Refs(rhs)) => lhs == rhs, 52 | (Data::Ref(r), Data::Refs(rhs)) => eq_ref_to_array(r, &rhs), 53 | (Data::Nothing, Data::Nothing) => true, 54 | _ => false, 55 | } 56 | } 57 | /// Compare two JSON values for equality. 58 | /// For numbers, it should implement interoperability for integer and float 59 | fn eq_json(lhs: &T, rhs: &T) -> bool { 60 | let lhs_f64 = lhs.as_f64().or_else(|| lhs.as_i64().map(|v| v as f64)); 61 | let rhs_f64 = rhs.as_f64().or_else(|| rhs.as_i64().map(|v| v as f64)); 62 | 63 | if let (Some(lhs_num), Some(rhs_num)) = (lhs_f64, rhs_f64) { 64 | (lhs_num - rhs_num).abs() < f64::EPSILON 65 | } else { 66 | lhs == rhs 67 | } 68 | } 69 | fn eq_ref_to_array(r: Pointer, rhs: &Vec>) -> bool { 70 | r.inner.as_array().map_or(false, |array| { 71 | eq_arrays(array, &rhs.iter().map(|p| p.inner).collect::>()) 72 | }) 73 | } 74 | 75 | fn eq_arrays(lhs: &Vec, rhs: &Vec<&T>) -> bool { 76 | lhs.len() == rhs.len() && lhs.iter().zip(rhs.iter()).all(|(a, b)| eq_json(a, *b)) 77 | } 78 | 79 | #[cfg(test)] 80 | mod tests { 81 | use crate::parser::model::{ 82 | Comparable, Comparison, Literal, SingularQuery, SingularQuerySegment, 83 | }; 84 | use crate::query::state::{Data, Pointer, State}; 85 | use crate::query::Query; 86 | use crate::singular_query; 87 | use crate::{cmp, comparable, lit, q_segment, q_segments}; 88 | use serde_json::json; 89 | #[test] 90 | fn eq_comp_val() { 91 | let data = json!({"key": "value"}); 92 | let state = State::root(&data); 93 | 94 | let comparison = Comparison::Eq(comparable!(lit!(s "key")), comparable!(lit!(s "key"))); 95 | let result = comparison.process(state); 96 | assert_eq!(result.ok_val(), Some(json!(true))); 97 | } 98 | 99 | #[test] 100 | fn eq_comp_ref() { 101 | let data = json!({"key": "value"}); 102 | let state = State::root(&data); 103 | 104 | let comparison = Comparison::Eq( 105 | comparable!(lit!(s "value")), 106 | comparable!(> singular_query!(@ key)), 107 | ); 108 | 109 | let result = comparison.process(state); 110 | assert_eq!(result.ok_val(), Some(json!(true))); 111 | } 112 | 113 | #[test] 114 | fn eq_comp_queries() { 115 | let data = json!({"key": "value", "key2": "value"}); 116 | let state = State::root(&data); 117 | 118 | let comparison = Comparison::Eq( 119 | comparable!(> singular_query!(@ key)), 120 | comparable!(> singular_query!(key2)), 121 | ); 122 | let result = comparison.process(state); 123 | assert_eq!(result.ok_val(), Some(json!(true))); 124 | } 125 | 126 | #[test] 127 | fn neq_comp_val() { 128 | let data = json!({"key": "value"}); 129 | let state = State::root(&data); 130 | 131 | let comparison = Comparison::Ne(comparable!(lit!(s "key")), comparable!(lit!(s "key"))); 132 | let result = comparison.process(state); 133 | assert_eq!(result.ok_val(), Some(json!(false))); 134 | } 135 | 136 | #[test] 137 | fn less_than() { 138 | let data = json!({"key": 3}); 139 | let state = State::root(&data); 140 | 141 | let comparison = Comparison::Lt( 142 | comparable!(lit!(i 2)), 143 | comparable!(> singular_query!(@ key)), 144 | ); 145 | let result = comparison.process(state); 146 | assert_eq!(result.ok_val(), Some(json!(true))); 147 | } 148 | 149 | #[test] 150 | fn less_than_false() { 151 | let data = json!({"key": 1}); 152 | let state = State::root(&data); 153 | 154 | let comparison = Comparison::Lt( 155 | comparable!(lit!(i 2)), 156 | comparable!(> singular_query!(@ key)), 157 | ); 158 | let result = comparison.process(state); 159 | assert_eq!(result.ok_val(), Some(json!(false))); 160 | } 161 | 162 | #[test] 163 | fn less_true() { 164 | let data = json!({"key": 1}); 165 | let state = State::root(&data); 166 | 167 | let comparison = Comparison::Lt( 168 | comparable!(> singular_query!(@ key)), 169 | comparable!(lit!(i 2)), 170 | ); 171 | let result = comparison.process(state); 172 | assert_eq!(result.ok_val(), Some(json!(true))); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/query/filter.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::model::Filter; 2 | use crate::query::queryable::Queryable; 3 | use crate::query::state::{Data, Pointer, State}; 4 | use crate::query::Query; 5 | 6 | impl Query for Filter { 7 | fn process<'a, T: Queryable>(&self, state: State<'a, T>) -> State<'a, T> { 8 | let root = state.root; 9 | state.flat_map(|p| { 10 | if p.is_internal() { 11 | Data::Value(self.filter_item(p, root).into()) 12 | } else if let Some(items) = p.inner.as_array() { 13 | Data::Refs( 14 | items 15 | .into_iter() 16 | .enumerate() 17 | .filter(|(_, item)| self.filter_item(Pointer::empty(*item), root)) 18 | .map(|(idx, item)| Pointer::idx(item, p.path.clone(), idx)) 19 | .collect(), 20 | ) 21 | } else if let Some(items) = p.inner.as_object() { 22 | Data::Refs( 23 | items 24 | .into_iter() 25 | .filter(|(_, item)| self.filter_item(Pointer::empty(*item), root)) 26 | .map(|(key, item)| Pointer::key(item, p.path.clone(), key)) 27 | .collect(), 28 | ) 29 | } else { 30 | return Data::Nothing; 31 | } 32 | }) 33 | } 34 | } 35 | 36 | impl Filter { 37 | fn process_elem<'a, T: Queryable>(&self, state: State<'a, T>) -> State<'a, T> { 38 | let process_cond = |filter: &Filter| { 39 | filter 40 | .process(state.clone()) 41 | .ok_val() 42 | .and_then(|v| v.as_bool()) 43 | .unwrap_or_default() 44 | }; 45 | match self { 46 | Filter::Or(ors) => State::bool(ors.iter().any(process_cond), state.root), 47 | Filter::And(ands) => State::bool(ands.iter().all(process_cond), state.root), 48 | Filter::Atom(atom) => atom.process(state), 49 | } 50 | } 51 | 52 | fn filter_item<'a, T: Queryable>(&self, item: Pointer<'a, T>, root: &T) -> bool { 53 | self.process_elem(State::data(root, Data::Ref(item.clone()))) 54 | .ok_val() 55 | .and_then(|v| v.as_bool()) 56 | .unwrap_or_default() 57 | } 58 | } 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use crate::query::js_path; 63 | use serde_json::json; 64 | 65 | #[test] 66 | fn smoke_ok() { 67 | let json = json!({"a" : [1,2,3]}); 68 | 69 | assert_eq!( 70 | js_path("$.a[? @ > 1]", &json), 71 | Ok(vec![ 72 | (&json!(2), "$['a'][1]".to_string()).into(), 73 | (&json!(3), "$['a'][2]".to_string()).into(), 74 | ]) 75 | ); 76 | } 77 | 78 | #[test] 79 | fn existence() { 80 | let json = json!({ 81 | "a": { 82 | "a":{"b":1}, 83 | "c": { 84 | "b": 2 85 | }, 86 | "d": { 87 | "b1": 3 88 | } 89 | } 90 | }); 91 | assert_eq!( 92 | js_path("$.a[?@.b]", &json), 93 | Ok(vec![ 94 | (&json!({"b":1}), "$['a']['a']".to_string()).into(), 95 | (&json!({"b":2}), "$['a']['c']".to_string()).into(), 96 | ]) 97 | ); 98 | } 99 | 100 | #[test] 101 | fn existence_or() { 102 | let json = json!({ 103 | "a": { 104 | "a":{"b":1}, 105 | "c": { 106 | "b": 2 107 | }, 108 | "d": { 109 | "b1": 3 110 | }, 111 | "e": { 112 | "b2": 3 113 | } 114 | } 115 | }); 116 | assert_eq!( 117 | js_path("$.a[?@.b || @.b1]", &json), 118 | Ok(vec![ 119 | (&json!({"b":1}), "$['a']['a']".to_string()).into(), 120 | (&json!({"b":2}), "$['a']['c']".to_string()).into(), 121 | (&json!({"b1":3}), "$['a']['d']".to_string()).into(), 122 | ]) 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/query/jp_query.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::model::{JpQuery, Segment}; 2 | use crate::query::queryable::Queryable; 3 | use crate::query::state::State; 4 | use crate::query::Query; 5 | 6 | impl Query for JpQuery { 7 | fn process<'a, T: Queryable>(&self, state: State<'a, T>) -> State<'a, T> { 8 | self.segments.process(state) 9 | } 10 | } 11 | 12 | impl Query for Vec { 13 | fn process<'a, T: Queryable>(&self, state: State<'a, T>) -> State<'a, T> { 14 | self.iter() 15 | .fold(state, |next, segment| segment.process(next)) 16 | } 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use crate::parser::model::{JpQuery, Segment, Selector}; 22 | use crate::query::state::{Data, Pointer, State}; 23 | use crate::query::Query; 24 | use serde_json::json; 25 | 26 | #[test] 27 | fn test_process() { 28 | let value = json!({ 29 | "result": [ 30 | { 31 | "message": "Hello, Emmy! Your order number is: #100", 32 | "phoneNumber": "255-301-9429", 33 | "phoneVariation": "+90 398 588 10 73", 34 | "status": "active", 35 | "name": { 36 | "first": "Blaise", 37 | "middle": "Kyle", 38 | "last": "Fadel" 39 | } 40 | } 41 | ] 42 | }); 43 | 44 | let query = JpQuery::new(vec![ 45 | Segment::Selector(Selector::Name("result".to_string())), 46 | Segment::Selector(Selector::Index(0)), 47 | Segment::Selector(Selector::Name("name".to_string())), 48 | Segment::Selector(Selector::Name("first".to_string())), 49 | ]); 50 | 51 | let state = State::data(&value, Data::new_ref(Pointer::new(&value, "$".to_string()))); 52 | 53 | let result = query.process(state); 54 | assert_eq!( 55 | result.ok_ref(), 56 | Some(vec![Pointer::new( 57 | &json!("Blaise"), 58 | "$['result'][0]['name']['first']".to_string() 59 | )]) 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/query/queryable.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::errors::JsonPathError; 2 | use crate::parser::model::{JpQuery, Segment, Selector}; 3 | use crate::parser::{parse_json_path, Parsed}; 4 | use crate::query::QueryPath; 5 | use serde_json::Value; 6 | use std::borrow::Cow; 7 | use std::fmt::Debug; 8 | 9 | /// A trait that abstracts JSON-like data structures for JSONPath queries 10 | /// 11 | /// This trait provides the essential operations needed to traverse and query 12 | /// hierarchical data structures in a JSONPath-compatible way. Implementors of 13 | /// this trait can be used with the JSONPath query engine. 14 | /// 15 | /// The trait requires several standard type conversions to be implemented to 16 | /// ensure that query operations can properly handle various data types. 17 | /// 18 | /// # Type Requirements 19 | /// 20 | /// Implementing types must satisfy these trait bounds: 21 | /// - `Default`: Provides a default value for the type 22 | /// - `Clone`: Allows creation of copies of values 23 | /// - `Debug`: Enables debug formatting 24 | /// - `From<&str>`: Conversion from string slices 25 | /// - `From`: Conversion from boolean values 26 | /// - `From`: Conversion from 64-bit integers 27 | /// - `From`: Conversion from 64-bit floating point values 28 | /// - `From>`: Conversion from vectors of the same type 29 | /// - `From`: Conversion from owned strings 30 | /// - `PartialEq`: Allows equality comparisons 31 | /// 32 | /// # Examples 33 | /// 34 | /// The trait is primarily implemented for `serde_json::Value` to enable 35 | /// JSONPath queries on JSON data structures: 36 | /// 37 | /// ``` 38 | /// use serde_json::json; 39 | /// use jsonpath_rust::JsonPath; 40 | /// 41 | /// let data = json!({ 42 | /// "store": { 43 | /// "books": [ 44 | /// {"title": "Book 1", "price": 10}, 45 | /// {"title": "Book 2", "price": 15} 46 | /// ] 47 | /// } 48 | /// }); 49 | /// 50 | /// // Access data using the Queryable trait 51 | /// let books = data.query("$.store.books[*].title").expect("no errors"); 52 | /// ``` 53 | pub trait Queryable 54 | where 55 | Self: Default 56 | + Clone 57 | + Debug 58 | + for<'a> From<&'a str> 59 | + From 60 | + From 61 | + From 62 | + From> 63 | + From 64 | + PartialEq, 65 | { 66 | /// Retrieves a reference to the value associated with the given key. 67 | /// It is the responsibility of the implementation to handle enclosing single and double quotes. 68 | /// The key will be normalized (quotes trimmed, whitespace handled, the escape symbols handled) before lookup. 69 | fn get(&self, key: &str) -> Option<&Self>; 70 | 71 | fn as_array(&self) -> Option<&Vec>; 72 | 73 | fn as_object(&self) -> Option>; 74 | 75 | fn as_str(&self) -> Option<&str>; 76 | 77 | fn as_i64(&self) -> Option; 78 | fn as_f64(&self) -> Option; 79 | fn as_bool(&self) -> Option; 80 | 81 | /// Returns a null value. 82 | fn null() -> Self; 83 | 84 | fn extension_custom(_name: &str, _args: Vec>) -> Self { 85 | Self::null() 86 | } 87 | 88 | /// Retrieves a reference to the element at the specified path. 89 | /// The path is specified as a string and can be obtained from the query. 90 | /// 91 | /// # Arguments 92 | /// * `path` - A json path to the element specified as a string (root, field, index only). 93 | fn reference(&self, _path: T) -> Option<&Self> 94 | where 95 | T: Into, 96 | { 97 | None 98 | } 99 | 100 | /// Retrieves a mutable reference to the element at the specified path. 101 | /// 102 | /// # Arguments 103 | /// * `path` - A json path to the element specified as a string (root, field, index only). 104 | /// 105 | /// # Examples 106 | /// 107 | /// ``` 108 | /// use serde_json::json; 109 | /// use jsonpath_rust::JsonPath; 110 | /// use jsonpath_rust::query::queryable::Queryable; 111 | /// let mut json = json!({ 112 | /// "a": { 113 | /// "b": { 114 | /// "c": 42 115 | /// } 116 | /// } 117 | /// }); 118 | /// if let Some(path) = json.query_only_path("$.a.b.c").unwrap().first() { 119 | /// if let Some(v) = json.reference_mut("$.a.b.c") { 120 | /// *v = json!(43); 121 | /// } 122 | /// 123 | /// assert_eq!( 124 | /// json, 125 | /// json!({ 126 | /// "a": { 127 | /// "b": { 128 | /// "c": 43 129 | /// } 130 | /// } 131 | /// }) 132 | /// ); 133 | /// } 134 | //// ``` 135 | fn reference_mut(&mut self, _path: T) -> Option<&mut Self> 136 | where 137 | T: Into, 138 | { 139 | None 140 | } 141 | } 142 | 143 | impl Queryable for Value { 144 | fn get(&self, key: &str) -> Option<&Self> { 145 | let key = if key.starts_with("'") && key.ends_with("'") { 146 | key.trim_matches(|c| c == '\'') 147 | } else if key.starts_with('"') && key.ends_with('"') { 148 | key.trim_matches(|c| c == '"') 149 | } else { 150 | key 151 | }; 152 | self.get(key) 153 | } 154 | 155 | fn as_array(&self) -> Option<&Vec> { 156 | self.as_array() 157 | } 158 | 159 | fn as_object(&self) -> Option> { 160 | self.as_object() 161 | .map(|v| v.into_iter().map(|(k, v)| (k, v)).collect()) 162 | } 163 | 164 | fn as_str(&self) -> Option<&str> { 165 | self.as_str() 166 | } 167 | 168 | fn as_i64(&self) -> Option { 169 | self.as_i64() 170 | } 171 | 172 | fn as_f64(&self) -> Option { 173 | self.as_f64() 174 | } 175 | 176 | fn as_bool(&self) -> Option { 177 | self.as_bool() 178 | } 179 | 180 | fn null() -> Self { 181 | Value::Null 182 | } 183 | 184 | /// Custom extension function for JSONPath queries. 185 | /// 186 | /// This function allows for custom operations to be performed on JSON data 187 | /// based on the provided `name` and `args`. 188 | /// 189 | /// # Arguments 190 | /// 191 | /// * `name` - A string slice that holds the name of the custom function. 192 | /// * `args` - A vector of `Cow` that holds the arguments for the custom function. 193 | /// 194 | /// # Returns 195 | /// 196 | /// Returns a `Self` value which is the result of the custom function. If the function 197 | /// name is not recognized, it returns `Self::null()`. 198 | /// 199 | /// # Custom Functions 200 | /// 201 | /// * `"in"` - Checks if the first argument is in the array provided as the second argument. 202 | /// Example: `$.elems[?in(@, $.list)]` - Returns elements from $.elems that are present in $.list 203 | /// 204 | /// * `"nin"` - Checks if the first argument is not in the array provided as the second argument. 205 | /// Example: `$.elems[?nin(@, $.list)]` - Returns elements from $.elems that are not present in $.list 206 | /// 207 | /// * `"none_of"` - Checks if none of the elements in the first array are in the second array. 208 | /// Example: `$.elems[?none_of(@, $.list)]` - Returns arrays from $.elems that have no elements in common with $.list 209 | /// 210 | /// * `"any_of"` - Checks if any of the elements in the first array are in the second array. 211 | /// Example: `$.elems[?any_of(@, $.list)]` - Returns arrays from $.elems that have at least one element in common with $.list 212 | /// 213 | /// * `"subset_of"` - Checks if all elements in the first array are in the second array. 214 | /// Example: `$.elems[?subset_of(@, $.list)]` - Returns arrays from $.elems where all elements are present in $.list 215 | fn extension_custom(name: &str, args: Vec>) -> Self { 216 | match name { 217 | "in" => match args.as_slice() { 218 | [lhs, rhs] => match rhs.as_array() { 219 | Some(elements) => elements.iter().any(|item| item == lhs.as_ref()).into(), 220 | None => Self::null(), 221 | }, 222 | _ => Self::null(), 223 | }, 224 | "nin" => match args.as_slice() { 225 | [lhs, rhs] => match rhs.as_array() { 226 | Some(elements) => (!elements.iter().any(|item| item == lhs.as_ref())).into(), 227 | None => Self::null(), 228 | }, 229 | _ => Self::null(), 230 | }, 231 | "none_of" => match args.as_slice() { 232 | [lhs, rhs] => match (lhs.as_array(), rhs.as_array()) { 233 | (Some(lhs_arr), Some(rhs_arr)) => lhs_arr 234 | .iter() 235 | .all(|lhs| !rhs_arr.iter().any(|rhs| lhs == rhs)) 236 | .into(), 237 | _ => Self::null(), 238 | }, 239 | _ => Self::null(), 240 | }, 241 | "any_of" => match args.as_slice() { 242 | [lhs, rhs] => match (lhs.as_array(), rhs.as_array()) { 243 | (Some(lhs_arr), Some(rhs_arr)) => lhs_arr 244 | .iter() 245 | .any(|lhs| rhs_arr.iter().any(|rhs| lhs == rhs)) 246 | .into(), 247 | _ => Self::null(), 248 | }, 249 | _ => Self::null(), 250 | }, 251 | "subset_of" => match args.as_slice() { 252 | [lhs, rhs] => match (lhs.as_array(), rhs.as_array()) { 253 | (Some(lhs_arr), Some(rhs_arr)) => lhs_arr 254 | .iter() 255 | .all(|lhs| rhs_arr.iter().any(|rhs| lhs == rhs)) 256 | .into(), 257 | _ => Self::null(), 258 | }, 259 | _ => Self::null(), 260 | }, 261 | _ => Self::null(), 262 | } 263 | } 264 | 265 | fn reference(&self, path: T) -> Option<&Self> 266 | where 267 | T: Into, 268 | { 269 | convert_js_path(&path.into()) 270 | .ok() 271 | .and_then(|p| self.pointer(p.as_str())) 272 | } 273 | 274 | fn reference_mut(&mut self, path: T) -> Option<&mut Self> 275 | where 276 | T: Into, 277 | { 278 | convert_js_path(&path.into()) 279 | .ok() 280 | .and_then(|p| self.pointer_mut(p.as_str())) 281 | } 282 | } 283 | 284 | fn convert_js_path(path: &str) -> Parsed { 285 | let JpQuery { segments } = parse_json_path(path)?; 286 | 287 | let mut path = String::new(); 288 | for segment in segments { 289 | match segment { 290 | Segment::Selector(Selector::Name(name)) => { 291 | path.push_str(&format!("/{}", name.trim_matches(|c| c == '\''))); 292 | } 293 | Segment::Selector(Selector::Index(index)) => { 294 | path.push_str(&format!("/{}", index)); 295 | } 296 | s => { 297 | return Err(JsonPathError::InvalidJsonPath(format!( 298 | "Invalid segment: {:?}", 299 | s 300 | ))); 301 | } 302 | } 303 | } 304 | Ok(path) 305 | } 306 | 307 | #[cfg(test)] 308 | mod tests { 309 | use crate::parser::Parsed; 310 | use crate::query::queryable::{convert_js_path, Queryable}; 311 | use crate::query::Queried; 312 | use crate::JsonPath; 313 | use serde_json::json; 314 | 315 | #[test] 316 | fn in_smoke() -> Queried<()> { 317 | let json = json!({ 318 | "elems": ["test", "t1", "t2"], 319 | "list": ["test", "test2", "test3"], 320 | }); 321 | 322 | let res = json.query("$.elems[?in(@, $.list)]")?; 323 | 324 | assert_eq!(res, [&json!("test")]); 325 | 326 | Ok(()) 327 | } 328 | #[test] 329 | fn nin_smoke() -> Queried<()> { 330 | let json = json!({ 331 | "elems": ["test", "t1", "t2"], 332 | "list": ["test", "test2", "test3"], 333 | }); 334 | 335 | let res = json.query("$.elems[?nin(@, $.list)]")?; 336 | 337 | assert_eq!(res, [&json!("t1"), &json!("t2")]); 338 | 339 | Ok(()) 340 | } 341 | #[test] 342 | fn none_of_smoke() -> Queried<()> { 343 | let json = json!({ 344 | "elems": [ ["t1", "_"], ["t2", "t5"], ["t4"]], 345 | "list": ["t1","t2", "t3"], 346 | }); 347 | 348 | let res = json.query("$.elems[?none_of(@, $.list)]")?; 349 | 350 | assert_eq!(res, [&json!(["t4"])]); 351 | 352 | Ok(()) 353 | } 354 | #[test] 355 | fn any_of_smoke() -> Queried<()> { 356 | let json = json!({ 357 | "elems": [ ["t1", "_"], ["t4", "t5"], ["t4"]], 358 | "list": ["t1","t2", "t3"], 359 | }); 360 | 361 | let res = json.query("$.elems[?any_of(@, $.list)]")?; 362 | 363 | assert_eq!(res, [&json!(["t1", "_"])]); 364 | 365 | Ok(()) 366 | } 367 | #[test] 368 | fn subset_of_smoke() -> Queried<()> { 369 | let json = json!({ 370 | "elems": [ ["t1", "t2"], ["t4", "t5"], ["t6"]], 371 | "list": ["t1","t2", "t3"], 372 | }); 373 | 374 | let res = json.query("$.elems[?subset_of(@, $.list)]")?; 375 | 376 | assert_eq!(res, [&json!(["t1", "t2"])]); 377 | 378 | Ok(()) 379 | } 380 | 381 | #[test] 382 | fn convert_paths() -> Parsed<()> { 383 | let r = convert_js_path("$.a.b[2]")?; 384 | assert_eq!(r, "/a/b/2"); 385 | 386 | Ok(()) 387 | } 388 | 389 | #[test] 390 | fn test_references() -> Parsed<()> { 391 | let mut json = json!({ 392 | "a": { 393 | "b": { 394 | "c": 42 395 | } 396 | } 397 | }); 398 | 399 | let r = convert_js_path("$.a.b.c")?; 400 | 401 | if let Some(v) = json.pointer_mut(r.as_str()) { 402 | *v = json!(43); 403 | } 404 | 405 | assert_eq!( 406 | json, 407 | json!({ 408 | "a": { 409 | "b": { 410 | "c": 43 411 | } 412 | } 413 | }) 414 | ); 415 | 416 | Ok(()) 417 | } 418 | #[test] 419 | fn test_js_reference() -> Parsed<()> { 420 | let mut json = json!({ 421 | "a": { 422 | "b": { 423 | "c": 42 424 | } 425 | } 426 | }); 427 | 428 | if let Some(path) = json.query_only_path("$.a.b.c")?.first() { 429 | if let Some(v) = json.reference_mut(path) { 430 | *v = json!(43); 431 | } 432 | 433 | assert_eq!( 434 | json, 435 | json!({ 436 | "a": { 437 | "b": { 438 | "c": 43 439 | } 440 | } 441 | }) 442 | ); 443 | } else { 444 | panic!("no path found"); 445 | } 446 | 447 | Ok(()) 448 | } 449 | } 450 | -------------------------------------------------------------------------------- /src/query/segment.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::model::{Segment, Selector}; 2 | use crate::query::queryable::Queryable; 3 | use crate::query::state::{Data, Pointer, State}; 4 | use crate::query::Query; 5 | 6 | impl Query for Segment { 7 | fn process<'a, T: Queryable>(&self, step: State<'a, T>) -> State<'a, T> { 8 | match self { 9 | Segment::Descendant(segment) => segment.process(step.flat_map(process_descendant)), 10 | Segment::Selector(selector) => selector.process(step), 11 | Segment::Selectors(selectors) => process_selectors(step, selectors), 12 | } 13 | } 14 | } 15 | 16 | fn process_selectors<'a, T: Queryable>( 17 | step: State<'a, T>, 18 | selectors: &Vec, 19 | ) -> State<'a, T> { 20 | selectors 21 | .into_iter() 22 | .map(|s| s.process(step.clone())) 23 | .reduce(State::reduce) 24 | .unwrap_or(step.root.into()) 25 | } 26 | 27 | fn process_descendant(data: Pointer) -> Data { 28 | if let Some(array) = data.inner.as_array() { 29 | Data::Ref(data.clone()).reduce( 30 | Data::new_refs( 31 | array 32 | .iter() 33 | .enumerate() 34 | .map(|(i, elem)| Pointer::idx(elem, data.path.clone(), i)) 35 | .collect(), 36 | ) 37 | .flat_map(process_descendant), 38 | ) 39 | } else if let Some(object) = data.inner.as_object() { 40 | Data::Ref(data.clone()).reduce( 41 | Data::new_refs( 42 | object 43 | .into_iter() 44 | .map(|(key, value)| Pointer::key(value, data.path.clone(), key)) 45 | .collect(), 46 | ) 47 | .flat_map(process_descendant), 48 | ) 49 | } else { 50 | Data::Nothing 51 | } 52 | } 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use crate::parser::model::{Segment, Selector}; 57 | use crate::query::state::{Pointer, State}; 58 | use crate::query::Query; 59 | use serde_json::json; 60 | 61 | #[test] 62 | fn test_process_selectors() { 63 | let value = json!({"firstName": "John", "lastName" : "doe",}); 64 | let segment = Segment::Selectors(vec![ 65 | Selector::Name("firstName".to_string()), 66 | Selector::Name("lastName".to_string()), 67 | ]); 68 | let step = segment.process(State::root(&value)); 69 | 70 | assert_eq!( 71 | step.ok_ref(), 72 | Some(vec![ 73 | Pointer::new(&json!("John"), "$['firstName']".to_string()), 74 | Pointer::new(&json!("doe"), "$['lastName']".to_string()) 75 | ]) 76 | ); 77 | } 78 | 79 | #[test] 80 | fn test_process_descendant() { 81 | let value = json!([{"name": "John"}, {"name": "doe"}]); 82 | let segment = Segment::Descendant(Box::new(Segment::Selector(Selector::Wildcard))); 83 | let step = segment.process(State::root(&value)); 84 | 85 | assert_eq!( 86 | step.ok_ref(), 87 | Some(vec![ 88 | Pointer::new(&json!({"name": "John"}), "$[0]".to_string()), 89 | Pointer::new(&json!({"name": "doe"}), "$[1]".to_string()), 90 | Pointer::new(&json!("John"), "$[0]['name']".to_string()), 91 | Pointer::new(&json!("doe"), "$[1]['name']".to_string()), 92 | ]) 93 | ); 94 | } 95 | 96 | #[test] 97 | fn test_process_descendant2() { 98 | let value = json!({"o": [0,1,[2,3]]}); 99 | let segment = Segment::Descendant(Box::new(Segment::Selector(Selector::Index(1)))); 100 | let step = segment.process(State::root(&value)); 101 | 102 | assert_eq!( 103 | step.ok_ref(), 104 | Some(vec![ 105 | Pointer::new(&json!(1), "$['o'][1]".to_string()), 106 | Pointer::new(&json!(3), "$['o'][2][1]".to_string()), 107 | ]) 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/query/selector.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::model::Selector; 2 | use crate::query::queryable::Queryable; 3 | use crate::query::state::{Data, Pointer, State}; 4 | use crate::query::Query; 5 | use std::cmp::{max, min}; 6 | 7 | impl Query for Selector { 8 | fn process<'a, T: Queryable>(&self, step: State<'a, T>) -> State<'a, T> { 9 | match self { 10 | Selector::Name(key) => step.flat_map(|d| process_key(d, key)), 11 | Selector::Index(idx) => step.flat_map(|d| process_index(d, idx)), 12 | Selector::Wildcard => step.flat_map(process_wildcard), 13 | Selector::Slice(start, end, sl_step) => { 14 | step.flat_map(|d| process_slice(d, start, end, sl_step)) 15 | } 16 | Selector::Filter(f) => f.process(step), 17 | } 18 | } 19 | } 20 | 21 | fn process_wildcard( 22 | Pointer { 23 | inner: pointer, 24 | path, 25 | }: Pointer, 26 | ) -> Data { 27 | if let Some(array) = pointer.as_array() { 28 | if array.is_empty() { 29 | Data::Nothing 30 | } else { 31 | Data::new_refs( 32 | array 33 | .iter() 34 | .enumerate() 35 | .map(|(i, elem)| Pointer::idx(elem, path.clone(), i)) 36 | .collect(), 37 | ) 38 | } 39 | } else if let Some(object) = pointer.as_object() { 40 | if object.is_empty() { 41 | Data::Nothing 42 | } else { 43 | Data::new_refs( 44 | object 45 | .into_iter() 46 | .map(|(key, value)| Pointer::key(value, path.clone(), key)) 47 | .collect(), 48 | ) 49 | } 50 | } else { 51 | Data::Nothing 52 | } 53 | } 54 | 55 | fn process_slice<'a, T: Queryable>( 56 | Pointer { inner, path }: Pointer<'a, T>, 57 | start: &Option, 58 | end: &Option, 59 | step: &Option, 60 | ) -> Data<'a, T> { 61 | let extract_elems = |elements: &'a Vec| -> Vec<(&'a T, usize)> { 62 | let len = elements.len() as i64; 63 | let norm = |i: i64| { 64 | if i >= 0 { 65 | i 66 | } else { 67 | len + i 68 | } 69 | }; 70 | 71 | match step.unwrap_or(1) { 72 | e if e > 0 => { 73 | let n_start = norm(start.unwrap_or(0)); 74 | let n_end = norm(end.unwrap_or(len)); 75 | let lower = min(max(n_start, 0), len); 76 | let upper = min(max(n_end, 0), len); 77 | 78 | let mut idx = lower; 79 | let mut res = vec![]; 80 | while idx < upper { 81 | let i = idx as usize; 82 | if let Some(elem) = elements.get(i) { 83 | res.push((elem, i)); 84 | } 85 | idx += e; 86 | } 87 | res 88 | } 89 | e if e < 0 => { 90 | let n_start = norm(start.unwrap_or(len - 1)); 91 | let n_end = norm(end.unwrap_or(-len - 1)); 92 | let lower = min(max(n_end, -1), len - 1); 93 | let upper = min(max(n_start, -1), len - 1); 94 | let mut idx = upper; 95 | let mut res = vec![]; 96 | while lower < idx { 97 | let i = idx as usize; 98 | if let Some(elem) = elements.get(i) { 99 | res.push((elem, i)); 100 | } 101 | idx += e; 102 | } 103 | res 104 | } 105 | _ => vec![], 106 | } 107 | }; 108 | 109 | let elems_to_step = |v: Vec<(&'a T, usize)>| { 110 | Data::new_refs( 111 | v.into_iter() 112 | .map(|(elem, i)| Pointer::idx(elem, path.clone(), i)) 113 | .collect(), 114 | ) 115 | }; 116 | 117 | inner 118 | .as_array() 119 | .map(extract_elems) 120 | .map(elems_to_step) 121 | .unwrap_or_default() 122 | } 123 | 124 | /// Processes escape sequences in JSON strings 125 | /// - Replaces `\\` with `\` 126 | /// - Replaces `\/` with `/` 127 | /// - Preserves other valid escapes like `\"` and `\'` 128 | fn normalize_json_key(input: &str) -> String { 129 | let mut result = String::with_capacity(input.len()); 130 | let mut chars = input.chars().peekable(); 131 | 132 | while let Some(c) = chars.next() { 133 | if c == '\\' { 134 | if let Some(&next) = chars.peek() { 135 | match next { 136 | '\\' => { 137 | result.push('\\'); 138 | chars.next(); // consume the second backslash 139 | } 140 | '/' => { 141 | result.push('/'); 142 | chars.next(); // consume the forward slash 143 | } 144 | '\'' => { 145 | result.push('\\'); 146 | result.push('\''); 147 | chars.next(); // consume the quote 148 | } 149 | '"' => { 150 | result.push('\\'); 151 | result.push('"'); 152 | chars.next(); // consume the quote 153 | } 154 | 'b' | 'f' | 'n' | 'r' | 't' | 'u' => { 155 | // Preserve these standard JSON escape sequences 156 | result.push('\\'); 157 | result.push(next); 158 | chars.next(); 159 | } 160 | _ => { 161 | // Invalid escape - just keep as-is 162 | result.push('\\'); 163 | } 164 | } 165 | } else { 166 | // Trailing backslash 167 | result.push('\\'); 168 | } 169 | } else { 170 | result.push(c); 171 | } 172 | } 173 | result 174 | } 175 | 176 | pub fn process_key<'a, T: Queryable>( 177 | Pointer { inner, path }: Pointer<'a, T>, 178 | key: &str, 179 | ) -> Data<'a, T> { 180 | inner 181 | .get(normalize_json_key(key).as_str()) 182 | .map(|v| Data::new_ref(Pointer::key(v, path, key))) 183 | .unwrap_or_default() 184 | } 185 | 186 | pub fn process_index<'a, T: Queryable>( 187 | Pointer { inner, path }: Pointer<'a, T>, 188 | idx: &i64, 189 | ) -> Data<'a, T> { 190 | inner 191 | .as_array() 192 | .map(|array| { 193 | if *idx >= 0 { 194 | if *idx >= array.len() as i64 { 195 | Data::Nothing 196 | } else { 197 | let i = *idx as usize; 198 | Data::new_ref(Pointer::idx(&array[i], path, i)) 199 | } 200 | } else { 201 | let abs_idx = idx.abs() as usize; 202 | if abs_idx > array.len() { 203 | Data::Nothing 204 | } else { 205 | let i = array.len() - abs_idx; 206 | Data::new_ref(Pointer::idx(&array[i], path, i)) 207 | } 208 | } 209 | }) 210 | .unwrap_or_default() 211 | } 212 | 213 | #[cfg(test)] 214 | mod tests { 215 | use super::*; 216 | use crate::parser::model::Segment; 217 | use crate::query::{js_path, Queried}; 218 | use serde_json::json; 219 | use std::vec; 220 | #[test] 221 | fn test_process_empty_key() { 222 | let value = json!({" ": "value"}); 223 | let segment = Segment::Selector(Selector::Name(" ".to_string())); 224 | 225 | let step = segment.process(State::root(&value)); 226 | 227 | assert_eq!( 228 | step.ok_ref(), 229 | Some(vec![Pointer::new(&json!("value"), "$[' ']".to_string())]) 230 | ); 231 | } 232 | #[test] 233 | fn test_process_key() { 234 | let value = json!({"key": "value"}); 235 | let segment = Segment::Selector(Selector::Name("key".to_string())); 236 | 237 | let step = segment.process(State::root(&value)); 238 | 239 | assert_eq!( 240 | step.ok_ref(), 241 | Some(vec![Pointer::new(&json!("value"), "$['key']".to_string())]) 242 | ); 243 | } 244 | 245 | #[test] 246 | fn test_process_key_failed() { 247 | let value = json!({"key": "value"}); 248 | let segment = Segment::Selector(Selector::Name("key2".to_string())); 249 | let step = segment.process(State::root(&value)); 250 | 251 | assert_eq!(step, State::nothing(&value)); 252 | } 253 | 254 | #[test] 255 | fn test_process_index() { 256 | let value = json!([1, 2, 3]); 257 | let segment = Segment::Selector(Selector::Index(1)); 258 | let step = segment.process(State::root(&value)); 259 | 260 | assert_eq!( 261 | step.ok_ref(), 262 | Some(vec![Pointer::new(&json!(2), "$[1]".to_string())]) 263 | ); 264 | } 265 | 266 | #[test] 267 | fn test_process_index_failed() { 268 | let value = json!([1, 2, 3]); 269 | let segment = Segment::Selector(Selector::Index(3)); 270 | let step = segment.process(State::root(&value)); 271 | 272 | assert_eq!(step, State::nothing(&value)); 273 | } 274 | 275 | #[test] 276 | fn test_process_slice1() { 277 | let value = json!([1, 2, 3, 4, 5]); 278 | let segment = Segment::Selector(Selector::Slice(Some(1), Some(4), Some(1))); 279 | let step = segment.process(State::root(&value)); 280 | 281 | assert_eq!( 282 | step.ok_ref(), 283 | Some(vec![ 284 | Pointer::new(&json!(2), "$[1]".to_string()), 285 | Pointer::new(&json!(3), "$[2]".to_string()), 286 | Pointer::new(&json!(4), "$[3]".to_string()) 287 | ]) 288 | ); 289 | } 290 | 291 | #[test] 292 | fn test_process_slice2() { 293 | let value = json!([1, 2, 3, 4, 5]); 294 | let segment = Segment::Selector(Selector::Slice(Some(2), Some(0), Some(-1))); 295 | let step = segment.process(State::root(&value)); 296 | 297 | assert_eq!( 298 | step.ok_ref(), 299 | Some(vec![ 300 | Pointer::new(&json!(3), "$[2]".to_string()), 301 | Pointer::new(&json!(2), "$[1]".to_string()), 302 | ]) 303 | ); 304 | } 305 | 306 | #[test] 307 | fn test_process_slice3() { 308 | let value = json!([1, 2, 3, 4, 5]); 309 | let segment = Segment::Selector(Selector::Slice(Some(0), Some(5), Some(2))); 310 | let step = segment.process(State::root(&value)); 311 | 312 | assert_eq!( 313 | step.ok_ref(), 314 | Some(vec![ 315 | Pointer::new(&json!(1), "$[0]".to_string()), 316 | Pointer::new(&json!(3), "$[2]".to_string()), 317 | Pointer::new(&json!(5), "$[4]".to_string()) 318 | ]) 319 | ); 320 | } 321 | 322 | #[test] 323 | fn test_process_slice_failed() { 324 | let value = json!([1, 2, 3, 4, 5]); 325 | let segment = Segment::Selector(Selector::Slice(Some(0), Some(5), Some(0))); 326 | let step = segment.process(State::root(&value)); 327 | 328 | assert_eq!(step.ok_ref(), Some(vec![])); 329 | } 330 | 331 | #[test] 332 | fn test_process_wildcard() { 333 | let value = json!({"key": "value", "key2": "value2"}); 334 | let segment = Segment::Selector(Selector::Wildcard); 335 | let step = segment.process(State::root(&value)); 336 | 337 | assert_eq!( 338 | step.ok_ref(), 339 | Some(vec![ 340 | Pointer::new(&json!("value"), "$['key']".to_string()), 341 | Pointer::new(&json!("value2"), "$['key2']".to_string()) 342 | ]) 343 | ); 344 | } 345 | 346 | #[test] 347 | fn test_process_wildcard_array() { 348 | let value = json!([1, 2, 3]); 349 | let segment = Segment::Selector(Selector::Wildcard); 350 | let step = segment.process(State::root(&value)); 351 | 352 | assert_eq!( 353 | step.ok_ref(), 354 | Some(vec![ 355 | Pointer::new(&json!(1), "$[0]".to_string()), 356 | Pointer::new(&json!(2), "$[1]".to_string()), 357 | Pointer::new(&json!(3), "$[2]".to_string()) 358 | ]) 359 | ); 360 | } 361 | 362 | #[test] 363 | fn test_process_wildcard_failed() { 364 | let value = json!(1); 365 | let segment = Segment::Selector(Selector::Wildcard); 366 | let step = segment.process(State::root(&value)); 367 | 368 | assert_eq!(step, State::nothing(&value)); 369 | } 370 | 371 | #[test] 372 | fn multi_selector() -> Queried<()> { 373 | let json = json!([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); 374 | 375 | let vec = js_path("$['a',1]", &json)?; 376 | 377 | assert_eq!(vec, vec![(&json!(1), "$[1]".to_string()).into(),]); 378 | 379 | Ok(()) 380 | } 381 | #[test] 382 | fn multi_selector_space() -> Queried<()> { 383 | let json = json!({ 384 | "a": "ab", 385 | "b": "bc" 386 | }); 387 | 388 | let vec = js_path("$['a',\r'b']", &json)?; 389 | 390 | assert_eq!( 391 | vec, 392 | vec![ 393 | (&json!("ab"), "$['a']".to_string()).into(), 394 | (&json!("bc"), "$['b']".to_string()).into(), 395 | ] 396 | ); 397 | 398 | Ok(()) 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /src/query/state.rs: -------------------------------------------------------------------------------- 1 | use crate::query::queryable::Queryable; 2 | use crate::query::QueryPath; 3 | use std::fmt::{Display, Formatter}; 4 | 5 | /// Represents the state of a query, including the current data and the root object. 6 | /// It is used to track the progress of a query as it traverses through the data structure. 7 | #[derive(Debug, Clone, PartialEq)] 8 | pub struct State<'a, T: Queryable> { 9 | pub data: Data<'a, T>, 10 | pub root: &'a T, 11 | } 12 | 13 | impl<'a, T: Queryable> Display for State<'a, T> { 14 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 15 | write!(f, "{}", self.data) 16 | } 17 | } 18 | 19 | impl<'a, T: Queryable> From<&'a T> for State<'a, T> { 20 | fn from(root: &'a T) -> Self { 21 | State::root(root) 22 | } 23 | } 24 | 25 | impl<'a, T: Queryable> State<'a, T> { 26 | pub fn bool(b: bool, root: &T) -> State { 27 | State::data(root, Data::Value(b.into())) 28 | } 29 | 30 | pub fn i64(i: i64, root: &T) -> State { 31 | State::data(root, Data::Value(i.into())) 32 | } 33 | pub fn str(v: &str, root: &'a T) -> State<'a, T> { 34 | State::data(root, Data::Value(v.into())) 35 | } 36 | 37 | pub fn shift_to_root(self) -> State<'a, T> { 38 | State::root(self.root) 39 | } 40 | 41 | pub fn root(root: &'a T) -> Self { 42 | State { 43 | root, 44 | data: Data::new_ref(Pointer::new(root, "$".to_string())), 45 | } 46 | } 47 | 48 | pub fn nothing(root: &'a T) -> Self { 49 | State { 50 | root, 51 | data: Data::Nothing, 52 | } 53 | } 54 | 55 | pub fn data(root: &'a T, data: Data<'a, T>) -> Self { 56 | State { root, data } 57 | } 58 | 59 | pub fn ok_ref(self) -> Option>> { 60 | self.data.ok_ref() 61 | } 62 | 63 | pub fn ok_val(self) -> Option { 64 | match self.data { 65 | Data::Value(v) => Some(v), 66 | _ => None, 67 | } 68 | } 69 | 70 | pub fn is_nothing(&self) -> bool { 71 | matches!(&self.data, Data::Nothing) 72 | } 73 | 74 | pub fn reduce(self, other: State<'a, T>) -> State<'a, T> { 75 | State { 76 | root: self.root, 77 | data: self.data.reduce(other.data), 78 | } 79 | } 80 | pub fn flat_map(self, f: F) -> State<'a, T> 81 | where 82 | F: Fn(Pointer<'a, T>) -> Data<'a, T>, 83 | { 84 | State { 85 | root: self.root, 86 | data: self.data.flat_map(f), 87 | } 88 | } 89 | } 90 | 91 | /// Represents the data that is being processed in the query. 92 | /// It can be a reference to a single object, a collection of references, 93 | #[derive(Debug, Clone, PartialEq)] 94 | pub enum Data<'a, T: Queryable> { 95 | Ref(Pointer<'a, T>), 96 | Refs(Vec>), 97 | Value(T), 98 | Nothing, 99 | } 100 | 101 | impl<'a, T: Queryable> Display for Data<'a, T> { 102 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 103 | match self { 104 | Data::Ref(p) => write!(f, "&{}", p), 105 | Data::Refs(p) => write!( 106 | f, 107 | "{}", 108 | p.iter() 109 | .map(|ptr| ptr.to_string()) 110 | .collect::>() 111 | .join("; ") 112 | ), 113 | Data::Value(v) => write!(f, "{:?}", v), 114 | Data::Nothing => write!(f, "Nothing"), 115 | } 116 | } 117 | } 118 | 119 | impl<'a, T: Queryable> Default for Data<'a, T> { 120 | fn default() -> Self { 121 | Data::Nothing 122 | } 123 | } 124 | 125 | impl<'a, T: Queryable> Data<'a, T> { 126 | pub fn reduce(self, other: Data<'a, T>) -> Data<'a, T> { 127 | match (self, other) { 128 | (Data::Ref(data), Data::Ref(data2)) => Data::Refs(vec![data, data2]), 129 | (Data::Ref(data), Data::Refs(data_vec)) => { 130 | Data::Refs(vec![data].into_iter().chain(data_vec).collect()) 131 | } 132 | (Data::Refs(data_vec), Data::Ref(data)) => { 133 | Data::Refs(data_vec.into_iter().chain(vec![data]).collect()) 134 | } 135 | (Data::Refs(data_vec), Data::Refs(data_vec2)) => { 136 | Data::Refs(data_vec.into_iter().chain(data_vec2).collect()) 137 | } 138 | (d @ (Data::Ref(_) | Data::Refs(..)), Data::Nothing) => d, 139 | (Data::Nothing, d @ (Data::Ref(_) | Data::Refs(..))) => d, 140 | _ => Data::Nothing, 141 | } 142 | } 143 | 144 | pub fn flat_map(self, f: F) -> Data<'a, T> 145 | where 146 | F: Fn(Pointer<'a, T>) -> Data<'a, T>, 147 | { 148 | match self { 149 | Data::Ref(data) => f(data), 150 | Data::Refs(data_vec) => Data::Refs( 151 | data_vec 152 | .into_iter() 153 | .flat_map(|data| match f(data) { 154 | Data::Ref(data) => vec![data], 155 | Data::Refs(data_vec) => data_vec, 156 | _ => vec![], 157 | }) 158 | .collect::>(), 159 | ), 160 | _ => Data::Nothing, 161 | } 162 | } 163 | 164 | /// Returns the inner value if it is a single reference. 165 | /// If it is a collection of references, it returns the first one. 166 | /// If it is a value, it returns None. 167 | pub fn ok_ref(self) -> Option>> { 168 | match self { 169 | Data::Ref(data) => Some(vec![data]), 170 | Data::Refs(data) => Some(data), 171 | _ => None, 172 | } 173 | } 174 | 175 | /// Returns the inner value if it is a single value. 176 | /// If it is a reference or a collection of references, it returns None. 177 | pub fn ok_val(self) -> Option { 178 | match self { 179 | Data::Value(v) => Some(v), 180 | _ => None, 181 | } 182 | } 183 | 184 | pub fn new_ref(data: Pointer<'a, T>) -> Data<'a, T> { 185 | Data::Ref(data) 186 | } 187 | 188 | pub fn new_refs(data: Vec>) -> Data<'a, T> { 189 | Data::Refs(data) 190 | } 191 | } 192 | 193 | /// Represents a pointer to a specific location in the data structure. 194 | /// It contains a reference to the data and a path that indicates the location of the data in the structure. 195 | #[derive(Debug, Clone, PartialEq)] 196 | pub(crate) struct Pointer<'a, T: Queryable> { 197 | pub inner: &'a T, 198 | pub path: QueryPath, 199 | } 200 | 201 | impl<'a, T: Queryable> Display for Pointer<'a, T> { 202 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 203 | write!(f, "{:?}='{}'", self.inner, self.path) 204 | } 205 | } 206 | 207 | impl<'a, T: Queryable> Pointer<'a, T> { 208 | pub fn new(inner: &'a T, path: QueryPath) -> Self { 209 | Pointer { inner, path } 210 | } 211 | 212 | pub fn key(inner: &'a T, path: QueryPath, key: &str) -> Self { 213 | let path = if key.starts_with("'") && key.ends_with("'") { 214 | format!("{}[{}]", path, key) 215 | } else { 216 | format!("{}['{}']", path, key) 217 | }; 218 | 219 | Pointer { inner, path } 220 | } 221 | pub fn idx(inner: &'a T, path: QueryPath, index: usize) -> Self { 222 | Pointer { 223 | inner, 224 | path: format!("{}[{}]", path, index), 225 | } 226 | } 227 | 228 | pub fn empty(inner: &'a T) -> Self { 229 | Pointer { 230 | inner, 231 | path: String::new(), 232 | } 233 | } 234 | 235 | pub fn is_internal(&self) -> bool { 236 | self.path.is_empty() 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/query/test.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::model::Test; 2 | use crate::query::queryable::Queryable; 3 | use crate::query::state::State; 4 | use crate::query::Query; 5 | 6 | impl Query for Test { 7 | fn process<'a, T: Queryable>(&self, state: State<'a, T>) -> State<'a, T> { 8 | match self { 9 | Test::RelQuery(segments) => segments.process(state), 10 | Test::AbsQuery(jquery) => jquery.process(state.shift_to_root()), 11 | Test::Function(tf) => tf.process(state), 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/query/test_function.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::model::{FnArg, TestFunction}; 2 | use crate::query::queryable::Queryable; 3 | use crate::query::state::{Data, Pointer, State}; 4 | use crate::query::Query; 5 | use regex::Regex; 6 | use std::borrow::Cow; 7 | 8 | impl TestFunction { 9 | pub fn apply<'a, T: Queryable>(&self, state: State<'a, T>) -> State<'a, T> { 10 | match self { 11 | TestFunction::Length(arg) => length(arg.process(state)), 12 | TestFunction::Count(arg) => count(arg.process(state)), 13 | TestFunction::Match(lhs, rhs) => { 14 | regex(lhs.process(state.clone()), rhs.process(state), false) 15 | } 16 | TestFunction::Search(lhs, rhs) => { 17 | regex(lhs.process(state.clone()), rhs.process(state), true) 18 | } 19 | TestFunction::Custom(name, args) => custom(name, args, state), 20 | TestFunction::Value(arg) => value(arg.process(state)), 21 | _ => State::nothing(state.root), 22 | } 23 | } 24 | } 25 | 26 | impl Query for FnArg { 27 | fn process<'a, T: Queryable>(&self, step: State<'a, T>) -> State<'a, T> { 28 | match self { 29 | FnArg::Literal(lit) => lit.process(step), 30 | FnArg::Test(test) => test.process(step), 31 | FnArg::Filter(filter) => filter.process(step), 32 | } 33 | } 34 | } 35 | 36 | impl Query for TestFunction { 37 | fn process<'a, T: Queryable>(&self, step: State<'a, T>) -> State<'a, T> { 38 | self.apply(step) 39 | } 40 | } 41 | 42 | fn custom<'a, T: Queryable>(name: &str, args: &Vec, state: State<'a, T>) -> State<'a, T> { 43 | let args = args 44 | .into_iter() 45 | .map(|v| v.process(state.clone())) 46 | .flat_map(|v| match v.data { 47 | Data::Value(v) => vec![Cow::Owned(v)], 48 | Data::Ref(Pointer { inner, .. }) => vec![Cow::Borrowed(inner)], 49 | Data::Refs(v) => v.into_iter().map(|v| Cow::Borrowed(v.inner)).collect(), 50 | _ => vec![], 51 | }) 52 | .collect::>(); 53 | 54 | State::data( 55 | state.root, 56 | Data::Value(Queryable::extension_custom(name, args)), 57 | ) 58 | } 59 | 60 | /// Returns the length/size of the object. 61 | /// 62 | /// # Returns 63 | /// 64 | /// Returns a `Progress` enum containing either: 65 | /// - `Progress::Data` with a vector of references to self and the query path for strings/arrays/objects 66 | /// - `Progress::Nothing` for other types 67 | /// 68 | /// The returned length follows JSON path length() function semantics based on the type: 69 | /// - String type: Number of Unicode scalar values 70 | /// - Array type: Number of elements 71 | /// - Object type: Number of members 72 | /// - Other types: Nothing 73 | fn length(state: State) -> State { 74 | let from_item = |item: &T| { 75 | if let Some(v) = item.as_str() { 76 | State::i64(v.chars().count() as i64, state.root) 77 | } else if let Some(items) = item.as_array() { 78 | State::i64(items.len() as i64, state.root) 79 | } else if let Some(items) = item.as_object() { 80 | State::i64(items.len() as i64, state.root) 81 | } else { 82 | State::nothing(state.root) 83 | } 84 | }; 85 | 86 | match state.data { 87 | Data::Ref(Pointer { inner, .. }) => from_item(inner), 88 | Data::Refs(items) => State::i64(items.len() as i64, state.root), 89 | Data::Value(item) => from_item(&item), 90 | Data::Nothing => State::nothing(state.root), 91 | } 92 | } 93 | 94 | /// The count() function extension provides a way 95 | /// to obtain the number of nodes in a nodelist 96 | /// and make that available for further processing in the filter expression 97 | fn count(state: State) -> State { 98 | let to_state = |count: i64| State::i64(count, state.root); 99 | 100 | match state.data { 101 | Data::Ref(..) | Data::Value(..) => to_state(1), 102 | Data::Refs(items) => to_state(items.len() as i64), 103 | Data::Nothing => State::nothing(state.root), 104 | } 105 | } 106 | /// The match() function extension provides 107 | /// a way to check whether (the entirety of; see Section 2.4.7) 108 | /// a given string matches a given regular expression, 109 | /// which is in the form described in [RFC9485]. 110 | /// 111 | /// Its arguments are instances of ValueType 112 | /// (possibly taken from a singular query, 113 | /// as for the first argument in the example above). 114 | /// If the first argument is not a string 115 | /// or the second argument is not a string conforming to [RFC9485], 116 | /// the result is LogicalFalse. Otherwise, the string that is the first argument is matched against 117 | /// the I-Regexp contained in the string that is the second argument; the result is LogicalTrue 118 | /// if the string matches the I-Regexp and is LogicalFalse otherwise. 119 | fn regex<'a, T: Queryable>(lhs: State<'a, T>, rhs: State<'a, T>, substr: bool) -> State<'a, T> { 120 | let to_state = |b| State::bool(b, lhs.root); 121 | let regex = |v: &str, r: Regex| { 122 | if substr { 123 | r.find(v).is_some() 124 | } else { 125 | r.is_match(v) 126 | } 127 | }; 128 | let to_str = |s: State<'a, T>| match s.data { 129 | Data::Value(v) => v.as_str().map(|s| s.to_string()), 130 | Data::Ref(Pointer { inner, .. }) => inner.as_str().map(|s| s.to_string()), 131 | _ => None, 132 | }; 133 | 134 | match (to_str(lhs), to_str(rhs)) { 135 | (Some(lhs), Some(rhs)) => Regex::new(&prepare_regex(rhs, substr)) 136 | .map(|re| to_state(regex(&lhs, re))) 137 | .unwrap_or(to_state(false)), 138 | _ => to_state(false), 139 | } 140 | } 141 | 142 | fn prepare_regex(pattern: String, substring: bool) -> String { 143 | let pattern = if !substring { 144 | let pattern = if pattern.starts_with('^') { 145 | pattern 146 | } else { 147 | format!("^{}", pattern) 148 | }; 149 | let pattern = if pattern.ends_with('$') { 150 | pattern 151 | } else { 152 | format!("{}$", pattern) 153 | }; 154 | pattern 155 | } else { 156 | pattern.to_string() 157 | }; 158 | let pattern = if pattern.contains("\\\\") { 159 | pattern.replace("\\\\", "\\") 160 | } else { 161 | pattern.to_string() 162 | }; 163 | 164 | pattern.trim_matches(|c| c == '\'' || c == '"').to_string() 165 | } 166 | 167 | fn value(state: State) -> State { 168 | match state.data { 169 | Data::Ref(..) | Data::Value(..) => state, 170 | Data::Refs(items) if items.len() == 1 => { 171 | State::data(state.root, Data::Ref(items[0].clone())) 172 | } 173 | _ => State::nothing(state.root), 174 | } 175 | } 176 | 177 | #[cfg(test)] 178 | mod tests { 179 | use crate::parser::model::Segment; 180 | use crate::parser::model::Selector; 181 | use crate::parser::model::Test; 182 | use crate::parser::model::TestFunction; 183 | use crate::query::state::{Data, Pointer, State}; 184 | use crate::query::test_function::{regex, FnArg}; 185 | use crate::query::Query; 186 | use crate::{arg, q_segment, segment, selector, test, test_fn}; 187 | use serde_json::json; 188 | 189 | #[test] 190 | fn test_len() { 191 | let json = json!({"array": [1,2,3]}); 192 | let state = State::root(&json); 193 | 194 | let query = test_fn!(length arg!(t test!(@ segment!(selector!(array))))); 195 | let res = query.process(state); 196 | 197 | assert_eq!(res.ok_val(), Some(json!(3))); 198 | } 199 | 200 | #[test] 201 | fn test_match_1() { 202 | let json = json!({"a": "abc sdgfudsf","b": "abc.*"}); 203 | let state = State::root(&json); 204 | 205 | let query = test_fn!(match 206 | arg!(t test!(@ segment!(selector!(a)))), 207 | arg!(t test!(@ segment!(selector!(b)))) 208 | ); 209 | let res = query.process(state); 210 | 211 | assert_eq!(res.ok_val(), Some(json!(true))); 212 | } 213 | 214 | #[test] 215 | fn test_count_1() { 216 | let json = json!({"array": [1,2,3]}); 217 | let state = State::root(&json); 218 | 219 | let query = test_fn!(count arg!(t test!(@ segment!(selector!(array))))); 220 | let res = query.process(state); 221 | 222 | assert_eq!(res.ok_val(), Some(json!(1))); 223 | } 224 | 225 | #[test] 226 | fn test_search() { 227 | let json = json!("123"); 228 | let state = State::root(&json); 229 | let reg = State::str("[a-z]+", &json); 230 | 231 | let res = regex(state, reg, true); 232 | 233 | assert_eq!(res.ok_val(), Some(json!(false))); 234 | } 235 | 236 | #[test] 237 | fn test_match() { 238 | let json = json!("bbab"); 239 | let state = State::root(&json); 240 | let reg = State::str("^b.?b$", &json); 241 | 242 | let res = regex(state, reg, false); 243 | 244 | assert_eq!(res.ok_val(), Some(json!(false))); 245 | } 246 | } 247 | --------------------------------------------------------------------------------