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 7616ea5..fe067ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,13 +9,19 @@ 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 * 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 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: "",