From 689703be4882744d7582bac9fa35c36f9e3c339f Mon Sep 17 00:00:00 2001 From: Markus Lehtonen Date: Thu, 26 Aug 2021 15:01:52 +0300 Subject: [PATCH] source/custom: implement 'GtLt' operator A new operator for checking that an input (integer) is between two values. --- source/custom/expression/expression.go | 37 +++++++++++++++++++-- source/custom/expression/expression_test.go | 21 ++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/source/custom/expression/expression.go b/source/custom/expression/expression.go index 2ccb28cc2..6ff961f03 100644 --- a/source/custom/expression/expression.go +++ b/source/custom/expression/expression.go @@ -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 @@ -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" @@ -106,6 +111,7 @@ var matchOps = map[MatchOp]struct{}{ MatchDoesNotExist: struct{}{}, MatchGt: struct{}{}, MatchLt: struct{}{}, + MatchGtLt: struct{}{}, MatchIsTrue: struct{}{}, MatchIsFalse: struct{}{}, } @@ -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) @@ -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: diff --git a/source/custom/expression/expression_test.go b/source/custom/expression/expression_test.go index 6261336c1..94b1681e3 100644 --- a/source/custom/expression/expression_test.go +++ b/source/custom/expression/expression_test.go @@ -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}, @@ -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}, @@ -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}, } @@ -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}, } @@ -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},