diff --git a/Cargo.lock b/Cargo.lock index 40c439c..7831154 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,7 +120,7 @@ dependencies = [ [[package]] name = "jsonpath_rust_bindings" -version = "1.1.0" +version = "1.1.1" dependencies = [ "jsonpath-rust", "mimalloc", diff --git a/Cargo.toml b/Cargo.toml index 2173df1..4655a59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jsonpath_rust_bindings" -version = "1.1.0" +version = "1.1.1" edition = "2021" [lib] diff --git a/README.md b/README.md index 45addd2..35d1be4 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ queries = [ '$..book[2:]', '$.store.book[?(@.price<10)]', '$..book[?(@.price<=$.expensive)]', - "$..book[?(@.author ~= '.*Rees')].price", + '$..book[?match(@.author, "(?i).*rees.*")].price', '$..*', ] diff --git a/src/lib.rs b/src/lib.rs index 6c63660..31b3eb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,43 +61,35 @@ impl Finder { // Execute JSONPath query, return list of results containing data and paths fn find(self_: PyRef<'_, Self>, query: String) -> PyResult> { - find_internal_path_value(&self_.value, &query, |_| true) + find_path_value_internal(&self_.value, &query) } // Execute JSONPath query, return only found data values fn find_data(self_: PyRef<'_, Self>, query: String) -> PyResult>> { - find_internal_data(&self_.value, &query, |_| true) + find_data_internal(&self_.value, &query) } // Execute JSONPath query, return only found absolute paths fn find_absolute_path(self_: PyRef<'_, Self>, query: String) -> PyResult> { - find_internal_path(&self_.value, &query, |_| true) + find_paths_internal(&self_.value, &query) } } // Execute JSONPath query and return processed results -fn execute_query<'a>( - value: &'a Value, - query: &str, - predicate: impl Fn(&QueryRef) -> bool, -) -> PyResult>> { +fn execute_query<'a>(value: &'a Value, query: &str) -> PyResult>> { let parsed_query = parse_query(query)?; let processed = js_path_process(&parsed_query, value) .map_err(|err| PyValueError::new_err(err.to_string()))?; - Ok(processed.into_iter().filter(predicate).collect()) + Ok(processed.into_iter().collect()) } // Execute query and return JsonPathResult list -fn find_internal_path_value( - value: &Value, - query: &str, - predicate: impl Fn(&QueryRef) -> bool, -) -> PyResult> { - let filtered = execute_query(value, query, predicate)?; +fn find_path_value_internal(value: &Value, query: &str) -> PyResult> { + let result = execute_query(value, query)?; Python::attach(|py| { - filtered + result .into_iter() .map(|v| map_json_path_value(py, v)) .collect() @@ -105,29 +97,17 @@ fn find_internal_path_value( } // Execute query and return data value list -fn find_internal_data( - value: &Value, - query: &str, - predicate: impl Fn(&QueryRef) -> bool, -) -> PyResult>> { - let filtered = execute_query(value, query, predicate)?; - - Python::attach(|py| { - filtered - .into_iter() - .map(|v| map_json_value(py, v)) - .collect() - }) +fn find_data_internal(value: &Value, query: &str) -> PyResult>> { + let result = execute_query(value, query)?; + Python::attach(|py| result.into_iter().map(|v| map_json_value(py, v)).collect()) } // Execute query and return path string list -fn find_internal_path( - value: &Value, - query: &str, - predicate: impl Fn(&QueryRef) -> bool, -) -> PyResult> { - let filtered = execute_query(value, query, predicate)?; - filtered.into_iter().map(|v| map_json_path(v)).collect() +fn find_paths_internal(value: &Value, query: &str) -> PyResult> { + Ok(execute_query(value, query)? + .into_iter() + .map(|v| v.path()) + .collect::>()) } // Map QueryRef to JsonPathResult @@ -141,11 +121,6 @@ fn map_json_path_value(py: Python, jpv: QueryRef) -> PyResult -fn map_json_path(jpv: QueryRef) -> PyResult { - Ok(jpv.path()) -} - // Convert value in QueryRef to Python object fn map_json_value(py: Python, jpv: QueryRef) -> PyResult> { let val = jpv.val(); diff --git a/tests/test_bindings.py b/tests/test_bindings.py index a91ac0b..f0b908a 100644 --- a/tests/test_bindings.py +++ b/tests/test_bindings.py @@ -64,137 +64,71 @@ def test_repr(sample_data): ) -def test_all_queries(sample_data): - queries = [ - "$.store.book[*].author", - "$..book[?(@.isbn)]", - "$.store.*", - "$..author", - "$.store..price", - "$..book[2]", - "$..book[-2]", - "$..book[0,1]", - "$..book[:2]", - "$..book[1:2]", - "$..book[-2:]", - "$..book[2:]", - "$.store.book[?(@.price<10)]", - "$..book[?(@.price<=$.expensive)]", - # "$..book[?@.author ~= '(?i)REES']", - "$..*", - ] - queries_results = [ - """[JsonPathResult(data='Nigel Rees', path="$['store']['book'][0]['author']"), -JsonPathResult(data='Evelyn Waugh', path="$['store']['book'][1]['author']"), -JsonPathResult(data='Herman Melville', path="$['store']['book'][2]['author']"), -JsonPathResult(data='J. R. R. Tolkien', path="$['store']['book'][3]['author']")]""", - """[JsonPathResult(data={'author': 'Herman Melville', 'category': 'fiction', 'isbn': '0-553-21311-3', 'price': 8.99, 'title': 'Moby Dick'}, -path="$['store']['book'][2]"), -JsonPathResult(data={'author': 'J. R. R. Tolkien', 'category': 'fiction', 'isbn': '0-395-19395-8', 'price': 22.99, 'title': 'The Lord of the Rings'}, -path="$['store']['book'][3]")]""", - """[JsonPathResult(data={'color': 'red', 'price': 19.95}, path="$['store']['bicycle']"), -JsonPathResult(data=[{'author': 'Nigel Rees', 'category': 'reference', 'price': 8.95, 'title': 'Sayings of the Century'}, -{'author': 'Evelyn Waugh', 'category': 'fiction', 'price': 12.99, 'title': 'Sword of Honour'}, -{'author': 'Herman Melville', 'category': 'fiction', 'isbn': '0-553-21311-3', 'price': 8.99, 'title': 'Moby Dick'}, -{'author': 'J. R. R. Tolkien', 'category': 'fiction', 'isbn': '0-395-19395-8', 'price': 22.99, 'title': 'The Lord of the Rings'}], -path="$['store']['book']")]""", - """[JsonPathResult(data='Nigel Rees', path="$['store']['book'][0]['author']"), -JsonPathResult(data='Evelyn Waugh', path="$['store']['book'][1]['author']"), -JsonPathResult(data='Herman Melville', path="$['store']['book'][2]['author']"), -JsonPathResult(data='J. R. R. Tolkien', path="$['store']['book'][3]['author']")]""", - """[JsonPathResult(data=19.95, path="$['store']['bicycle']['price']"), -JsonPathResult(data=8.95, path="$['store']['book'][0]['price']"), -JsonPathResult(data=12.99, path="$['store']['book'][1]['price']"), -JsonPathResult(data=8.99, path="$['store']['book'][2]['price']"), -JsonPathResult(data=22.99, path="$['store']['book'][3]['price']")]""", - """[JsonPathResult(data={'author': 'Herman Melville', 'category': 'fiction', 'isbn': '0-553-21311-3', 'price': 8.99, 'title': 'Moby Dick'}, -path="$['store']['book'][2]")]""", - """[JsonPathResult(data={'author': 'Herman Melville', 'category': 'fiction', 'isbn': '0-553-21311-3', 'price': 8.99, 'title': 'Moby Dick'}, -path="$['store']['book'][2]")]""", - """[JsonPathResult(data={'author': 'Nigel Rees', 'category': 'reference', 'price': 8.95, 'title': 'Sayings of the Century'}, -path="$['store']['book'][0]"), -JsonPathResult(data={'author': 'Evelyn Waugh', 'category': 'fiction', 'price': 12.99, 'title': 'Sword of Honour'}, path="$['store']['book'][1]")]""", - """[JsonPathResult(data={'author': 'Nigel Rees', 'category': 'reference', 'price': 8.95, 'title': 'Sayings of the Century'}, -path="$['store']['book'][0]"), -JsonPathResult(data={'author': 'Evelyn Waugh', 'category': 'fiction', 'price': 12.99, 'title': 'Sword of Honour'}, path="$['store']['book'][1]")]""", - """[JsonPathResult(data={'author': 'Evelyn Waugh', 'category': 'fiction', 'price': 12.99, 'title': 'Sword of Honour'}, -path="$['store']['book'][1]")]""", - """[JsonPathResult(data={'author': 'Herman Melville', 'category': 'fiction', 'isbn': '0-553-21311-3', 'price': 8.99, 'title': 'Moby Dick'}, -path="$['store']['book'][2]"), -JsonPathResult(data={'author': 'J. R. R. Tolkien', 'category': 'fiction', 'isbn': '0-395-19395-8', 'price': 22.99, 'title': 'The Lord of the Rings'}, -path="$['store']['book'][3]")]""", - """[JsonPathResult(data={'author': 'Herman Melville', 'category': 'fiction', 'isbn': '0-553-21311-3', 'price': 8.99, 'title': 'Moby Dick'}, -path="$['store']['book'][2]"), -JsonPathResult(data={'author': 'J. R. R. Tolkien', 'category': 'fiction', 'isbn': '0-395-19395-8', 'price': 22.99, 'title': 'The Lord of the Rings'}, -path="$['store']['book'][3]")]""", - """[JsonPathResult(data={'author': 'Nigel Rees', 'category': 'reference', 'price': 8.95, 'title': 'Sayings of the Century'}, -path="$['store']['book'][0]"), -JsonPathResult(data={'author': 'Herman Melville', 'category': 'fiction', 'isbn': '0-553-21311-3', 'price': 8.99, 'title': 'Moby Dick'}, -path="$['store']['book'][2]")]""", - """[JsonPathResult(data={'author': 'Nigel Rees', 'category': 'reference', 'price': 8.95, 'title': 'Sayings of the Century'}, -path="$['store']['book'][0]"), -JsonPathResult(data={'author': 'Herman Melville', 'category': 'fiction', 'isbn': '0-553-21311-3', 'price': 8.99, 'title': 'Moby Dick'}, -path="$['store']['book'][2]")]""", - """[JsonPathResult(data=10, path="$['expensive']"), -JsonPathResult(data={'bicycle': {'color': 'red', 'price': 19.95}, -'book': [{'author': 'Nigel Rees', 'category': 'reference', 'price': 8.95, 'title': 'Sayings of the Century'}, -{'author': 'Evelyn Waugh', 'category': 'fiction', 'price': 12.99, 'title': 'Sword of Honour'}, -{'author': 'Herman Melville', 'category': 'fiction', 'isbn': '0-553-21311-3', 'price': 8.99, 'title': 'Moby Dick'}, -{'author': 'J. R. R. Tolkien', 'category': 'fiction', 'isbn': '0-395-19395-8', 'price': 22.99, 'title': 'The Lord of the Rings'}]}, -path="$['store']"), -JsonPathResult(data={'color': 'red', 'price': 19.95}, path="$['store']['bicycle']"), -JsonPathResult(data=[{'author': 'Nigel Rees', 'category': 'reference', 'price': 8.95, 'title': 'Sayings of the Century'}, -{'author': 'Evelyn Waugh', 'category': 'fiction', 'price': 12.99, 'title': 'Sword of Honour'}, -{'author': 'Herman Melville', 'category': 'fiction', 'isbn': '0-553-21311-3', 'price': 8.99, 'title': 'Moby Dick'}, -{'author': 'J. R. R. Tolkien', 'category': 'fiction', 'isbn': '0-395-19395-8', 'price': 22.99, 'title': 'The Lord of the Rings'}], -path="$['store']['book']"), -JsonPathResult(data='red', path="$['store']['bicycle']['color']"), -JsonPathResult(data=19.95, path="$['store']['bicycle']['price']"), -JsonPathResult(data={'author': 'Nigel Rees', 'category': 'reference', 'price': 8.95, 'title': 'Sayings of the Century'}, -path="$['store']['book'][0]"), -JsonPathResult(data={'author': 'Evelyn Waugh', 'category': 'fiction', 'price': 12.99, 'title': 'Sword of Honour'}, path="$['store']['book'][1]"), -JsonPathResult(data={'author': 'Herman Melville', 'category': 'fiction', 'isbn': '0-553-21311-3', 'price': 8.99, 'title': 'Moby Dick'}, -path="$['store']['book'][2]"), -JsonPathResult(data={'author': 'J. R. R. Tolkien', 'category': 'fiction', 'isbn': '0-395-19395-8', 'price': 22.99, 'title': 'The Lord of the Rings'}, -path="$['store']['book'][3]"), -JsonPathResult(data='Nigel Rees', path="$['store']['book'][0]['author']"), -JsonPathResult(data='reference', path="$['store']['book'][0]['category']"), -JsonPathResult(data=8.95, path="$['store']['book'][0]['price']"), -JsonPathResult(data='Sayings of the Century', path="$['store']['book'][0]['title']"), -JsonPathResult(data='Evelyn Waugh', path="$['store']['book'][1]['author']"), -JsonPathResult(data='fiction', path="$['store']['book'][1]['category']"), -JsonPathResult(data=12.99, path="$['store']['book'][1]['price']"), -JsonPathResult(data='Sword of Honour', path="$['store']['book'][1]['title']"), -JsonPathResult(data='Herman Melville', path="$['store']['book'][2]['author']"), -JsonPathResult(data='fiction', path="$['store']['book'][2]['category']"), -JsonPathResult(data='0-553-21311-3', path="$['store']['book'][2]['isbn']"), -JsonPathResult(data=8.99, path="$['store']['book'][2]['price']"), -JsonPathResult(data='Moby Dick', path="$['store']['book'][2]['title']"), -JsonPathResult(data='J. R. R. Tolkien', path="$['store']['book'][3]['author']"), -JsonPathResult(data='fiction', path="$['store']['book'][3]['category']"), -JsonPathResult(data='0-395-19395-8', path="$['store']['book'][3]['isbn']"), -JsonPathResult(data=22.99, path="$['store']['book'][3]['price']"), -JsonPathResult(data='The Lord of the Rings', path="$['store']['book'][3]['title']")]""", - ] - f = Finder(sample_data) - res = [] - res_data = [] - res_absolute_path = [] - for query, queries_result in zip(queries, queries_results): - temp_res = f.find(query) - res.extend(temp_res) - res_data.extend(f.find_data(query)) - res_absolute_path.extend(f.find_absolute_path(query)) - assert str(temp_res) == queries_result.replace("\n", "") - assert [r.data for r in res] == res_data - assert [r.path for r in res] == res_absolute_path - # print(query) - # print(f.find(query), '\n') - # print('----------') - - def test_overflow(): big_number = 18446744005107584948 f = Finder({"test": big_number}) res = f.find('$.test')[0].data assert res == big_number + + +QUERIES = [ + "$.store.book[*].author", + "$..book[?(@.isbn)]", + "$.store.*", + "$..author", + "$.store..price", + "$..book[2]", + "$..book[-2]", + "$..book[0,1]", + "$..book[:2]", + "$..book[1:2]", + "$..book[-2:]", + "$..book[2:]", + "$.store.book[?(@.price<10)]", + "$..book[?(@.price<=$.expensive)]", + "$..*", + r'$..book[?match(@.author, "(?i).*rees.*")]', +] + +@pytest.mark.parametrize("query", QUERIES, ids=QUERIES) +def test_smoke(sample_data, query): + f = Finder(sample_data) + + results = f.find(query) + assert isinstance(results, list) + assert all(isinstance(r, JsonPathResult) for r in results) + + assert [r.data for r in results] == f.find_data(query) + assert [r.path for r in results] == f.find_absolute_path(query) + + +def test_authors_data_and_paths(sample_data): + f = Finder(sample_data) + results = f.find("$.store.book[*].author") + data, paths = _extract(results) + + assert data == [ + "Nigel Rees", + "Evelyn Waugh", + "Herman Melville", + "J. R. R. Tolkien", + ] + assert paths == [ + "$['store']['book'][0]['author']", + "$['store']['book'][1]['author']", + "$['store']['book'][2]['author']", + "$['store']['book'][3]['author']", + ] + +def test_bicycle_color_value_and_path(sample_data): + f = Finder(sample_data) + results = f.find("$.store.bicycle.color") + data, paths = _extract(results) + + assert data == ["red"] + assert paths == ["$['store']['bicycle']['color']"] + + +def _extract(results): + assert all(isinstance(r, JsonPathResult) for r in results) + return [r.data for r in results], [r.path for r in results]