Skip to content

Commit

Permalink
source/custom: implement 'GtLt' operator
Browse files Browse the repository at this point in the history
A new operator for checking that an input (integer) is between two
values.
  • Loading branch information
marquiz committed Nov 11, 2021
1 parent e017775 commit c586018
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 2 deletions.
37 changes: 35 additions & 2 deletions source/custom/expression/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ type MatchExpression struct {
// Value is the list of values that the operand evaluates the input
// against. Value should be empty if the operator is Exists, DoesNotExist,
// IsTrue or IsFalse. Value should contain exactly one element if the
// operator is Gt or Lt. In other cases Value should contain at least one
// element.
// operator is Gt or Lt and exactly two elements if the operator is GtLt.
// In other cases Value should contain at least one element.
Value MatchValue `json:",omitempty"`

// valueRe caches compiled regexps for "InRegexp" operator
Expand Down Expand Up @@ -89,6 +89,11 @@ const (
// Both the input and value must be integer numbers, otherwise an error is
// returned.
MatchLt MatchOp = "Lt"
// MatchGtLt returns true if the input is between two values, i.e. greater
// than the first value and less than the second value of the expression
// (number of values in the expression must be exactly two). Both the input
// and values must be integer numbers, otherwise an error is returned.
MatchGtLt MatchOp = "GtLt"
// MatchIsTrue returns true if the input holds the value "true". The
// expression must not have any values.
MatchIsTrue MatchOp = "IsTrue"
Expand All @@ -106,6 +111,7 @@ var matchOps = map[MatchOp]struct{}{
MatchDoesNotExist: struct{}{},
MatchGt: struct{}{},
MatchLt: struct{}{},
MatchGtLt: struct{}{},
MatchIsTrue: struct{}{},
MatchIsFalse: struct{}{},
}
Expand Down Expand Up @@ -154,6 +160,20 @@ func (m *MatchExpression) Validate() error {
if _, err := strconv.Atoi(m.Value[0]); err != nil {
return fmt.Errorf("Value must be an integer for Op %q (have %v)", m.Op, m.Value[0])
}
case MatchGtLt:
if len(m.Value) != 2 {
return fmt.Errorf("Value must contain exactly two elements for Op %q (have %v)", m.Op, m.Value)
}
var err error
v := make([]int, 2)
for i := 0; i < 2; i++ {
if v[i], err = strconv.Atoi(m.Value[i]); err != nil {
return fmt.Errorf("Value must contain integers for Op %q (have %v)", m.Op, m.Value)
}
}
if v[0] >= v[1] {
return fmt.Errorf("Value[0] must be less than Value[1] for Op %q (have %v)", m.Op, m.Value)
}
case MatchInRegexp:
if len(m.Value) == 0 {
return fmt.Errorf("Value must be non-empty for Op %q", m.Op)
Expand Down Expand Up @@ -223,6 +243,19 @@ func (m *MatchExpression) Match(valid bool, value interface{}) (bool, error) {
if (l < r && m.Op == MatchLt) || (l > r && m.Op == MatchGt) {
return true, nil
}
case MatchGtLt:
v, err := strconv.Atoi(value)
if err != nil {
return false, fmt.Errorf("not a number %q", value)
}
lr := make([]int, 2)
for i := 0; i < 2; i++ {
lr[i], err = strconv.Atoi(m.Value[i])
if err != nil {
return false, fmt.Errorf("not a number %q in %v", m.Value[i], m)
}
}
return v > lr[0] && v < lr[1], nil
case MatchIsTrue:
return value == "true", nil
case MatchIsFalse:
Expand Down
21 changes: 21 additions & 0 deletions source/custom/expression/expression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ func TestCreateMatchExpression(t *testing.T) {
{op: e.MatchLt, values: V{"1", "2", "3"}, err: assert.NotNilf},
{op: e.MatchLt, values: V{"a"}, err: assert.NotNilf},

{op: e.MatchGtLt, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"1"}, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"1", "2"}, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"2", "1"}, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"1", "2", "3"}, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"a", "2"}, err: assert.NotNilf},

{op: e.MatchIsTrue, err: assert.Nilf},
{op: e.MatchIsTrue, values: V{"1"}, err: assert.NotNilf},

Expand Down Expand Up @@ -134,6 +141,12 @@ func TestMatch(t *testing.T) {
{op: e.MatchLt, values: V{"2"}, input: "1", valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchLt, values: V{"2"}, input: "1.0", valid: true, result: assert.Falsef, err: assert.NotNilf},

{op: e.MatchGtLt, values: V{"1", "10"}, input: "1", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"1", "10"}, input: "1", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"1", "10"}, input: "10", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"1", "10"}, input: "2", valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"1", "10"}, input: "1.0", valid: true, result: assert.Falsef, err: assert.NotNilf},

{op: e.MatchIsTrue, input: true, valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIsTrue, input: true, valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchIsTrue, input: false, valid: true, result: assert.Falsef, err: assert.Nilf},
Expand All @@ -155,6 +168,7 @@ func TestMatch(t *testing.T) {

{op: e.MatchGt, values: V{"3.0"}, input: 1, valid: true},
{op: e.MatchLt, values: V{"0x2"}, input: 1, valid: true},
{op: e.MatchGtLt, values: V{"1", "str"}, input: 1, valid: true},
{op: "non-existent-op", values: V{"1"}, input: 1, valid: true},
}

Expand Down Expand Up @@ -196,6 +210,7 @@ func TestMatchKeys(t *testing.T) {
{op: e.MatchInRegexp, values: V{"foo"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchGt, values: V{"1"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchLt, values: V{"1"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"1", "10"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchIsTrue, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchIsFalse, name: "foo", result: assert.Falsef, err: assert.NotNilf},
}
Expand Down Expand Up @@ -252,6 +267,12 @@ func TestMatchValues(t *testing.T) {
{op: e.MatchLt, values: V{"2"}, name: "foo", input: I{"bar": "1", "foo": "1"}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchLt, values: V{"2"}, name: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.Falsef, err: assert.NotNilf},

{op: e.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1", "foo": "11"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1", "foo": "-11"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1", "foo": "1"}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.Falsef, err: assert.NotNilf},

{op: e.MatchIsTrue, name: "foo", result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIsTrue, name: "foo", input: I{"foo": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIsTrue, name: "foo", input: I{"foo": "true"}, result: assert.Truef, err: assert.Nilf},
Expand Down

0 comments on commit c586018

Please sign in to comment.