hiltontj / serde_json_path

Query serde_json Values with JSONPath
https://serdejsonpath.live/
MIT License
50 stars 3 forks source link

[#1, #4] Function Extensions and Other Changes #32

Closed hiltontj closed 1 year ago

hiltontj commented 1 year ago

The primary focus of this PR is the introduction of support for Function Extensions in JSONPath query strings (closes #1). In addition, a refactored error system was introduced (related to #4), the crate was converted to a workspace, and tracing was introduced.

Function Extensions

The JSONPath spec outlines Function Extensions. These allow for the use of functions within queries, e.g.,

$[? length(@.foo) > 5 ]

There is a standard register of functions, currently comprised of length, count, match, search, and value functions. There is also a type-system that the standard itself, as well as implementors of the standard, can use to extend JSONPath via the creation of additional functions.

In this PR, support for Function Extensions has been added via the functions module, as well as the #[function] attribute macro.

serde_json_path will now support the standard registered functions defined in the JSONPath specification. Currently that is limited to the five listed above, but will grow as the standard grows.

The Type System and Parse-time Function Validation

serde_json_path validates the use of functions in query strings at parse time. There is a concept of well-typedness in the standard that defines where functions, based on their signature, can be used within queries. These rules are enforced for functions by serde_json_path.

This PR therefore introduces three new types to support those defined in the standard: ValueType, NodesType, and LogicalType. These are used internally by serde_json_path to implement the standard registry functions, but are also exposed through the public API for users to use in their own custom functions, along with the #[function] attribute macro.

The #[function] Attribute Macro

The #[function] attribute macro was introduced to enable custom function registration in serde_json_path. This macro allows users of serde_json_path to define their own functions. To give an example, say you want to have a function called first() in your queries, that takes a NodeList, and returns the first node, if it is not empty. You can now accomplish this by adding the following somewhere in your application's code:

use serde_json_path::functions::{NodesType, ValueType};

#[serde_json_path::function]
fn first(nodes: NodesType) -> ValueType {
    match nodes.first() {
        Some(v) => ValueType::Node(v),
        None => ValueType::Nothing,
    }
}

And that's it! Now you can use a function called first in your queries, i.e., the following query will be valid:

$[? first(@.*) == 42 ]

The macro will ensure that user-defined functions are valid at compile-time, and will generate the means to:

This maintains the current status quo; namely, that JsonPath::parse is fallible, while JsonPath::query is not.

The macro's capability to register functions at compile time is made possible via the inventory crate.

For users that do not want to use the #[function] attribute macro, they can disable the functions feature.

Other Changes

Updated Error System

The introduction of functions brought with it the need for a custom parser error type; mainly, to propagate specific errors pertaining to function misuse in query strings at parse-time. This was inevitable (see #4), but needed to be addressed, so I did so here, and not in a future PR.

Therefore, the internal parser error system was updated by this PR. The public Error type was also altered so that it can provide more concise error messages, and better support the addition of more fine grained errors in future, without introducing breaking changes.

Overhaul of Repository Structure

To support the new #[function] attribute macro, the repository was converted to a workspace, comprised of the following members:

This means that serde_json_path now covers four separate crates.

The trace feature flag

To better support debugging of query parsing and evaluation, the use of tracing was introduced, and gated by the trace feature flag, which is not enabled by default. All parser functions and query evaluation functions are instrumented at the TRACE level, to enable debugging of query string parsing and evaluation.

This is largely to aid internal development efforts of serde_json_path, but may also prove useful to anyone attempting to define their own functions, or submit issues for parser bugs.