Skip to content

Commit 87a4f95

Browse files
committed
Simplify FuncType, conversion validation, names
Replace the `ConvertsTo*()` methods on `FuncType` with `ConvertsTo()` on `FuncExprArg`. This allows more-direct conversion-checking without separately fetching the type. Remove the `FuncLiteral` and `FuncSingularQuery` variants of `FuncType`. Implementations of `ConvertsTo()` now properly distinguishes between singular and non-singular queries, and the Literal type provided no value over `ValueType`. This restores `FuncLiteral` to just the three variants defined by [RFC 9535 Section 2.4.1]: `ValueType`, `NodesType`, and `LogicalType`. Improve the docs for `ValueFrom()`, `NodesFrom()`, and `LogicalFrom()` to document whether and how each type is converted, and describe how to avoid panics for invalid conversion by checking `ConvertsTo()` in the function extension validator. Also improve the text of the `panic()` messages, and remove the handling of boolean values from `LogicalFrom()`, as `Logical()` handles that behavior. Remove `NodesQueryExpr`, a thin wrapper around `PathQuery`, by simply implementing the `FuncExprArg` interface on Query. Move the `Function` struct to `spec` and name it `FuncExtension`. Have the registry construct it via the `Extension()` constructor, which tightens things up quite a bit. Also move the `Validator` and `Evaluator` aliases to `spec` and use them consistently. Remove the `PathFunction` interface, as it's no longer needed; we instead use `FuncExtension` directly. More consistently refer to them as "function extensions" in the docs, to patch the phrase of the spec. Rename `JSONPathValue` to `PathValue`, as the `JSON` prefix feels redundant. [RFC 9535 Section 2.4.1]: https://www.rfc-editor.org/rfc/rfc9535#name-type-system-for-function-ex
1 parent a122146 commit 87a4f95

20 files changed

+697
-740
lines changed

.golangci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ linters:
3535
- empty
3636
- stdlib
3737
- generic
38-
- spec\.JSONPathValue
38+
- spec\.PathValue
3939
- spec\.FuncExprArg
4040
- spec\.Selector
4141
- spec\.BasicExpr

CHANGELOG.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,17 @@ All notable changes to this project will be documented in this file. It uses the
1111

1212
### ⚡ Improvements
1313

14-
* Made significant changes to the `spec` package toward public stability,
15-
usability, and greater comprehension. The names of things are more
16-
consistent, the APIs more legible and user-friendly. See [PR #14] for
17-
details.
14+
* Significantly refactored the `spec` package toward greater stability,
15+
usability, and increased comprehensibility. The names of things are more
16+
consistent, the APIs more legible and user-friendly. Quite a few types
17+
were renamed or merged.
1818
* Added support for [json.Number] values to complement the existing support
1919
for Go core numeric types. This should allow for transparent handling of
2020
values marshaled with [json.Decoder.UseNumber] enabled.
21+
* Moved the function extension types from the `registry` to the `spec`
22+
package, simplifying `registry` and the handling of function extensions,
23+
without changing the interface for using a registry or adding extensions
24+
to it.
2125

2226
### 📚 Documentation
2327

@@ -26,7 +30,8 @@ All notable changes to this project will be documented in this file. It uses the
2630
and complete lists of interface implementations and examples for each
2731
significant type. See [PR #14] for details.
2832
* Removed the "Package Stability" statement from the README, as all packages
29-
are considered stable.
33+
are considered stable or else potentially unstable types in the `spec`
34+
package have been labeled as such.
3035
* Fixed links and typos in the main package documentation, and moved the
3136
registry example under `WithRegistry`.
3237

parser/parse_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ func TestParseFilter(t *testing.T) {
201201
"__true",
202202
spec.FuncLogical,
203203
func([]spec.FuncExprArg) error { return nil },
204-
func([]spec.JSONPathValue) spec.JSONPathValue {
204+
func([]spec.PathValue) spec.PathValue {
205205
return spec.LogicalTrue
206206
},
207207
)

path_example_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ func validateFirstArgs(args []spec.FuncExprArg) error {
275275
return fmt.Errorf("expected 1 argument but found %v", len(args))
276276
}
277277

278-
if !args[0].ResultType().ConvertsToNodes() {
278+
if !args[0].ConvertsTo(spec.FuncNodes) {
279279
return errors.New("cannot convert argument to nodes")
280280
}
281281

@@ -285,7 +285,7 @@ func validateFirstArgs(args []spec.FuncExprArg) error {
285285
// firstFunc defines the custom first() JSONPath function. It converts its
286286
// single argument to a [spec.NodesType] value and returns a [spec.ValueType]
287287
// that contains the first node. If there are no nodes it returns nil.
288-
func firstFunc(jv []spec.JSONPathValue) spec.JSONPathValue {
288+
func firstFunc(jv []spec.PathValue) spec.PathValue {
289289
nodes := spec.NodesFrom(jv[0])
290290
if len(nodes) == 0 {
291291
return nil

registry/funcs.go

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@ import (
1313
// checkLengthArgs checks the argument expressions to length() and returns an
1414
// error if there is not exactly one expression that results in a compatible
1515
// [spec.FuncValue] value.
16-
func checkLengthArgs(fea []spec.FuncExprArg) error {
17-
if len(fea) != 1 {
18-
return fmt.Errorf("expected 1 argument but found %v", len(fea))
16+
func checkLengthArgs(args []spec.FuncExprArg) error {
17+
if len(args) != 1 {
18+
return fmt.Errorf("expected 1 argument but found %v", len(args))
1919
}
2020

21-
kind := fea[0].ResultType()
22-
if !kind.ConvertsToValue() {
21+
if !args[0].ConvertsTo(spec.FuncValue) {
2322
return errors.New("cannot convert argument to Value")
2423
}
2524

@@ -37,7 +36,7 @@ func checkLengthArgs(fea []spec.FuncExprArg) error {
3736
// - If jv[0] is an map[string]any, the result is the number of members in
3837
// the map.
3938
// - For any other value, the result is nil.
40-
func lengthFunc(jv []spec.JSONPathValue) spec.JSONPathValue {
39+
func lengthFunc(jv []spec.PathValue) spec.PathValue {
4140
v := spec.ValueFrom(jv[0])
4241
if v == nil {
4342
return nil
@@ -58,13 +57,12 @@ func lengthFunc(jv []spec.JSONPathValue) spec.JSONPathValue {
5857
// checkCountArgs checks the argument expressions to count() and returns an
5958
// error if there is not exactly one expression that results in a
6059
// [spec.FuncNodes]-compatible value.
61-
func checkCountArgs(fea []spec.FuncExprArg) error {
62-
if len(fea) != 1 {
63-
return fmt.Errorf("expected 1 argument but found %v", len(fea))
60+
func checkCountArgs(args []spec.FuncExprArg) error {
61+
if len(args) != 1 {
62+
return fmt.Errorf("expected 1 argument but found %v", len(args))
6463
}
6564

66-
kind := fea[0].ResultType()
67-
if !kind.ConvertsToNodes() {
65+
if !args[0].ConvertsTo(spec.FuncNodes) {
6866
return errors.New("cannot convert argument to Nodes")
6967
}
7068

@@ -75,20 +73,19 @@ func checkCountArgs(fea []spec.FuncExprArg) error {
7573
// a ValueType containing an unsigned integer for the number of nodes in
7674
// jv[0]. Panics if jv[0] doesn't exist or is not convertible to
7775
// [spec.NodesType].
78-
func countFunc(jv []spec.JSONPathValue) spec.JSONPathValue {
76+
func countFunc(jv []spec.PathValue) spec.PathValue {
7977
return spec.Value(len(spec.NodesFrom(jv[0])))
8078
}
8179

8280
// checkValueArgs checks the argument expressions to value() and returns an
8381
// error if there is not exactly one expression that results in a
8482
// [spec.FuncNodes]-compatible value.
85-
func checkValueArgs(fea []spec.FuncExprArg) error {
86-
if len(fea) != 1 {
87-
return fmt.Errorf("expected 1 argument but found %v", len(fea))
83+
func checkValueArgs(args []spec.FuncExprArg) error {
84+
if len(args) != 1 {
85+
return fmt.Errorf("expected 1 argument but found %v", len(args))
8886
}
8987

90-
kind := fea[0].ResultType()
91-
if !kind.ConvertsToNodes() {
88+
if !args[0].ConvertsTo(spec.FuncNodes) {
9289
return errors.New("cannot convert argument to Nodes")
9390
}
9491

@@ -100,7 +97,7 @@ func checkValueArgs(fea []spec.FuncExprArg) error {
10097
//
10198
// - If jv[0] contains a single node, the result is the value of the node.
10299
// - If jv[0] is empty or contains multiple nodes, the result is nil.
103-
func valueFunc(jv []spec.JSONPathValue) spec.JSONPathValue {
100+
func valueFunc(jv []spec.PathValue) spec.PathValue {
104101
nodes := spec.NodesFrom(jv[0])
105102
if len(nodes) == 1 {
106103
return spec.Value(nodes[0])
@@ -111,15 +108,14 @@ func valueFunc(jv []spec.JSONPathValue) spec.JSONPathValue {
111108
// checkMatchArgs checks the argument expressions to match() and returns an
112109
// error if there are not exactly two expressions that result in compatible
113110
// [spec.FuncValue] values.
114-
func checkMatchArgs(fea []spec.FuncExprArg) error {
111+
func checkMatchArgs(args []spec.FuncExprArg) error {
115112
const matchArgLen = 2
116-
if len(fea) != matchArgLen {
117-
return fmt.Errorf("expected 2 arguments but found %v", len(fea))
113+
if len(args) != matchArgLen {
114+
return fmt.Errorf("expected 2 arguments but found %v", len(args))
118115
}
119116

120-
for i, arg := range fea {
121-
kind := arg.ResultType()
122-
if !kind.ConvertsToValue() {
117+
for i, arg := range args {
118+
if !arg.ConvertsTo(spec.FuncValue) {
123119
return fmt.Errorf("cannot convert argument %v to Value", i+1)
124120
}
125121
}
@@ -132,11 +128,11 @@ func checkMatchArgs(fea []spec.FuncExprArg) error {
132128
// implied \A and \z anchors and used to match the first, returning LogicalTrue for
133129
// a match and LogicalFalse for no match. Returns LogicalFalse if either jv value
134130
// is not a string or if jv[1] fails to compile.
135-
func matchFunc(jv []spec.JSONPathValue) spec.JSONPathValue {
131+
func matchFunc(jv []spec.PathValue) spec.PathValue {
136132
if v, ok := spec.ValueFrom(jv[0]).Value().(string); ok {
137133
if r, ok := spec.ValueFrom(jv[1]).Value().(string); ok {
138134
if rc := compileRegex(`\A` + r + `\z`); rc != nil {
139-
return spec.LogicalFrom(rc.MatchString(v))
135+
return spec.Logical(rc.MatchString(v))
140136
}
141137
}
142138
}
@@ -146,15 +142,14 @@ func matchFunc(jv []spec.JSONPathValue) spec.JSONPathValue {
146142
// checkSearchArgs checks the argument expressions to search() and returns an
147143
// error if there are not exactly two expressions that result in compatible
148144
// [spec.FuncValue] values.
149-
func checkSearchArgs(fea []spec.FuncExprArg) error {
145+
func checkSearchArgs(args []spec.FuncExprArg) error {
150146
const searchArgLen = 2
151-
if len(fea) != searchArgLen {
152-
return fmt.Errorf("expected 2 arguments but found %v", len(fea))
147+
if len(args) != searchArgLen {
148+
return fmt.Errorf("expected 2 arguments but found %v", len(args))
153149
}
154150

155-
for i, arg := range fea {
156-
kind := arg.ResultType()
157-
if !kind.ConvertsToValue() {
151+
for i, arg := range args {
152+
if !arg.ConvertsTo(spec.FuncValue) {
158153
return fmt.Errorf("cannot convert argument %v to Value", i+1)
159154
}
160155
}
@@ -167,11 +162,11 @@ func checkSearchArgs(fea []spec.FuncExprArg) error {
167162
// to match the former, returning LogicalTrue for a match and LogicalFalse for no
168163
// match. Returns LogicalFalse if either value is not a string, or if jv[1]
169164
// fails to compile.
170-
func searchFunc(jv []spec.JSONPathValue) spec.JSONPathValue {
165+
func searchFunc(jv []spec.PathValue) spec.PathValue {
171166
if val, ok := spec.ValueFrom(jv[0]).Value().(string); ok {
172167
if r, ok := spec.ValueFrom(jv[1]).Value().(string); ok {
173168
if rc := compileRegex(r); rc != nil {
174-
return spec.LogicalFrom(rc.MatchString(val))
169+
return spec.Logical(rc.MatchString(val))
175170
}
176171
}
177172
}

0 commit comments

Comments
 (0)