Skip to content

Commit 425d519

Browse files
committed
Add type checks
This commits implements more advance type checks in strict mode. To enter strict mode set variable types with expr.Define() or expr.With() functions.
1 parent 5a03788 commit 425d519

File tree

14 files changed

+799
-163
lines changed

14 files changed

+799
-163
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ Inspired by
2424
## Features
2525

2626
* Works with any valid Go object (structs, maps, etc)
27-
* Compile-time checks for used variables
28-
* Сlear error messages:
27+
* Strict mode with type checks
28+
* User-friendly error messages
2929
```
3030
unclosed "("
3131
(boo + bar]
@@ -48,3 +48,4 @@ go get -u github.com/antonmedv/expr
4848
## License
4949

5050
MIT
51+

bench_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
package expr
1+
package expr_test
22

33
import (
44
"testing"
55

6+
"github.com/antonmedv/expr"
67
"github.com/dop251/goja"
78
"github.com/robertkrimen/otto"
89
)
@@ -30,13 +31,13 @@ func Benchmark_expr(b *testing.B) {
3031
Marker: "test",
3132
}
3233

33-
script, err := Parse(`Segments[0].Origin == "MOW" && Passengers.Adults == 2 && Marker == "test"`)
34+
script, err := expr.Parse(`Segments[0].Origin == "MOW" && Passengers.Adults == 2 && Marker == "test"`)
3435
if err != nil {
3536
b.Fatal(err)
3637
}
3738

3839
for n := 0; n < b.N; n++ {
39-
Run(script, r)
40+
expr.Run(script, r)
4041
}
4142
}
4243

doc.go

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ For example you can pass nested structures without any modification or preparati
4242
Cookies []Cookie
4343
}
4444
type Request struct {
45-
User user
45+
User *user
4646
}
4747
48-
req := Request{User{
48+
req := Request{&User{
4949
Cookies: []Cookie{{"origin", "www"}},
5050
UserAgent: "Firefox",
5151
}}
@@ -71,39 +71,53 @@ You can also pass functions into the expression:
7171
ok, err := expr.Eval(`"www" in Values(Request.User.Cookies)`, data)
7272
7373
74-
Caching
74+
Parsing and caching
7575
76-
If you planning to execute some expression lots times, it's good to compile it first and only one time:
76+
If you planning to execute some expression lots times, it's good to parse it first and only one time:
77+
78+
// Parse expression to AST.
79+
ast, err := expr.Parse(expression)
80+
81+
// Run given AST
82+
ok, err := expr.Run(ast, data)
7783
78-
// Precompile
79-
node, err := expr.Parse(expression)
8084
81-
// Run
82-
ok, err := expr.Run(node, data)
85+
Strict mode
8386
87+
Expr package support strict parse mode in which some type checks performed during parsing.
88+
To parse expression in strict mode, define all of used variables:
8489
85-
Checking variables and functions
8690
87-
It is possible to check used variables and functions during parsing of the expression.
91+
expression := `Request.User.UserAgent matches "Firefox"`
92+
node, err := expr.Parse(expression, expr.Define("Request", request{}))
8893
8994
90-
expression := `Request.User.UserAgent matches "Firefox" && "www" in Values(Request.User.Cookies)`
95+
Parse function will check used variables, accessed filed, logical operators and some other type checks.
9196
92-
node, err := expr.Parse(expression, expr.Names("Request"), expr.Funcs("Values"))
97+
If you try to use some undeclared variables, or access unknown field, an error will be returned during paring:
9398
99+
expression := `Request.User.Cookies[0].Timestamp`
100+
node, err := expr.Parse(expression, expr.Define("Request", request{}))
94101
95-
Only `Request` and `Values` will be available inside expression, otherwise parse error.
102+
// err: Request.User.Cookies[0].Timestamp undefined (type expr_test.cookie has no field Timestamp)
96103
97-
If you try to use some undeclared variables or functions, an error will be returned during compilation:
104+
Also it's possible to define all used variables and functions using expr.With and struct:
105+
106+
type payload struct {
107+
Request *Request
108+
Values func(xs []Cookie) []string
109+
}
98110
99-
expression := `Unknown(Request.User.UserAgent)`
100-
node, err := expr.Parse(expression, expr.Names("Request"), expr.Funcs("Values"))
111+
node, err := expr.Parse(expression, expr.With(payload{}))
101112
102-
// err.Error():
113+
Or with map:
114+
115+
data := map[string]interface{}{
116+
"Request": req,
117+
"Values": func(xs []Cookie) []string {...},
118+
}
103119
104-
unknown func Unknown
105-
Unknown(Request.User.UserAgent)
106-
-------^
120+
node, err := expr.Parse(expression, expr.With(data))
107121
108122
109123
Printing

doc_test.go

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ func ExampleEval() {
1616
}
1717

1818
fmt.Printf("%v", output)
19+
1920
// Output: hello world
2021
}
2122

@@ -36,6 +37,7 @@ func ExampleEval_map() {
3637
}
3738

3839
fmt.Printf("%v", output)
40+
3941
// Output: hello user
4042
}
4143

@@ -54,6 +56,7 @@ func ExampleEval_struct() {
5456
}
5557

5658
fmt.Printf("%v", output)
59+
5760
// Output: 42
5861
}
5962

@@ -66,6 +69,7 @@ func ExampleEval_error() {
6669
}
6770

6871
fmt.Printf("%v", output)
72+
6973
// Output: err: unclosed "("
7074
//(boo + bar]
7175
//----------^
@@ -80,6 +84,7 @@ func ExampleEval_matches() {
8084
}
8185

8286
fmt.Printf("%v", output)
87+
8388
// Output: err: error parsing regexp: missing closing ): `a(`
8489
//"a" matches "a("
8590
//----------------^
@@ -106,6 +111,7 @@ func ExampleParse() {
106111
}
107112

108113
fmt.Printf("%v", output)
114+
109115
// Output: true
110116
}
111117

@@ -130,33 +136,76 @@ func ExampleRun() {
130136
}
131137

132138
fmt.Printf("%v", output)
139+
133140
// Output: false
134141
}
135142

136-
func ExampleNames() {
137-
_, err := expr.Parse("foo + bar + baz", expr.Names("foo", "bar"))
143+
func ExampleDefine() {
144+
var foo, bar int
145+
_, err := expr.Parse("foo + bar + baz", expr.Define("foo", foo), expr.Define("bar", bar))
138146

139147
if err != nil {
140148
fmt.Printf("err: %v", err)
141149
return
142150
}
143151

144152
// Output: err: unknown name baz
145-
//foo + bar + baz
146-
//------------^
153+
}
154+
155+
func ExampleWith() {
156+
type segment struct {
157+
Origin string
158+
}
159+
type passengers struct {
160+
Adults int
161+
}
162+
type request struct {
163+
Segments []*segment
164+
Passengers *passengers
165+
Marker string
166+
Meta map[string]interface{}
167+
}
168+
169+
code := `Segments[0].Origin == "MOW" && Passengers.Adults == 2 && Marker == "test" && Meta["accept"]`
170+
ast, err := expr.Parse(code, expr.With(&request{}))
171+
172+
if err != nil {
173+
fmt.Printf("err: %v", err)
174+
return
175+
}
176+
177+
r := &request{
178+
Segments: []*segment{
179+
{Origin: "MOW"},
180+
},
181+
Passengers: &passengers{
182+
Adults: 2,
183+
},
184+
Marker: "test",
185+
Meta: map[string]interface{}{"accept": true},
186+
}
187+
output, err := expr.Run(ast, r)
188+
189+
if err != nil {
190+
fmt.Printf("err: %v", err)
191+
return
192+
}
193+
194+
fmt.Printf("%v", output)
195+
196+
// Output: true
147197
}
148198

149199
func ExampleFuncs() {
150-
_, err := expr.Parse("foo(bar(baz()))", expr.Funcs("foo", "bar"))
200+
var foo, bar func()
201+
_, err := expr.Parse("foo(bar(baz()))", expr.Define("foo", foo), expr.Define("bar", bar))
151202

152203
if err != nil {
153204
fmt.Printf("err: %v", err)
154205
return
155206
}
156207

157-
// Output: err: unknown func baz
158-
//foo(bar(baz()))
159-
//--------^
208+
// Output: err: unknown func baz()
160209
}
161210

162211
func ExampleNode() {
@@ -168,5 +217,6 @@ func ExampleNode() {
168217
}
169218

170219
fmt.Printf("%v", node)
220+
171221
// Output: foo.bar
172222
}

0 commit comments

Comments
 (0)