Skip to content

Commit

Permalink
feat: add support for in expression operator (#23)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
evilmonkeyinc authored Feb 11, 2022
1 parent 85cff81 commit 2f1f51e
Show file tree
Hide file tree
Showing 11 changed files with 506 additions and 25 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/pull_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
go mod download
- name: Run Test
run: |
go test -v -count=10 ./...
go test -count=10 ./...
lint:
strategy:
matrix:
Expand All @@ -51,7 +51,7 @@ jobs:
release_check:
runs-on: ubuntu-latest
outputs:
git_diff: ${{ steps.changes.outputs.git_diff }}
git_diff: ${{ steps.output.outputs.git_diff }}
steps:
- name: Checkout repository
uses: actions/checkout@v2
Expand All @@ -61,11 +61,12 @@ jobs:
uses: technote-space/get-diff-action@v6
with:
PATTERNS: |
+**/*.go
**/*.go
!**/*_test.go
FILES: |
go.mod
- name: Output Diff
id: output
if: env.GIT_DIFF
run: |
echo "::set-output name=git_diff::${{ env.GIT_DIFF }}"
Expand Down
9 changes: 5 additions & 4 deletions .github/workflows/push_main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
go mod download
- name: Run Test
run: |
go test -v -count=10 ./...
go test -count=10 ./...
lint:
strategy:
matrix:
Expand Down Expand Up @@ -61,7 +61,7 @@ jobs:
go mod download
- name: Run Test
run: |
go test -v -coverprofile=coverage.txt -covermode=atomic -count=10 ./...
go test -coverprofile=coverage.txt -covermode=atomic -count=10 ./...
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2
with:
Expand All @@ -73,7 +73,7 @@ jobs:
runs-on: ubuntu-latest
needs: [test, lint]
outputs:
git_diff: ${{ steps.changes.outputs.git_diff }}
git_diff: ${{ steps.output.outputs.git_diff }}
steps:
- name: Checkout repository
uses: actions/checkout@v2
Expand All @@ -83,12 +83,13 @@ jobs:
uses: technote-space/get-diff-action@v6
with:
PATTERNS: |
+**/*.go
**/*.go
!**/*_test.go
FILES: |
go.mod
- name: Output Diff
if: env.GIT_DIFF
id: output
run: |
echo "::set-output name=git_diff::${{ env.GIT_DIFF }}"
release:
Expand Down
42 changes: 42 additions & 0 deletions script/standard/complex_operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,45 @@ func (op *selectorOperator) Evaluate(parameters map[string]interface{}) (interfa
}
return value, nil
}

type inOperator struct {
arg1, arg2 interface{}
}

func (op *inOperator) Evaluate(parameters map[string]interface{}) (interface{}, error) {
var item interface{} = op.arg1
if numValue, err := getNumber(op.arg1, parameters); err == nil {
item = numValue
} else if strValue, err := getString(op.arg1, parameters); err == nil {
item = strValue
} else if boolValue, err := getBoolean(op.arg1, parameters); err == nil {
item = boolValue
}

elements, err := getElements(op.arg2, parameters)
if err != nil {
return nil, err
}

for _, element := range elements {
if element == item {
return true, nil
}
}

return false, nil
}

type notInOperator struct {
arg1, arg2 interface{}
}

func (op *notInOperator) Evaluate(parameters map[string]interface{}) (interface{}, error) {
inOperator := &inOperator{arg1: op.arg1, arg2: op.arg2}
val, err := inOperator.Evaluate(parameters)
if err != nil {
return nil, err
}
boolVal := val.(bool)
return !boolVal, nil
}
160 changes: 160 additions & 0 deletions script/standard/complex_operators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,163 @@ func Test_selectorOperator(t *testing.T) {
}
batchOperatorTests(t, tests)
}

func Test_inOperator(t *testing.T) {
currentDSelector, _ := newSelectorOperator("@.d", &ScriptEngine{}, nil)

tests := []*operatorTest{
{
input: operatorTestInput{
operator: &inOperator{arg1: nil, arg2: nil},
paramters: map[string]interface{}{},
},
expected: operatorTestExpected{
err: "invalid argument. is nil",
},
},
{
input: operatorTestInput{
operator: &inOperator{arg1: nil, arg2: []interface{}{"one"}},
paramters: map[string]interface{}{},
},
expected: operatorTestExpected{
value: false,
},
},
{
input: operatorTestInput{
operator: &inOperator{arg1: "one", arg2: []interface{}{"one"}},
paramters: map[string]interface{}{},
},
expected: operatorTestExpected{
value: true,
},
},
{
input: operatorTestInput{
operator: &inOperator{arg1: "one", arg2: `["one","two"]`},
paramters: map[string]interface{}{},
},
expected: operatorTestExpected{
value: true,
},
},
{
input: operatorTestInput{
operator: &inOperator{arg1: "one", arg2: `{"1":"one","2":"two"}`},
paramters: map[string]interface{}{},
},
expected: operatorTestExpected{
value: true,
},
},
{
input: operatorTestInput{
operator: &inOperator{arg1: "1", arg2: `[1,2,3]`},
paramters: map[string]interface{}{},
},
expected: operatorTestExpected{
value: true,
},
},
{
input: operatorTestInput{
operator: &inOperator{arg1: "1", arg2: `["1","2","3"]`},
paramters: map[string]interface{}{},
},
expected: operatorTestExpected{
value: false,
},
},
{
input: operatorTestInput{
operator: &inOperator{
arg1: "2",
arg2: currentDSelector,
},
paramters: map[string]interface{}{
"@": map[string]interface{}{
"d": []interface{}{
float64(1),
float64(2),
float64(3),
},
},
},
},
expected: operatorTestExpected{
value: true,
},
},
}
batchOperatorTests(t, tests)
}

func Test_notInOperator(t *testing.T) {
tests := []*operatorTest{
{
input: operatorTestInput{
operator: &notInOperator{arg1: nil, arg2: nil},
paramters: map[string]interface{}{},
},
expected: operatorTestExpected{
err: "invalid argument. is nil",
},
},
{
input: operatorTestInput{
operator: &notInOperator{arg1: nil, arg2: []interface{}{"one"}},
paramters: map[string]interface{}{},
},
expected: operatorTestExpected{
value: true,
},
},
{
input: operatorTestInput{
operator: &notInOperator{arg1: "one", arg2: []interface{}{"one"}},
paramters: map[string]interface{}{},
},
expected: operatorTestExpected{
value: false,
},
},
{
input: operatorTestInput{
operator: &notInOperator{arg1: "one", arg2: `["one","two"]`},
paramters: map[string]interface{}{},
},
expected: operatorTestExpected{
value: false,
},
},
{
input: operatorTestInput{
operator: &notInOperator{arg1: "one", arg2: `{"1":"one","2":"two"}`},
paramters: map[string]interface{}{},
},
expected: operatorTestExpected{
value: false,
},
},
{
input: operatorTestInput{
operator: &notInOperator{arg1: "1", arg2: `[1,2,3]`},
paramters: map[string]interface{}{},
},
expected: operatorTestExpected{
value: false,
},
},
{
input: operatorTestInput{
operator: &notInOperator{arg1: "1", arg2: `["1","2","3"]`},
paramters: map[string]interface{}{},
},
expected: operatorTestExpected{
value: true,
},
},
}
batchOperatorTests(t, tests)
}
14 changes: 11 additions & 3 deletions script/standard/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@ import (
"github.com/evilmonkeyinc/jsonpath/script"
)

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

// ScriptEngine standard implementation of the script engine interface
Expand Down Expand Up @@ -123,6 +121,16 @@ func (engine *ScriptEngine) buildOperators(expression string, tokens []string, o
arg1: leftside,
arg2: rightside,
}, nil
case "not in":
return &notInOperator{
arg1: leftside,
arg2: rightside,
}, nil
case "in":
return &inOperator{
arg1: leftside,
arg2: rightside,
}, nil
case "!":
if leftside != nil {
// There should not be a left side to this operator
Expand Down
33 changes: 33 additions & 0 deletions script/standard/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,39 @@ func Test_ScriptEngine_buildOperators(t *testing.T) {
err: "",
},
},
{
input: input{
expression: "1 in [1]",
tokens: defaultTokens,
},
expected: expected{
operator: &inOperator{arg1: "1", arg2: []interface{}{float64(1)}},
err: "",
},
},
{
input: input{
expression: "1 not in [1]",
tokens: defaultTokens,
},
expected: expected{
operator: &notInOperator{arg1: "1", arg2: []interface{}{float64(1)}},
err: "",
},
},
{
input: input{
expression: "1 in [1] && 1 not in [2]",
tokens: defaultTokens,
},
expected: expected{
operator: &andOperator{
arg1: &inOperator{arg1: "1", arg2: []interface{}{float64(1)}},
arg2: &notInOperator{arg1: "1", arg2: []interface{}{float64(2)}},
},
err: "",
},
},
}

for idx, test := range tests {
Expand Down
15 changes: 8 additions & 7 deletions script/standard/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import (
)

var (
errUnsupportedOperator error = fmt.Errorf("unsupported operator")
errInvalidArgument error = fmt.Errorf("invalid argument")
errInvalidArgumentNil error = fmt.Errorf("%w. is nil", errInvalidArgument)
errInvalidArgumentExpectedInteger error = fmt.Errorf("%w. expected integer", errInvalidArgument)
errInvalidArgumentExpectedNumber error = fmt.Errorf("%w. expected number", errInvalidArgument)
errInvalidArgumentExpectedBoolean error = fmt.Errorf("%w. expected boolean", errInvalidArgument)
errInvalidArgumentExpectedRegex error = fmt.Errorf("%w. expected a valid regexp", errInvalidArgument)
errUnsupportedOperator error = fmt.Errorf("unsupported operator")
errInvalidArgument error = fmt.Errorf("invalid argument")
errInvalidArgumentNil error = fmt.Errorf("%w. is nil", errInvalidArgument)
errInvalidArgumentExpectedInteger error = fmt.Errorf("%w. expected integer", errInvalidArgument)
errInvalidArgumentExpectedNumber error = fmt.Errorf("%w. expected number", errInvalidArgument)
errInvalidArgumentExpectedBoolean error = fmt.Errorf("%w. expected boolean", errInvalidArgument)
errInvalidArgumentExpectedRegex error = fmt.Errorf("%w. expected a valid regexp", errInvalidArgument)
errInvalidArgumentExpectedCollection error = fmt.Errorf("%w. expected array, map, or slice", errInvalidArgument)
)

func getInvalidExpressionEmptyError() error {
Expand Down
Loading

0 comments on commit 2f1f51e

Please sign in to comment.