Skip to content

Commit

Permalink
Add Parser object with custom registry
Browse files Browse the repository at this point in the history
Add the Parser struct, which just stores a reference to a function
registry. Give it `Parse` and `MustParse` methods, which parse paths
with the supplied registry. This allows the use of a registry with
custom extensions. Update the `Parse` function to delegate to a parser
and add a `MustParse` function to do the same but panic instead of
returning an error.

Change `parse.Parse` to take a registry as an argument, and teach the
Parser to pass one.

Add example tests demonstrating these use cases.

Update the golangci-lint config to allow functions that return
interfaces defined by the spec package and to ignore the double `R.` in
the `J. R. R. Tolkien" examples.
  • Loading branch information
theory committed Sep 17, 2024
1 parent ef87c24 commit a68899d
Show file tree
Hide file tree
Showing 6 changed files with 346 additions and 35 deletions.
10 changes: 8 additions & 2 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,16 @@ linters-settings:
- empty
- stdlib
- generic
- JSONPathValue
- FunctionExprArg
- spec\.JSONPathValue
- spec\.FunctionExprArg
- spec\.Selector
- spec\.BasicExpr
- spec\.CompVal
# You can specify idiomatic endings for interface
# - (or|er)$
# reject-list of interfaces
# reject:
# - github.com\/user\/package\/v4\.Type
dupword:
ignore:
- R.
15 changes: 4 additions & 11 deletions parser/parse.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Package parser handles parsing RFC 9535 JSONPath queries.
// Package parser parses RFC 9535 JSONPath queries into parse trees. Most
// JSONPath users will use package [jsonpath] instead of this package.
package parser

import (
Expand Down Expand Up @@ -35,10 +36,10 @@ type parser struct {

// Parse parses path, a JSON Path query string, into a PathQuery. Returns a
// PathParseError on parse failure.
func Parse(path string) (*spec.PathQuery, error) {
func Parse(reg *registry.Registry, path string) (*spec.PathQuery, error) {
lex := newLexer(path)
tok := lex.scan()
p := parser{lex, registry.New()}
p := parser{lex, reg}

switch tok.tok {
case '$':
Expand Down Expand Up @@ -110,8 +111,6 @@ func (p *parser) parseQuery(root bool) (*spec.PathQuery, error) {

// parseNameOrWildcard parses a name or '*' wildcard selector. Returns the
// parsed Selector.
//
//nolint:ireturn
func parseNameOrWildcard(lex *lexer) (spec.Selector, error) {
switch tok := lex.scan(); tok.tok {
case identifier:
Expand Down Expand Up @@ -356,8 +355,6 @@ func (p *parser) parseLogicalAndExpr() (spec.LogicalAnd, error) {
// parseBasicExpr parses a [BasicExpr] from lex. A [BasicExpr] may be a
// parenthesized expression (paren-expr), comparison expression
// (comparison-expr), or test expression (test-expr).
//
//nolint:ireturn
func (p *parser) parseBasicExpr() (spec.BasicExpr, error) {
// Consume blank space.
lex := p.lex
Expand Down Expand Up @@ -423,8 +420,6 @@ func (p *parser) parseBasicExpr() (spec.BasicExpr, error) {
// Otherwise it will be a [ComparisonExpr] (comparison-expr), as long as the
// function call is compared to another expression. Any other configuration
// returns an error.
//
//nolint:ireturn
func (p *parser) parseFunctionFilterExpr(ident token) (spec.BasicExpr, error) {
f, err := p.parseFunction(ident)
if err != nil {
Expand Down Expand Up @@ -645,8 +640,6 @@ func (p *parser) parseComparableExpr(left spec.CompVal) (*spec.ComparisonExpr, e
}

// parseComparableVal parses a [CompVal] (comparable) from lex.
//
//nolint:ireturn
func (p *parser) parseComparableVal(tok token) (spec.CompVal, error) {
switch tok.tok {
case goString, integer, number, boolFalse, boolTrue, jsonNull:
Expand Down
8 changes: 5 additions & 3 deletions parser/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func TestParseRoot(t *testing.T) {
a := assert.New(t)
r := require.New(t)

q, err := Parse("$")
q, err := Parse(registry.New(), "$")
r.NoError(err)
a.Equal("$", q.String())
a.Empty(q.Segments())
Expand All @@ -26,6 +26,7 @@ func TestParseSimple(t *testing.T) {
t.Parallel()
a := assert.New(t)
r := require.New(t)
reg := registry.New()

for _, tc := range []struct {
name string
Expand Down Expand Up @@ -199,7 +200,7 @@ func TestParseSimple(t *testing.T) {
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
q, err := Parse(tc.path)
q, err := Parse(reg, tc.path)
if tc.err == "" {
r.NoError(err)
a.Equal(tc.exp, q)
Expand Down Expand Up @@ -856,6 +857,7 @@ func TestParseSelectors(t *testing.T) {
t.Parallel()
a := assert.New(t)
r := require.New(t)
reg := registry.New()

for _, tc := range []struct {
name string
Expand Down Expand Up @@ -1289,7 +1291,7 @@ func TestParseSelectors(t *testing.T) {
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
q, err := Parse(tc.path)
q, err := Parse(reg, tc.path)
if tc.err == "" {
r.NoError(err)
a.Equal(tc.exp, q)
Expand Down
74 changes: 64 additions & 10 deletions path.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ package jsonpath

import (
"github.com/theory/jsonpath/parser"
"github.com/theory/jsonpath/registry"
"github.com/theory/jsonpath/spec"
)

// ErrPathParse errors are returned for path parse errors.
var ErrPathParse = parser.ErrPathParse

// Path represents a [RFC 9535] JSONPath query.
//
// [RFC 9535]: https://www.rfc-editor.org/rfc/rfc9535.html
Expand All @@ -18,16 +22,16 @@ func New(q *spec.PathQuery) *Path {
return &Path{q: q}
}

// Parse parses path, a JSON Path query string, into a Path. Returns a
// PathParseError on parse failure.
//
//nolint:wrapcheck
// Parse parses path, a JSONPath query string, into a Path. Returns an
// ErrPathParse on parse failure.
func Parse(path string) (*Path, error) {
q, err := parser.Parse(path)
if err != nil {
return nil, err
}
return New(q), nil
return NewParser().Parse(path)
}

// MustParse parses path into a Path. Panics with an ErrPathParse on parse
// failure.
func MustParse(path string) *Path {
return NewParser().MustParse(path)
}

// String returns a string representation of p.
Expand All @@ -40,7 +44,57 @@ func (p *Path) Query() *spec.PathQuery {
return p.q
}

// Select executes the p query against input and returns the results.
// Select returns the values that JSONPath query p selects from input.
func (p *Path) Select(input any) []any {
return p.q.Select(nil, input)
}

// Parser compiles JSONPaths.
type Parser struct {
reg *registry.Registry
}

// Option defines a parser option.
type Option func(*Parser)

// WithRegistry configures a Parser with a function Registry, which may
// contain function extensions. See [Parser] for an example.
func WithRegistry(reg *registry.Registry) Option {
return func(p *Parser) { p.reg = reg }
}

// NewParser creates a new Parser configured by opt.
func NewParser(opt ...Option) *Parser {
p := &Parser{}
for _, o := range opt {
o(p)
}

if p.reg == nil {
p.reg = registry.New()
}

return p
}

// Parse parses path, a JSON Path query string, into a Path. Returns an
// ErrPathParse on parse failure.
//
//nolint:wrapcheck
func (c *Parser) Parse(path string) (*Path, error) {
q, err := parser.Parse(c.reg, path)
if err != nil {
return nil, err
}
return New(q), nil
}

// MustParse parses path, a JSON Path query string, into a Path. Panics with
// an ErrPathParse on parse failure.
func (c *Parser) MustParse(path string) *Path {
q, err := parser.Parse(c.reg, path)
if err != nil {
panic(err)
}
return New(q)
}
Loading

0 comments on commit a68899d

Please sign in to comment.