Skip to content

Commit

Permalink
Merge pull request skyhackvip#12 from skyhackvip/feature/6
Browse files Browse the repository at this point in the history
eval compare
  • Loading branch information
skyhackvip committed Nov 1, 2022
2 parents b63d492 + db0c94d commit 0d0f63d
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 57 deletions.
18 changes: 18 additions & 0 deletions configs/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const (
AFTER = "AFTER"
KEYEXIST = "KEYEXIST"
VALUEEXIST = "VALUEEXIST"
AND = "and"
OR = "or"
)

var OperatorMap = map[string]string{
Expand All @@ -33,6 +35,8 @@ var OperatorMap = map[string]string{
AFTER: "after",
KEYEXIST: "keyexist",
VALUEEXIST: "valueexist",
AND: "&&",
OR: "||",
}

var NumSupportOperator = map[string]struct{}{
Expand Down Expand Up @@ -81,6 +85,20 @@ var DefaultSupportOperator = map[string]struct{}{
NEQ: struct{}{},
}

var CompareOperators = map[string]struct{}{
EQ: struct{}{},
NEQ: struct{}{},
GT: struct{}{},
LT: struct{}{},
GE: struct{}{},
LE: struct{}{},
}

var BooleanOperators = map[string]string{
AND: OperatorMap[AND],
OR: OperatorMap[OR],
}

//all support node
const (
START = "start"
Expand Down
13 changes: 6 additions & 7 deletions core/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func (feature *TypeNumFeature) Compare(op string, target interface{}) (bool, err
case "NEQ":
rs, err := operator.Compare(op, value, target)
return rs, err
case "BETWEEN":
case "BETWEEN": //todo: [ ( ) ]
if t, ok := target.([]interface{}); !ok {
return false, errcode.ParseErrorTargetMustBeArray
} else {
Expand All @@ -189,7 +189,6 @@ func (feature *TypeNumFeature) Compare(op string, target interface{}) (bool, err
return false, err
}
return rs1 && rs2, nil

}
case "IN":
if t, ok := target.([]interface{}); !ok {
Expand Down Expand Up @@ -358,7 +357,7 @@ func (feature *TypeDateFeature) Compare(op string, target interface{}) (bool, er
var err error
switch target.(type) {
case string:
targetTime, err = util.StringToDate(target.(string))
targetTime, err = util.ToDate(target.(string))
if err != nil {
return false, err
}
Expand All @@ -368,11 +367,11 @@ func (feature *TypeDateFeature) Compare(op string, target interface{}) (bool, er
if targetArr := target.([]string); len(targetArr) != 2 {
return false, errcode.ParseErrorTargetNotSupport
} else {
targetTimeLeft, err = util.StringToDate(targetArr[0])
targetTimeLeft, err = util.ToDate(targetArr[0])
if err != nil {
return false, err
}
targetTimeRight, err = util.StringToDate(targetArr[1])
targetTimeRight, err = util.ToDate(targetArr[1])
if err != nil {
return false, err
}
Expand Down Expand Up @@ -466,9 +465,9 @@ func (feature *TypeArrayFeature) Compare(op string, target interface{}) (bool, e
}
switch op {
case configs.EQ:
return operator.CompareArray(valueArr, targetArr), nil
return operator.Compare(op, valueArr, targetArr)
case configs.NEQ:
return !operator.CompareArray(valueArr, targetArr), nil
return operator.Compare(op, valueArr, targetArr)
case configs.IN:
return operator.AInB(valueArr, targetArr), nil
case configs.CONTAIN:
Expand Down
8 changes: 6 additions & 2 deletions internal/errcode/common_error.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package errcode

var (
ErrorFeatureTypeUnknow = NewError(2000001, "feature type support int,float,bool,string,date,array,map")
ErrorTypeConvert = NewError(2000002, "type convert error")
ErrorFeatureTypeUnknow = NewError(2000001, "feature type support int,float,bool,string,date,array,map")
ErrorTypeConvert = NewError(2000002, "type convert error")
ErrorNotSupportOperator = NewError(2000003, "not support operator")
ErrorNotANumber = NewError(2000004, "not a number")
ErrorBooleanValEmpty = NewError(2000005, "boolean operator value is empty")
ErrorBooleanValLack = NewError(2000006, "boolean operator value lack")
)
26 changes: 3 additions & 23 deletions internal/operator/base.go → internal/operator/array_op.go
Original file line number Diff line number Diff line change
@@ -1,39 +1,19 @@
package operator

import (
"errors"
"github.com/Knetic/govaluate"
"github.com/skyhackvip/risk_engine/internal/log"
)

func Evaluate(exprStr string, params map[string]interface{}) (bool, error) {
expr, err := govaluate.NewEvaluableExpression(exprStr)
log.Infof("base evaluate: %v", expr, params)
if err != nil {
return false, err
}
eval, err := expr.Evaluate(params)
if err != nil {
return false, err
}
if result, ok := eval.(bool); ok {
return result, nil
}
return false, errors.New("convert error")
}

//jundge val in arr
func InArray(arr []interface{}, val interface{}) bool {
if len(arr) == 0 {
return false
}
for _, v := range arr {
if v == val {
if ok, err := Compare("EQ", v, val); err != nil && ok {
return true
}
}
return false
}

//jundge array A in Array B
func AInB(a []interface{}, b []interface{}) bool {
if len(b) == 0 {
return false
Expand Down
133 changes: 122 additions & 11 deletions internal/operator/compare.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,108 @@
package operator

import (
"errors"
"fmt"
"github.com/skyhackvip/risk_engine/configs"
"github.com/skyhackvip/risk_engine/internal/errcode"
"github.com/skyhackvip/risk_engine/internal/log"
"github.com/skyhackvip/risk_engine/internal/util"
)

//compare expression:left [><=] right
//compare operator expression
//left [operator] right
//operator: [eq,neq,gt,lt,ge,le]
func Compare(operator string, left interface{}, right interface{}) (bool, error) {
var params = make(map[string]interface{})
params["left"] = left
params["right"] = right
log.Infof("compare operator: %v", operator, left, right)
if _, ok := configs.CompareOperators[operator]; !ok {
log.Errorf("not compare operator: %v", operator)
return false, errcode.ErrorNotSupportOperator
}

switch operator {
case configs.EQ:
return equal(left, right)
case configs.NEQ:
rs, err := equal(left, right)
return !rs, err

if _, ok := configs.OperatorMap[operator]; !ok {
return false, errors.New("not support operator")
//only number can compare(gt,lt,ge,le)
case configs.GT:
return numCompare(left, right, operator)
case configs.LT:
return numCompare(left, right, operator)
case configs.GE:
return numCompare(left, right, operator)
case configs.LE:
return numCompare(left, right, operator)
}
expr := fmt.Sprintf("left %s right", configs.OperatorMap[operator])
return false, errcode.ErrorNotSupportOperator
}

return Evaluate(expr, params)
//jundge left == right
func equal(left, right interface{}) (bool, error) {
leftType, err := util.GetType(left)
if err != nil {
log.Errorf("left type unknow: %v", left, err)
return false, err //unknow type
}
rightType, err := util.GetType(right)
if err != nil {
log.Errorf("right type unknow: %v", right, err)
return false, err
}
if !util.MatchType(leftType, rightType) {
return false, nil
}
if leftType == configs.ARRAY {
return arrayEqual(left.([]interface{}), right.([]interface{})), nil
}
if leftType == configs.MAP {
return false, errcode.ErrorNotSupportOperator
}
if leftType == configs.STRING {
return left.(string) == right.(string), nil
}
if leftType == configs.BOOL {
leftBool, err := util.ToBool(left)
if err != nil {
return false, err
}
rightBool, err := util.ToBool(right)
if err != nil {
return false, err
}
return leftBool == rightBool, nil
}
if leftType == configs.INT || leftType == configs.FLOAT {
leftNum, err := util.ToFloat64(left)
if err != nil {
return false, err
}
rightNum, err := util.ToFloat64(right)
if err != nil {
return false, err
}
return numCompare(leftNum, rightNum, configs.EQ)
}
if leftType == configs.DATE {
leftDate, err := util.ToDate(left)
if err != nil {
return false, err
}
rightDate, err := util.ToDate(right)
if err != nil {
return false, err
}
return leftDate.Equal(rightDate), nil
}
if leftType == configs.DEFAULT {
return left == right, nil
}
return false, errcode.ErrorNotSupportOperator
}

func CompareArray(a, b []interface{}) bool {
//a == b true
//a != b false
func arrayEqual(a, b []interface{}) bool {
if len(a) != len(b) {
return false
}
Expand All @@ -39,3 +121,32 @@ func CompareArray(a, b []interface{}) bool {
}
return true
}

//compare number (lt, gt, le, ge, eq, neq)
//only number can compare
func numCompare(left, right interface{}, op string) (bool, error) {
leftNum, err := util.ToFloat64(left)
if err != nil {
return false, errcode.ErrorNotANumber
}
rightNum, err := util.ToFloat64(right)
if err != nil {
return false, errcode.ErrorNotANumber
}
switch op {
case configs.EQ:
return leftNum == rightNum, nil
case configs.NEQ:
return leftNum != rightNum, nil
case configs.GT:
return leftNum > rightNum, nil
case configs.LT:
return leftNum < rightNum, nil
case configs.GE:
return leftNum >= rightNum, nil
case configs.LE:
return leftNum <= rightNum, nil
default:
return false, errcode.ErrorNotSupportOperator
}
}
24 changes: 24 additions & 0 deletions internal/operator/eval.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package operator

import (
"errors"
"github.com/Knetic/govaluate"
"github.com/skyhackvip/risk_engine/internal/log"
)

//using govalute to execute expression
func Evaluate(exprStr string, params map[string]interface{}) (bool, error) {
expr, err := govaluate.NewEvaluableExpression(exprStr)
log.Infof("base evaluate: %v", expr, params)
if err != nil {
return false, err
}
eval, err := expr.Evaluate(params)
if err != nil {
return false, err
}
if result, ok := eval.(bool); ok {
return result, nil
}
return false, errors.New("convert error")
}
39 changes: 34 additions & 5 deletions internal/operator/operator_test.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,46 @@
package operator

import (
"github.com/skyhackvip/risk_engine/internal/log"
"testing"
"time"
)

func TestCompareArray(t *testing.T) {
t.Log(CompareArray([]interface{}{3, 8, 7, 6, 9}, []interface{}{9, 6, 7, 3, 8}))
t.Log(CompareArray([]interface{}{"a", "b", "d", "c", "e"}, []interface{}{"a", "b", "c", "d", "e"}))
}

func TestAInB(t *testing.T) {
t.Log(AInB([]interface{}{1, 3, 5}, []interface{}{1, 2, 3, 4, 5}))
t.Log(AInB([]interface{}{1, 3, 5}, []interface{}{1, 4, 5, 6}))
t.Log(AInB([]interface{}{1, 3, 5}, []interface{}{1, 3}))
t.Log(AInB([]interface{}{1, 3, 5}, []interface{}{5, 8, 7, 3, 1}))
}

func TestCompare(t *testing.T) {
log.InitLogger("console", "")
t.Log("------eq-------")
t.Log(Compare("EQ", 3, 3.0))
t.Log(Compare("EQ", 3, "3.000"))
t.Log(Compare("EQ", "3", "3.000"))
t.Log(Compare("EQ", "3.0", "3.000"))
t.Log(Compare("EQ", "3.0", "3.0r00"))
t.Log(Compare("EQ", "aa", "aa"))
t.Log(Compare("EQ", "true", true))
t.Log(Compare("EQ", "true", "true"))
t.Log(Compare("EQ", true, true))

t.Log("------ge-------")
t.Log(Compare("GE", "3.0", "2.99999999"))
t.Log(Compare("GE", "3", "2.99999999"))
t.Log(Compare("GE", "3", "2"))
t.Log(Compare("GE", 3, 2))
t.Log(Compare("GE", 3, 2.99999999))
t.Log(Compare("GE", 3.0, 2.99999999))
t.Log(Compare("GE", 3.0, 3.000000000))

t.Log("------date-----")
t.Log(Compare("EQ", "2022-10-10", "2022-10-10 00:00:00"))
now := time.Now()
t.Log(Compare("EQ", now, now))

t.Log("------array-----")
t.Log(Compare("EQ", []interface{}{3, 8, 7, 6, 9}, []interface{}{9, 6, 7, 3, 8}))
t.Log(Compare("EQ", []interface{}{"a", "b", "d", "c", "e"}, []interface{}{"a", "b", "c", "d", "e"}))
}
Loading

0 comments on commit 0d0f63d

Please sign in to comment.