From deb0af22e242f71bed89124a1f21556ce3bcfddf Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Sat, 6 Sep 2025 18:28:51 -0400 Subject: [PATCH 1/2] Allow true/false/null to be name selectors --- CHANGELOG.md | 3 +++ parser/parse.go | 2 +- parser/parse_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7616ea5..362d1c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ All notable changes to this project will be documented in this file. It uses the ## [v0.10.1] — Unreleased +### 🐞 Bug Fixes + +* Allow `true`, `false`, and `null` to be used as selectors, e.g., `$.true`. ### 📔 Notes diff --git a/parser/parse.go b/parser/parse.go index e99eafb..de9d762 100644 --- a/parser/parse.go +++ b/parser/parse.go @@ -114,7 +114,7 @@ func (p *parser) parseQuery(root bool) (*spec.PathQuery, error) { // parsed Selector. func parseNameOrWildcard(lex *lexer) (spec.Selector, error) { switch tok := lex.scan(); tok.tok { - case identifier: + case identifier, boolTrue, boolFalse, jsonNull: return spec.Name(tok.val), nil case '*': return spec.Wildcard(), nil diff --git a/parser/parse_test.go b/parser/parse_test.go index 827e342..03b6796 100644 --- a/parser/parse_test.go +++ b/parser/parse_test.go @@ -149,6 +149,30 @@ func TestParseSimple(t *testing.T) { spec.Descendant(spec.Name("xyz")), ), }, + { + test: "true_for_name", + path: `$.true`, + exp: spec.Query( + true, + spec.Child(spec.Name("true")), + ), + }, + { + test: "false_for_name", + path: `$.false`, + exp: spec.Query( + true, + spec.Child(spec.Name("false")), + ), + }, + { + test: "null_for_name", + path: `$.null`, + exp: spec.Query( + true, + spec.Child(spec.Name("null")), + ), + }, { test: "empty_string", path: "", From 79cbfa04c726e0b7d4c130d4dc01b4e1212a39f0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 15 Sep 2025 12:35:34 -0400 Subject: [PATCH 2/2] Add cburgmer/json-path-comparison test suite Add `make` targets to download the suite and run the test, which is gated by the `compare` tag. Skip tests where the consensus is that a query is not supported, as well as a couple of ambiguous tests that need clarification, as well as one test that clearly conflicts with RFC 9535. Skip comparing the result of tests that have no consensus. Leave XXX comments where the behavior is unexpected or unexplained. Requires explicit gopkg.in/yaml.v3 import, so add a note to the README documenting no runtime dependencies. Also upgrade the acceptance test suite, now with tests to confirm the fix in deb0af2. --- .github/workflows/ci.yml | 2 +- .gitignore | 3 ++ CHANGELOG.md | 3 ++ Makefile | 9 ++++ README.md | 4 ++ compare/compare_test.go | 98 ++++++++++++++++++++++++++++++++++ go.mod | 6 ++- jsonpath-compliance-test-suite | 2 +- 8 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 compare/compare_test.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb90654..7c2ae0e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: uses: actions/setup-go@v5 with: { go-version: "${{ matrix.go }}", check-latest: true } - name: Run Tests - run: make test + run: make test-all - name: Setup TinyGo uses: acifani/setup-tinygo@v2 with: { tinygo-version: 0.37.0 } diff --git a/.gitignore b/.gitignore index 689c20e..d6a35e8 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ pub/ # Temp files *.tmp + +# Generated files. +/compare/regression_suite.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 362d1c5..fe067ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,8 +17,11 @@ All notable changes to this project will be documented in this file. It uses the * Upgraded to `golangci-lint` v2.3.1. * Fixed test name scoping issues with testify objects. +* Upgraded the [compliance test suite], now with tests using `true`, + `false`, and `null` to be used as selectors. [v0.10.1]: https://github.com/theory/jsonpath/compare/v0.10.0...v0.10.1 + [compliance test suite]: https://github.com/jsonpath-standard/jsonpath-compliance-test-suite ## [v0.10.0] — 2025-07-11 diff --git a/Makefile b/Makefile index 2c35f51..a1c13b6 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,15 @@ cover: $(shell find . -name \*.go) GOTOOLCHAIN=local $(GO) test -v -coverprofile=cover.out -covermode=count ./... @$(GO) tool cover -html=cover.out +compare/regression_suite.yaml: + curl -so "$@" https://raw.githubusercontent.com/cburgmer/json-path-comparison/refs/heads/master/regression_suite/regression_suite.yaml + +test-compare: compare/regression_suite.yaml + GOTOOLCHAIN=local ${GO} test -tags=compare ./compare -count=1 + +test-all: compare/regression_suite.yaml + GOTOOLCHAIN=local ${GO} test -tags=compare ./... -count=1 + .PHONY: lint # Lint the project lint: .golangci.yaml @pre-commit run --show-diff-on-failure --color=always --all-files diff --git a/README.md b/README.md index 4889072..f389397 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,10 @@ A brief overview of [RFC 9535 JSONPath] syntax: | `?` | filter selector: selects particular children using a logical expression | | `length(@.foo)` | function extension: invokes a function in a filter expression | +## Dependencies + +This package has no runtime dependencies, only testing dependencies. + ## Copyright Copyright © 2024-2025 David E. Wheeler diff --git a/compare/compare_test.go b/compare/compare_test.go new file mode 100644 index 0000000..91e37bf --- /dev/null +++ b/compare/compare_test.go @@ -0,0 +1,98 @@ +//go:build compare + +// Package compare tests theory/jsonpath against the [json-path-comparison] +// project's regression suite. It requires the file regression_suite.yaml to +// be in this directory. The test only runs with the "compare" tag. Use make +// for the easiest way to download regression_suite.yaml and run the tests: +// +// make test-compare +// +// [json-path-comparison]: https://github.com/cburgmer/json-path-comparison +package compare + +import ( + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/theory/jsonpath" + "gopkg.in/yaml.v3" +) + +type Query struct { + ID string `yaml:"id"` + Selector string `yaml:"selector"` + Document any `yaml:"document"` + Consensus any `yaml:"consensus"` + Ordered bool `yaml:"ordered"` +} + +func file(t *testing.T) string { + t.Helper() + _, fn, _, ok := runtime.Caller(0) + assert.True(t, ok) + return filepath.Clean(filepath.Join( + filepath.Dir(fn), + "regression_suite.yaml", + )) +} + +func queries(t *testing.T) []Query { + t.Helper() + data, err := os.ReadFile(file(t)) + require.NoError(t, err) + var q struct { + Queries []Query `yaml:"queries"` + } + require.NoError(t, yaml.Unmarshal(data, &q)) + return q.Queries +} + +func TestConsensus(t *testing.T) { + t.Parallel() + + skip := map[string]string{ + "array_slice_with_step_and_leading_zeros": "leading zeros disallowed integers; see RFC 9535 sections 2.3.3.1, 2.3.4.1", + "dot_notation_with_number_on_object": "leading digits disallowed in shorthand names; see RFC 9535 section 2.5.1.1", + "dot_notation_with_dash": "dash disallowed in shorthand hames; see RFC 9535 section 2.5.1.1", + } + + for _, q := range queries(t) { + t.Run(q.ID, func(t *testing.T) { + t.Parallel() + + if q.Consensus == "NOT_SUPPORTED" { + t.Skip(q.Consensus) + } + + if r, ok := skip[q.ID]; ok { + t.Skip(r) + } + + path, err := jsonpath.Parse(q.Selector) + // XXX Why is consensus empty? + if q.Consensus != nil { + require.NoError(t, err) + } + if err != nil { + // XXX Why is there an error? TODOs? + assert.Nil(t, path) + return + } + result := []any(path.Select(q.Document)) + + switch { + case q.Ordered: + assert.Equal(t, q.Consensus, result) + case q.Consensus == nil: + // XXX What to do here? + // assert.Empty(t, result) + default: + assert.ElementsMatch(t, q.Consensus, result) + } + }) + } +} diff --git a/go.mod b/go.mod index f6a5912..1d2e75d 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,10 @@ module github.com/theory/jsonpath go 1.23 -require github.com/stretchr/testify v1.10.0 +require ( + github.com/stretchr/testify v1.10.0 + gopkg.in/yaml.v3 v3.0.1 +) require ( github.com/davecgh/go-spew v1.1.1 // indirect @@ -10,5 +13,4 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/jsonpath-compliance-test-suite b/jsonpath-compliance-test-suite index 05f6cac..13b60f1 160000 --- a/jsonpath-compliance-test-suite +++ b/jsonpath-compliance-test-suite @@ -1 +1 @@ -Subproject commit 05f6cac786bf0cce95437e6f1adedc3186d54a71 +Subproject commit 13b60f1749e49b591dbbcf62fed8cd67f9aee13d