Skip to content

Commit 2f1f51e

Browse files
feat: add support for in expression operator (#23)
* feat: add support for in expression operator add support for "in" and "not in" expression operators * test: update to pipeline to properly do release checks
1 parent 85cff81 commit 2f1f51e

11 files changed

+506
-25
lines changed

.github/workflows/pull_request.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
go mod download
2828
- name: Run Test
2929
run: |
30-
go test -v -count=10 ./...
30+
go test -count=10 ./...
3131
lint:
3232
strategy:
3333
matrix:
@@ -51,7 +51,7 @@ jobs:
5151
release_check:
5252
runs-on: ubuntu-latest
5353
outputs:
54-
git_diff: ${{ steps.changes.outputs.git_diff }}
54+
git_diff: ${{ steps.output.outputs.git_diff }}
5555
steps:
5656
- name: Checkout repository
5757
uses: actions/checkout@v2
@@ -61,11 +61,12 @@ jobs:
6161
uses: technote-space/get-diff-action@v6
6262
with:
6363
PATTERNS: |
64-
+**/*.go
64+
**/*.go
6565
!**/*_test.go
6666
FILES: |
6767
go.mod
6868
- name: Output Diff
69+
id: output
6970
if: env.GIT_DIFF
7071
run: |
7172
echo "::set-output name=git_diff::${{ env.GIT_DIFF }}"

.github/workflows/push_main.yaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
go mod download
2727
- name: Run Test
2828
run: |
29-
go test -v -count=10 ./...
29+
go test -count=10 ./...
3030
lint:
3131
strategy:
3232
matrix:
@@ -61,7 +61,7 @@ jobs:
6161
go mod download
6262
- name: Run Test
6363
run: |
64-
go test -v -coverprofile=coverage.txt -covermode=atomic -count=10 ./...
64+
go test -coverprofile=coverage.txt -covermode=atomic -count=10 ./...
6565
- name: Upload coverage to Codecov
6666
uses: codecov/codecov-action@v2
6767
with:
@@ -73,7 +73,7 @@ jobs:
7373
runs-on: ubuntu-latest
7474
needs: [test, lint]
7575
outputs:
76-
git_diff: ${{ steps.changes.outputs.git_diff }}
76+
git_diff: ${{ steps.output.outputs.git_diff }}
7777
steps:
7878
- name: Checkout repository
7979
uses: actions/checkout@v2
@@ -83,12 +83,13 @@ jobs:
8383
uses: technote-space/get-diff-action@v6
8484
with:
8585
PATTERNS: |
86-
+**/*.go
86+
**/*.go
8787
!**/*_test.go
8888
FILES: |
8989
go.mod
9090
- name: Output Diff
9191
if: env.GIT_DIFF
92+
id: output
9293
run: |
9394
echo "::set-output name=git_diff::${{ env.GIT_DIFF }}"
9495
release:

script/standard/complex_operators.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,45 @@ func (op *selectorOperator) Evaluate(parameters map[string]interface{}) (interfa
8989
}
9090
return value, nil
9191
}
92+
93+
type inOperator struct {
94+
arg1, arg2 interface{}
95+
}
96+
97+
func (op *inOperator) Evaluate(parameters map[string]interface{}) (interface{}, error) {
98+
var item interface{} = op.arg1
99+
if numValue, err := getNumber(op.arg1, parameters); err == nil {
100+
item = numValue
101+
} else if strValue, err := getString(op.arg1, parameters); err == nil {
102+
item = strValue
103+
} else if boolValue, err := getBoolean(op.arg1, parameters); err == nil {
104+
item = boolValue
105+
}
106+
107+
elements, err := getElements(op.arg2, parameters)
108+
if err != nil {
109+
return nil, err
110+
}
111+
112+
for _, element := range elements {
113+
if element == item {
114+
return true, nil
115+
}
116+
}
117+
118+
return false, nil
119+
}
120+
121+
type notInOperator struct {
122+
arg1, arg2 interface{}
123+
}
124+
125+
func (op *notInOperator) Evaluate(parameters map[string]interface{}) (interface{}, error) {
126+
inOperator := &inOperator{arg1: op.arg1, arg2: op.arg2}
127+
val, err := inOperator.Evaluate(parameters)
128+
if err != nil {
129+
return nil, err
130+
}
131+
boolVal := val.(bool)
132+
return !boolVal, nil
133+
}

script/standard/complex_operators_test.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,163 @@ func Test_selectorOperator(t *testing.T) {
173173
}
174174
batchOperatorTests(t, tests)
175175
}
176+
177+
func Test_inOperator(t *testing.T) {
178+
currentDSelector, _ := newSelectorOperator("@.d", &ScriptEngine{}, nil)
179+
180+
tests := []*operatorTest{
181+
{
182+
input: operatorTestInput{
183+
operator: &inOperator{arg1: nil, arg2: nil},
184+
paramters: map[string]interface{}{},
185+
},
186+
expected: operatorTestExpected{
187+
err: "invalid argument. is nil",
188+
},
189+
},
190+
{
191+
input: operatorTestInput{
192+
operator: &inOperator{arg1: nil, arg2: []interface{}{"one"}},
193+
paramters: map[string]interface{}{},
194+
},
195+
expected: operatorTestExpected{
196+
value: false,
197+
},
198+
},
199+
{
200+
input: operatorTestInput{
201+
operator: &inOperator{arg1: "one", arg2: []interface{}{"one"}},
202+
paramters: map[string]interface{}{},
203+
},
204+
expected: operatorTestExpected{
205+
value: true,
206+
},
207+
},
208+
{
209+
input: operatorTestInput{
210+
operator: &inOperator{arg1: "one", arg2: `["one","two"]`},
211+
paramters: map[string]interface{}{},
212+
},
213+
expected: operatorTestExpected{
214+
value: true,
215+
},
216+
},
217+
{
218+
input: operatorTestInput{
219+
operator: &inOperator{arg1: "one", arg2: `{"1":"one","2":"two"}`},
220+
paramters: map[string]interface{}{},
221+
},
222+
expected: operatorTestExpected{
223+
value: true,
224+
},
225+
},
226+
{
227+
input: operatorTestInput{
228+
operator: &inOperator{arg1: "1", arg2: `[1,2,3]`},
229+
paramters: map[string]interface{}{},
230+
},
231+
expected: operatorTestExpected{
232+
value: true,
233+
},
234+
},
235+
{
236+
input: operatorTestInput{
237+
operator: &inOperator{arg1: "1", arg2: `["1","2","3"]`},
238+
paramters: map[string]interface{}{},
239+
},
240+
expected: operatorTestExpected{
241+
value: false,
242+
},
243+
},
244+
{
245+
input: operatorTestInput{
246+
operator: &inOperator{
247+
arg1: "2",
248+
arg2: currentDSelector,
249+
},
250+
paramters: map[string]interface{}{
251+
"@": map[string]interface{}{
252+
"d": []interface{}{
253+
float64(1),
254+
float64(2),
255+
float64(3),
256+
},
257+
},
258+
},
259+
},
260+
expected: operatorTestExpected{
261+
value: true,
262+
},
263+
},
264+
}
265+
batchOperatorTests(t, tests)
266+
}
267+
268+
func Test_notInOperator(t *testing.T) {
269+
tests := []*operatorTest{
270+
{
271+
input: operatorTestInput{
272+
operator: &notInOperator{arg1: nil, arg2: nil},
273+
paramters: map[string]interface{}{},
274+
},
275+
expected: operatorTestExpected{
276+
err: "invalid argument. is nil",
277+
},
278+
},
279+
{
280+
input: operatorTestInput{
281+
operator: &notInOperator{arg1: nil, arg2: []interface{}{"one"}},
282+
paramters: map[string]interface{}{},
283+
},
284+
expected: operatorTestExpected{
285+
value: true,
286+
},
287+
},
288+
{
289+
input: operatorTestInput{
290+
operator: &notInOperator{arg1: "one", arg2: []interface{}{"one"}},
291+
paramters: map[string]interface{}{},
292+
},
293+
expected: operatorTestExpected{
294+
value: false,
295+
},
296+
},
297+
{
298+
input: operatorTestInput{
299+
operator: &notInOperator{arg1: "one", arg2: `["one","two"]`},
300+
paramters: map[string]interface{}{},
301+
},
302+
expected: operatorTestExpected{
303+
value: false,
304+
},
305+
},
306+
{
307+
input: operatorTestInput{
308+
operator: &notInOperator{arg1: "one", arg2: `{"1":"one","2":"two"}`},
309+
paramters: map[string]interface{}{},
310+
},
311+
expected: operatorTestExpected{
312+
value: false,
313+
},
314+
},
315+
{
316+
input: operatorTestInput{
317+
operator: &notInOperator{arg1: "1", arg2: `[1,2,3]`},
318+
paramters: map[string]interface{}{},
319+
},
320+
expected: operatorTestExpected{
321+
value: false,
322+
},
323+
},
324+
{
325+
input: operatorTestInput{
326+
operator: &notInOperator{arg1: "1", arg2: `["1","2","3"]`},
327+
paramters: map[string]interface{}{},
328+
},
329+
expected: operatorTestExpected{
330+
value: true,
331+
},
332+
},
333+
}
334+
batchOperatorTests(t, tests)
335+
}

script/standard/engine.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,13 @@ import (
88
"github.com/evilmonkeyinc/jsonpath/script"
99
)
1010

11-
// TODO : add tests for what is in readme
12-
// TODO : update readme to give more details, maybe add readme to this package and link from main
1311
// TODO : add support for bitwise operators | &^ ^ & << >> after + and -
1412
var defaultTokens []string = []string{
1513
"||", "&&",
1614
"==", "!=", "<=", ">=", "<", ">", "=~", "!",
1715
"+", "-",
1816
"**", "*", "/", "%",
19-
"@", "$",
17+
"not in", "in", "@", "$",
2018
}
2119

2220
// ScriptEngine standard implementation of the script engine interface
@@ -123,6 +121,16 @@ func (engine *ScriptEngine) buildOperators(expression string, tokens []string, o
123121
arg1: leftside,
124122
arg2: rightside,
125123
}, nil
124+
case "not in":
125+
return &notInOperator{
126+
arg1: leftside,
127+
arg2: rightside,
128+
}, nil
129+
case "in":
130+
return &inOperator{
131+
arg1: leftside,
132+
arg2: rightside,
133+
}, nil
126134
case "!":
127135
if leftside != nil {
128136
// There should not be a left side to this operator

script/standard/engine_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,39 @@ func Test_ScriptEngine_buildOperators(t *testing.T) {
716716
err: "",
717717
},
718718
},
719+
{
720+
input: input{
721+
expression: "1 in [1]",
722+
tokens: defaultTokens,
723+
},
724+
expected: expected{
725+
operator: &inOperator{arg1: "1", arg2: []interface{}{float64(1)}},
726+
err: "",
727+
},
728+
},
729+
{
730+
input: input{
731+
expression: "1 not in [1]",
732+
tokens: defaultTokens,
733+
},
734+
expected: expected{
735+
operator: &notInOperator{arg1: "1", arg2: []interface{}{float64(1)}},
736+
err: "",
737+
},
738+
},
739+
{
740+
input: input{
741+
expression: "1 in [1] && 1 not in [2]",
742+
tokens: defaultTokens,
743+
},
744+
expected: expected{
745+
operator: &andOperator{
746+
arg1: &inOperator{arg1: "1", arg2: []interface{}{float64(1)}},
747+
arg2: &notInOperator{arg1: "1", arg2: []interface{}{float64(2)}},
748+
},
749+
err: "",
750+
},
751+
},
719752
}
720753

721754
for idx, test := range tests {

script/standard/errors.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import (
77
)
88

99
var (
10-
errUnsupportedOperator error = fmt.Errorf("unsupported operator")
11-
errInvalidArgument error = fmt.Errorf("invalid argument")
12-
errInvalidArgumentNil error = fmt.Errorf("%w. is nil", errInvalidArgument)
13-
errInvalidArgumentExpectedInteger error = fmt.Errorf("%w. expected integer", errInvalidArgument)
14-
errInvalidArgumentExpectedNumber error = fmt.Errorf("%w. expected number", errInvalidArgument)
15-
errInvalidArgumentExpectedBoolean error = fmt.Errorf("%w. expected boolean", errInvalidArgument)
16-
errInvalidArgumentExpectedRegex error = fmt.Errorf("%w. expected a valid regexp", errInvalidArgument)
10+
errUnsupportedOperator error = fmt.Errorf("unsupported operator")
11+
errInvalidArgument error = fmt.Errorf("invalid argument")
12+
errInvalidArgumentNil error = fmt.Errorf("%w. is nil", errInvalidArgument)
13+
errInvalidArgumentExpectedInteger error = fmt.Errorf("%w. expected integer", errInvalidArgument)
14+
errInvalidArgumentExpectedNumber error = fmt.Errorf("%w. expected number", errInvalidArgument)
15+
errInvalidArgumentExpectedBoolean error = fmt.Errorf("%w. expected boolean", errInvalidArgument)
16+
errInvalidArgumentExpectedRegex error = fmt.Errorf("%w. expected a valid regexp", errInvalidArgument)
17+
errInvalidArgumentExpectedCollection error = fmt.Errorf("%w. expected array, map, or slice", errInvalidArgument)
1718
)
1819

1920
func getInvalidExpressionEmptyError() error {

0 commit comments

Comments
 (0)