Skip to content

Commit ff6a014

Browse files
authored
fix: do not fail with empty inputs (#7)
1 parent fd27c01 commit ff6a014

File tree

5 files changed

+167
-73
lines changed

5 files changed

+167
-73
lines changed

README.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ A powerful `filter` query parameter implementation based on [RFC 8040][rfc8040]
1111
> [!NOTE]
1212
> This repository only contains the lexer-parser.
1313
>
14-
> To check the current data-layer implementations check [Implementation](#-implementation).
14+
> To check the current data-layer implementations, check [Implementations](#-implementations).
1515
1616
## 🚀 Features
1717

@@ -53,10 +53,21 @@ Then it's expected to filter the following conditions:
5353
| `age gt 18 and age lt 65` |||||
5454
| `age le 18 or age gt 65` |||||
5555

56-
## 🔧 Implementation
56+
## 🔧 Implementations
5757

5858
GoQrius is designed to be easily integrated into your REST API endpoints.
59-
The filter parameter parses the expression and converts it into executable conditions for your data layer.
59+
By using:
60+
61+
```go
62+
filter := "name eq 'John'" // e.g. value retrieved from a query parameter.
63+
e, err := goqrius.Parse(filter)
64+
if err != nil {
65+
...
66+
}
67+
...
68+
```
69+
70+
You get the GoQrius expression that can be transformed to a filtering clause in your data layer.
6071

6172
The current data layers implementations for GoQrius are:
6273

goqrius.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Package goqrius
2+
package goqrius
3+
4+
import (
5+
"strings"
6+
7+
"github.com/golaxo/goqrius/lexer"
8+
)
9+
10+
// Parse the input filter expression to a goqrius Expression.
11+
func Parse(input string) (Expression, error) {
12+
if input == "" {
13+
//nolint:nilnil // TODO think about returning something like EmptyExpression{}, nil.
14+
return nil, nil
15+
}
16+
17+
l := lexer.New(input)
18+
p := New(l)
19+
e := p.Parse()
20+
21+
var err error
22+
if len(p.Errors()) > 0 {
23+
err = ParseError{errors: p.Errors()}
24+
}
25+
26+
return e, err
27+
}
28+
29+
func MustParse(input string) Expression {
30+
e, err := Parse(input)
31+
if err != nil {
32+
panic(err)
33+
}
34+
35+
return e
36+
}
37+
38+
var _ error = new(ParseError)
39+
40+
type ParseError struct {
41+
errors []string
42+
}
43+
44+
func (p ParseError) Error() string {
45+
return strings.Join(p.errors, ",")
46+
}

nodes.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package goqrius
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/golaxo/goqrius/token"
7+
)
8+
9+
type (
10+
Node interface {
11+
String() string
12+
}
13+
14+
Expression interface {
15+
Node
16+
expressionNode()
17+
}
18+
)
19+
20+
func (i *Identifier) String() string { return i.Value }
21+
func (i *Identifier) expressionNode() {}
22+
23+
func (il *IntegerLiteral) String() string { return il.Value }
24+
func (il *IntegerLiteral) expressionNode() {}
25+
26+
func (sl *StringLiteral) String() string { return fmt.Sprintf("'%s'", sl.Value) }
27+
func (sl *StringLiteral) expressionNode() {}
28+
29+
type (
30+
// Identifier is the Expression to indicate the key of a filter clause, e.g. `name`.
31+
Identifier struct {
32+
Value string
33+
}
34+
35+
// IntegerLiteral is the Expression to indicate an int value of a filter clause, e.g. `1`.
36+
IntegerLiteral struct {
37+
Value string
38+
}
39+
40+
// StringLiteral is the Expression to indicate an int value of a filter clause, e.g. `'John'`.
41+
StringLiteral struct {
42+
Value string
43+
}
44+
)
45+
46+
type (
47+
// NotExpr negates an Expression.
48+
NotExpr struct {
49+
Right Expression
50+
}
51+
52+
// FilterExpr represents a key and operator and a value in a filter clause.
53+
FilterExpr struct {
54+
Left Expression
55+
Operator token.Type
56+
Right Expression
57+
}
58+
)
59+
60+
func (ne *NotExpr) String() string { return fmt.Sprintf("(not %s)", ne.Right.String()) }
61+
func (ne *NotExpr) expressionNode() {}
62+
63+
func (ie *FilterExpr) String() string {
64+
return fmt.Sprintf("(%s %s %s)", ie.Left.String(), string(ie.Operator), ie.Right.String())
65+
}
66+
func (ie *FilterExpr) expressionNode() {}

parser/parser.go renamed to parser.go

Lines changed: 22 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
// Package parser parse the filter expression.
2-
package parser
1+
package goqrius
32

43
import (
54
"fmt"
@@ -11,72 +10,34 @@ import (
1110
// Precedences.
1211
const (
1312
_ int = iota
14-
LOWEST
15-
OR // or
16-
AND // and
17-
PREFIX // not
18-
COMPARE // eq, ne, gt, ge, lt, le
13+
lowest
14+
or // or
15+
and // and
16+
prefix // not
17+
compare // eq, ne, gt, ge, lt, le
1918
)
2019

2120
//nolint:exhaustive,gochecknoglobals // no need to put all the tokens.
2221
var precedences = map[token.Type]int{
23-
token.Or: OR,
24-
token.And: AND,
25-
token.Eq: COMPARE,
26-
token.NotEq: COMPARE,
27-
token.GreaterThan: COMPARE,
28-
token.GreaterThanOrEqual: COMPARE,
29-
token.LessThan: COMPARE,
30-
token.LessThanOrEqual: COMPARE,
22+
token.Or: or,
23+
token.And: and,
24+
token.Eq: compare,
25+
token.NotEq: compare,
26+
token.GreaterThan: compare,
27+
token.GreaterThanOrEqual: compare,
28+
token.LessThan: compare,
29+
token.LessThanOrEqual: compare,
3130
}
3231

33-
// AST nodes
34-
35-
type Node interface{ String() string }
36-
37-
type Expression interface {
38-
Node
39-
expressionNode()
40-
}
41-
42-
type Identifier struct{ Value string }
43-
44-
func (i *Identifier) String() string { return i.Value }
45-
func (i *Identifier) expressionNode() {}
46-
47-
type IntegerLiteral struct{ Value string }
48-
49-
func (il *IntegerLiteral) String() string { return il.Value }
50-
func (il *IntegerLiteral) expressionNode() {}
51-
52-
type StringLiteral struct{ Value string }
53-
54-
func (sl *StringLiteral) String() string { return fmt.Sprintf("'%s'", sl.Value) }
55-
func (sl *StringLiteral) expressionNode() {}
56-
57-
type NotExpr struct{ Right Expression }
58-
59-
func (ne *NotExpr) String() string { return fmt.Sprintf("(not %s)", ne.Right.String()) }
60-
func (ne *NotExpr) expressionNode() {}
61-
62-
type InfixExpr struct {
63-
Left Expression
64-
Operator token.Type
65-
Right Expression
66-
}
67-
68-
func (ie *InfixExpr) String() string {
69-
return fmt.Sprintf("(%s %s %s)", ie.Left.String(), string(ie.Operator), ie.Right.String())
70-
}
71-
func (ie *InfixExpr) expressionNode() {}
72-
7332
type Parser struct {
7433
l *lexer.Lexer
7534
curToken token.Token
7635
peekToken token.Token
7736
errors []string
7837
}
7938

39+
// New creates a new Parser based on a Lexer.
40+
// It's recommended to use goqrius.Parse instead of this.
8041
func New(l *lexer.Lexer) *Parser {
8142
p := &Parser{
8243
l: l,
@@ -99,7 +60,7 @@ func (p *Parser) Parse() Expression {
9960
p.nextToken()
10061
}
10162

102-
expr := p.parseExpression(LOWEST)
63+
expr := p.parseExpression(lowest)
10364

10465
// consume trailing tokens until EOF
10566
for p.peekToken.Type != token.EOF {
@@ -128,15 +89,15 @@ func (p *Parser) peekPrecedence() int {
12889
return pr
12990
}
13091

131-
return LOWEST
92+
return lowest
13293
}
13394

13495
func (p *Parser) curPrecedence() int {
13596
if pr, ok := precedences[p.curToken.Type]; ok {
13697
return pr
13798
}
13899

139-
return LOWEST
100+
return lowest
140101
}
141102

142103
func (p *Parser) peekError(t token.Type) {
@@ -156,12 +117,12 @@ func (p *Parser) parseExpression(precedence int) Expression {
156117
leftExp = &StringLiteral{Value: p.curToken.Literal}
157118
case token.Not:
158119
p.nextToken()
159-
right := p.parseExpression(PREFIX)
120+
right := p.parseExpression(prefix)
160121
leftExp = &NotExpr{Right: right}
161122
case token.Lparen:
162123
p.nextToken()
163124

164-
leftExp = p.parseExpression(LOWEST)
125+
leftExp = p.parseExpression(lowest)
165126
if !p.expectPeek(token.Rparen) {
166127
return nil
167128
}
@@ -182,7 +143,7 @@ func (p *Parser) parseExpression(precedence int) Expression {
182143
prec := p.curPrecedence()
183144
p.nextToken() // advance to the right expression's first token
184145
right := p.parseExpression(prec)
185-
leftExp = &InfixExpr{Left: leftExp, Operator: op, Right: right}
146+
leftExp = &FilterExpr{Left: leftExp, Operator: op, Right: right}
186147
default:
187148
return leftExp
188149
}
Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package parser
1+
package goqrius
22

33
import (
44
"testing"
@@ -51,25 +51,35 @@ func TestParseExpressions(t *testing.T) {
5151
t.Run(name, func(t *testing.T) {
5252
t.Parallel()
5353

54-
l := lexer.New(tt.input)
55-
p := New(l)
56-
expr := p.Parse()
54+
expr, err := Parse(tt.input)
55+
if err != nil {
56+
t.Fatalf("err not expected; error=%v", err)
57+
}
5758

5859
if expr == nil {
59-
t.Fatalf("expected non-nil expression; errors=%v", p.Errors())
60+
t.Fatalf("expected non-nil expression")
6061
}
6162

6263
if got := expr.String(); got != tt.expectedString {
6364
t.Fatalf("unexpected AST string. expected=%q got=%q", tt.expectedString, got)
6465
}
65-
66-
if len(p.Errors()) != 0 {
67-
t.Fatalf("unexpected parser errors: %v", p.Errors())
68-
}
6966
})
7067
}
7168
}
7269

70+
func TestEmptyInput(t *testing.T) {
71+
t.Parallel()
72+
73+
expr, err := Parse("")
74+
if err != nil {
75+
t.Fatalf("err not expected; error=%v", err)
76+
}
77+
78+
if expr != nil {
79+
t.Fatalf("expected nil expression")
80+
}
81+
}
82+
7383
func TestParseErrors(t *testing.T) {
7484
t.Parallel()
7585

0 commit comments

Comments
 (0)