Skip to content

Commit

Permalink
[FFM-1452]: Fix bool, int and number comparisons + implement slices
Browse files Browse the repository at this point in the history
This change fixes the bool, int and number comparisons.  It replaces
a cast with the appropriate function from the strconv package.

The slice type is implemented with support for in, contains and equals
operators.

Customers can store target attributes as strings, float64, bool and slices.
When creating a rule the value is always a slice of strings.

The code was trying to cast a string to a int, float etc which does not work.
This caused rules evaluations to fail.

Added unit tests to verify the type operators now work as expected.
  • Loading branch information
davejohnston committed Oct 1, 2021
1 parent 42cda01 commit 52e6d19
Show file tree
Hide file tree
Showing 11 changed files with 437 additions and 111 deletions.
4 changes: 3 additions & 1 deletion evaluation/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ func (t Target) GetOperator(attr string) (types.ValueType, error) {
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int8, reflect.Uint, reflect.Uint16,
reflect.Uint32, reflect.Uint64, reflect.Uint8:
return types.Integer(value.Int()), nil
case reflect.Slice:
return types.NewSlice(value.Interface()), nil
case reflect.Array, reflect.Chan, reflect.Complex128, reflect.Complex64, reflect.Func, reflect.Interface,
reflect.Invalid, reflect.Map, reflect.Ptr, reflect.Slice, reflect.Struct, reflect.Uintptr, reflect.UnsafePointer:
reflect.Invalid, reflect.Map, reflect.Ptr, reflect.Struct, reflect.Uintptr, reflect.UnsafePointer:
fallthrough
default:
return nil, fmt.Errorf("unexpected type: %s", value.Kind().String())
Expand Down
44 changes: 28 additions & 16 deletions types/bool.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package types

import (
"fmt"
"strconv"

"github.com/drone/ff-golang-server-sdk/log"
)

// Boolean type for clause attribute evaluation
Expand All @@ -17,61 +20,70 @@ func NewBoolean(value interface{}) (*Boolean, error) {
return nil, fmt.Errorf("%v: cant cast to a booelan", ErrWrongTypeAssertion)
}

// boolOperator iterates over value, converts it to a float64 and calls fn on each element
func boolOperator(value []string, fn func(bool) bool) bool {
if len(value) > 0 {
if i, err := strconv.ParseBool(value[0]); err == nil {
return fn(i)
}
log.Warnf("input contains invalid value for bool comparisons: %s\n", value)
}
return false
}

// StartsWith always return false
func (b Boolean) StartsWith(value interface{}) bool {
func (b Boolean) StartsWith(value []string) bool {
return false
}

// EndsWith always return false
func (b Boolean) EndsWith(value interface{}) bool {
func (b Boolean) EndsWith(value []string) bool {
return false
}

// Match always return false
func (b Boolean) Match(value interface{}) bool {
func (b Boolean) Match(value []string) bool {
return false
}

// Contains always return false
func (b Boolean) Contains(value interface{}) bool {
func (b Boolean) Contains(value []string) bool {
return false
}

// EqualSensitive always return false
func (b Boolean) EqualSensitive(value interface{}) bool {
func (b Boolean) EqualSensitive(value []string) bool {
return false
}

// Equal check if the boolean and value are equal
func (b Boolean) Equal(value interface{}) bool {
val, ok := value.(bool)
if ok {
return Boolean(val) == b
}
return false
func (b Boolean) Equal(value []string) bool {
return boolOperator(value, func(f bool) bool {
return bool(b) == f
})
}

// GreaterThan always return false
func (b Boolean) GreaterThan(value interface{}) bool {
func (b Boolean) GreaterThan(value []string) bool {
return false
}

// GreaterThanEqual always return false
func (b Boolean) GreaterThanEqual(value interface{}) bool {
func (b Boolean) GreaterThanEqual(value []string) bool {
return false
}

// LessThan always return false
func (b Boolean) LessThan(value interface{}) bool {
func (b Boolean) LessThan(value []string) bool {
return false
}

// LessThanEqual always return false
func (b Boolean) LessThanEqual(value interface{}) bool {
func (b Boolean) LessThanEqual(value []string) bool {
return false
}

// In always return false
func (b Boolean) In(value interface{}) bool {
func (b Boolean) In(value []string) bool {
return false
}
25 changes: 25 additions & 0 deletions types/bool_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package types

import "testing"

func TestBoolean_Equal(t *testing.T) {
tests := []struct {
name string
b Boolean
args []string
want bool
}{
{"test equal returns true with match", Boolean(true), []string{"true"}, true},
{"test equal returns false when no match", Boolean(true), []string{"false"}, false},
{"test equal returns true with multiple values", Boolean(false), []string{"false", "true"}, true},
{"test equal only matches first value", Boolean(false), []string{"true", "false"}, false},
{"test equal returns false for invalid value", Boolean(true), []string{"on"}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.b.Equal(tt.args); got != tt.want {
t.Errorf("Equal() = %v, want %v", got, tt.want)
}
})
}
}
59 changes: 33 additions & 26 deletions types/int.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package types

import (
"fmt"
"strconv"

"github.com/drone/ff-golang-server-sdk/log"
)

// Integer type for clause attribute evaluation
Expand All @@ -17,82 +20,86 @@ func NewInteger(value interface{}) (*Integer, error) {
return nil, fmt.Errorf("%v: cant cast to a integer", ErrWrongTypeAssertion)
}

func (n Integer) operator(value interface{}, fn func(int64) bool) bool {
num, ok := value.([]int64)
if ok {
return fn(num[0])
// intOperator takes the first element from the slice, converts it to a int64 and passes to fn for processing.
// we ignore any additional elements if they exist.
func intOperator(value []string, fn func(int64) bool) bool {
if len(value) > 0 {
if i, err := strconv.ParseInt(value[0], 10, 64); err == nil {
if fn(i) {
return true
}
}
log.Warnf("input contains invalid value for integer comparisons: %s\n", value)
}

return false
}

// StartsWith always return false
func (n Integer) StartsWith(interface{}) bool {
func (n Integer) StartsWith([]string) bool {
return false
}

// EndsWith always return false
func (n Integer) EndsWith(interface{}) bool {
func (n Integer) EndsWith([]string) bool {
return false
}

// Match always return false
func (n Integer) Match(interface{}) bool {
func (n Integer) Match([]string) bool {
return false
}

// Contains always return false
func (n Integer) Contains(interface{}) bool {
func (n Integer) Contains([]string) bool {
return false
}

// EqualSensitive always return false
func (n Integer) EqualSensitive(interface{}) bool {
func (n Integer) EqualSensitive([]string) bool {
return false
}

// Equal check if the number and value are equal
func (n Integer) Equal(value interface{}) bool {
return n.operator(value, func(f int64) bool {
func (n Integer) Equal(value []string) bool {
return intOperator(value, func(f int64) bool {
return int64(n) == f
})
}

// GreaterThan checks if the number is greater than the value
func (n Integer) GreaterThan(value interface{}) bool {
return n.operator(value, func(f int64) bool {
func (n Integer) GreaterThan(value []string) bool {
return intOperator(value, func(f int64) bool {
return int64(n) > f
})
}

// GreaterThanEqual checks if the number is greater or equal than the value
func (n Integer) GreaterThanEqual(value interface{}) bool {
return n.operator(value, func(f int64) bool {
func (n Integer) GreaterThanEqual(value []string) bool {
return intOperator(value, func(f int64) bool {
return int64(n) >= f
})
}

// LessThan checks if the number is less than the value
func (n Integer) LessThan(value interface{}) bool {
return n.operator(value, func(f int64) bool {
func (n Integer) LessThan(value []string) bool {
return intOperator(value, func(f int64) bool {
return int64(n) < f
})
}

// LessThanEqual checks if the number is less or equal than the value
func (n Integer) LessThanEqual(value interface{}) bool {
return n.operator(value, func(f int64) bool {
func (n Integer) LessThanEqual(value []string) bool {
return intOperator(value, func(f int64) bool {
return int64(n) <= f
})
}

// In checks if the number exist in slice of numbers (value)
func (n Integer) In(value interface{}) bool {
array, ok := value.([]interface{})
if ok {
for _, val := range array {
if n.Equal(val) {
return true
}
func (n Integer) In(value []string) bool {
for _, x := range value {
if n.Equal([]string{x}) {
return true
}
}
return false
Expand Down
26 changes: 26 additions & 0 deletions types/int_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package types

import "testing"

func TestInteger_Equal(t *testing.T) {

tests := []struct {
name string
n Integer
args []string
want bool
}{
{"test equal returns true with match", Integer(22), []string{"22"}, true},
{"test equal returns false when no match", Integer(22), []string{"25"}, false},
{"test equal returns true with multiple values", Integer(22), []string{"22", "23"}, true},
{"test equal only matches first value", Integer(22), []string{"23", "22"}, false},
{"test equal returns false for invalid value", Integer(22), []string{"true"}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.n.Equal(tt.args); got != tt.want {
t.Errorf("Equal() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit 52e6d19

Please sign in to comment.