condition language parser and tests

This commit is contained in:
Radu Ioan Fericean
2016-01-18 22:03:11 +02:00
parent e0c8ea02c5
commit 5e7bdbcd97
4 changed files with 399 additions and 63 deletions

View File

@@ -697,17 +697,20 @@ func (acc *Account) DebitConnectionFee(cc *CallCost, usefulMoneyBalances Balance
}
}
func (acc *Account) matchConditions(condition string) (bool, error) {
condMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(condition), condMap); err != nil {
return false, err
func (acc *Account) matchConditions(condition, balanceType string) (bool, error) {
cl := &utils.CondLoader{}
cl.Parse(condition)
for _, b := range acc.BalanceMap[balanceType] {
check, err := cl.Check(b)
if err != nil {
return false, err
}
if check {
return true, nil
}
}
operator, found := condMap["Operator"]
if !found {
operator = utils.COND_EQ
}
_ = operator
return true, nil
return false, nil
}
// used in some api for transition

View File

@@ -628,7 +628,7 @@ func transferMonetaryDefault(acc *Account, sq *StatsQueueTriggered, a *Action, a
}
func conditionalDebitAction(acc *Account, sq *StatsQueueTriggered, a *Action, acs Actions) error {
if matched, err := acc.matchConditions(a.ExtraParameters); matched {
if matched, err := acc.matchConditions(a.ExtraParameters, a.BalanceType); matched {
return debitAction(acc, sq, a, acs)
} else {
return err
@@ -636,7 +636,7 @@ func conditionalDebitAction(acc *Account, sq *StatsQueueTriggered, a *Action, ac
}
func conditionalDebitResetAction(acc *Account, sq *StatsQueueTriggered, a *Action, acs Actions) error {
if matched, err := acc.matchConditions(a.ExtraParameters); matched {
if matched, err := acc.matchConditions(a.ExtraParameters, a.BalanceType); matched {
return debitResetAction(acc, sq, a, acs)
} else {
return err
@@ -644,7 +644,7 @@ func conditionalDebitResetAction(acc *Account, sq *StatsQueueTriggered, a *Actio
}
func conditionalTopupAction(acc *Account, sq *StatsQueueTriggered, a *Action, acs Actions) error {
if matched, err := acc.matchConditions(a.ExtraParameters); matched {
if matched, err := acc.matchConditions(a.ExtraParameters, a.BalanceType); matched {
return topupAction(acc, sq, a, acs)
} else {
return err
@@ -652,7 +652,7 @@ func conditionalTopupAction(acc *Account, sq *StatsQueueTriggered, a *Action, ac
}
func conditionalTopupResetAction(acc *Account, sq *StatsQueueTriggered, a *Action, acs Actions) error {
if matched, err := acc.matchConditions(a.ExtraParameters); matched {
if matched, err := acc.matchConditions(a.ExtraParameters, a.BalanceType); matched {
return topupResetAction(acc, sq, a, acs)
} else {
return err

View File

@@ -2,88 +2,164 @@ package utils
import (
"encoding/json"
"errors"
"reflect"
"strings"
)
const (
COND_EQ = "*eq"
COND_GT = "*gt"
COND_LT = "*lt"
COND_EXP = "*exp"
CondEQ = "*eq"
CondGT = "*gt"
CondGTE = "*gte"
CondLT = "*lt"
CondLTE = "*lte"
CondEXP = "*exp"
CondOR = "*or"
CondAND = "*and"
)
type CondElement interface {
AddChild(CondElement)
CheckStruct(interface{}) (bool, error)
var (
ErrNotNumerical = errors.New("NOT_NUMERICAL")
)
type condElement interface {
addChild(condElement) error
checkStruct(interface{}) (bool, error)
}
type OperatorSlice struct {
Operator string
Slice []CondElement
type operatorSlice struct {
operator string
slice []condElement
}
func (os *OperatorSlice) AddChild(ce CondElement) {
os.Slice = append(os.Slice, ce)
func (os *operatorSlice) addChild(ce condElement) error {
os.slice = append(os.slice, ce)
return nil
}
func (os *OperatorSlice) CheckStruct(o interface{}) (bool, error) { return true, nil }
type KeyStruct struct {
Key string
Struct CondElement
func (os *operatorSlice) checkStruct(o interface{}) (bool, error) {
switch os.operator {
case CondOR:
for _, cond := range os.slice {
check, err := cond.checkStruct(o)
if err != nil {
return false, err
}
if check {
return true, nil
}
}
case CondAND:
accumulator := true
for _, cond := range os.slice {
check, err := cond.checkStruct(o)
if err != nil {
return false, err
}
accumulator = accumulator && check
}
return accumulator, nil
}
return false, nil
}
func (ks *KeyStruct) AddChild(ce CondElement) {
ks.Struct = ce
}
func (ks *KeyStruct) CheckStruct(o interface{}) (bool, error) { return true, nil }
type OperatorValue struct {
Operator string
Value interface{}
type keyStruct struct {
key string
elem condElement
}
func (ov *OperatorValue) AddChild(CondElement) {}
func (ov *OperatorValue) CheckStruct(o interface{}) (bool, error) { return true, nil }
type KeyValue struct {
Key string
Value interface{}
func (ks *keyStruct) addChild(ce condElement) error {
ks.elem = ce
return nil
}
func (ks *keyStruct) checkStruct(o interface{}) (bool, error) {
obj := reflect.ValueOf(o)
if obj.Kind() == reflect.Ptr {
obj = obj.Elem()
}
value := obj.FieldByName(ks.key)
return ks.elem.checkStruct(value.Interface())
}
func (os *KeyValue) AddChild(CondElement) {}
func (os *KeyValue) CheckStruct(o interface{}) (bool, error) { return true, nil }
type operatorValue struct {
operator string
value interface{}
}
func (ov *operatorValue) addChild(condElement) error { return ErrNotImplemented }
func (ov *operatorValue) checkStruct(o interface{}) (bool, error) {
if ov.operator == CondEQ {
return ov.value == o, nil
}
var of, vf float64
var ok bool
if of, ok = o.(float64); !ok {
return false, ErrNotNumerical
}
if vf, ok = ov.value.(float64); !ok {
return false, ErrNotNumerical
}
switch ov.operator {
case CondGT:
return of > vf, nil
case CondGTE:
return of >= vf, nil
case CondLT:
return of < vf, nil
case CondLTE:
return of <= vf, nil
}
return true, nil
}
type keyValue struct {
key string
value interface{}
}
func (kv *keyValue) addChild(condElement) error { return ErrNotImplemented }
func (kv *keyValue) checkStruct(o interface{}) (bool, error) {
obj := reflect.ValueOf(o)
if obj.Kind() == reflect.Ptr {
obj = obj.Elem()
}
value := obj.FieldByName(kv.key)
return value.Interface() == kv.value, nil
}
func isOperator(s string) bool {
return strings.HasPrefix(s, "*")
}
type CondLoader struct{}
type CondLoader struct {
rootElement condElement
}
func (cp *CondLoader) Load(a map[string]interface{}, parentElement CondElement) (CondElement, error) {
func (cp *CondLoader) load(a map[string]interface{}, parentElement condElement) (condElement, error) {
for key, value := range a {
var currentElement CondElement
var currentElement condElement
switch t := value.(type) {
case []interface{}:
currentElement = &OperatorSlice{Operator: key}
currentElement = &operatorSlice{operator: key}
for _, e := range t {
cp.Load(e.(map[string]interface{}), currentElement)
cp.load(e.(map[string]interface{}), currentElement)
}
case map[string]interface{}:
currentElement = &KeyStruct{Key: key}
currentElement = &keyStruct{key: key}
//log.Print("map: ", t)
cp.Load(t, currentElement)
cp.load(t, currentElement)
case interface{}:
if isOperator(key) {
currentElement = &OperatorValue{Operator: key, Value: t}
currentElement = &operatorValue{operator: key, value: t}
} else {
currentElement = &KeyValue{Key: key, Value: t}
currentElement = &keyValue{key: key, value: t}
}
//log.Print("generic interface: ", t)
default:
return nil, ErrParserError
}
if parentElement != nil {
parentElement.AddChild(currentElement)
parentElement.addChild(currentElement)
} else {
return currentElement, nil
}
@@ -91,10 +167,15 @@ func (cp *CondLoader) Load(a map[string]interface{}, parentElement CondElement)
return nil, nil
}
func (cp *CondLoader) Parse(s string) (root CondElement, err error) {
func (cp *CondLoader) Parse(s string) (err error) {
a := make(map[string]interface{})
if err := json.Unmarshal([]byte([]byte(s)), &a); err != nil {
return nil, err
return err
}
return cp.Load(a, nil)
cp.rootElement, err = cp.load(a, nil)
return
}
func (cp *CondLoader) Check(o interface{}) (bool, error) {
return cp.rootElement.checkStruct(o)
}

View File

@@ -4,8 +4,260 @@ import "testing"
func TestCondLoader(t *testing.T) {
cl := &CondLoader{}
root, err := cl.Parse(`{"*or":[{"test":1},{"field":{"*gt":1}},{"best":"coco"}]}`)
if err != nil || root == nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(root), err)
err := cl.Parse(`{"*or":[{"test":1},{"field":{"*gt":1}},{"best":"coco"}]}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
}
func TestCondKeyValue(t *testing.T) {
o := struct {
Test string
Field float64
Other bool
}{
Test: "test",
Field: 6.0,
Other: true,
}
cl := &CondLoader{}
err := cl.Parse(`{"Test":"test"}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":6}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"Other":true}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
}
func TestCondKeyValuePointer(t *testing.T) {
o := &struct {
Test string
Field float64
Other bool
}{
Test: "test",
Field: 6.0,
Other: true,
}
cl := &CondLoader{}
err := cl.Parse(`{"Test":"test"}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":6}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"Other":true}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
}
func TestCondOperatorValue(t *testing.T) {
root := &operatorValue{operator: "*gt", value: 3.4}
if check, err := root.checkStruct(3.5); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(root))
}
root = &operatorValue{operator: "*eq", value: 3.4}
if check, err := root.checkStruct(3.5); check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(root))
}
root = &operatorValue{operator: "*eq", value: 3.4}
if check, err := root.checkStruct(3.4); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(root))
}
root = &operatorValue{operator: "*eq", value: "zinc"}
if check, err := root.checkStruct("zinc"); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(root))
}
}
func TestCondKeyStruct(t *testing.T) {
o := struct {
Test string
Field float64
Other bool
}{
Test: "test",
Field: 6.0,
Other: true,
}
cl := &CondLoader{}
err := cl.Parse(`{"Field":{"*gt": 5}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"Test":{"*gt": 5}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); check || err != ErrNotNumerical {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":{"*gte": 6}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":{"*lt": 7}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":{"*lte": 6}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":{"*eq": 6}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"Test":{"*eq": "test"}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
}
func TestCondKeyStructPointer(t *testing.T) {
o := &struct {
Test string
Field float64
Other bool
}{
Test: "test",
Field: 6.0,
Other: true,
}
cl := &CondLoader{}
err := cl.Parse(`{"Field":{"*gt": 5}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"Test":{"*gt": 5}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); check || err != ErrNotNumerical {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":{"*gte": 6}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":{"*lt": 7}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":{"*lte": 6}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":{"*eq": 6}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"Test":{"*eq": "test"}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
}
func TestCondOperatorSlice(t *testing.T) {
o := &struct {
Test string
Field float64
Other bool
}{
Test: "test",
Field: 6.0,
Other: true,
}
cl := &CondLoader{}
err := cl.Parse(`{"*or":[{"Test":"test"},{"Field":{"*gt":5}},{"Other":true}]}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"*or":[{"Test":"test"},{"Field":{"*gt":7}},{"Other":false}]}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"*and":[{"Test":"test"},{"Field":{"*gt":5}},{"Other":true}]}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"*and":[{"Test":"test"},{"Field":{"*gt":7}},{"Other":false}]}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
}