implemented *has operator and added Type field

This commit is contained in:
Radu Ioan Fericean
2016-01-19 16:12:21 +02:00
parent f9bcba9745
commit 2812eafc3e
4 changed files with 138 additions and 29 deletions

View File

@@ -697,17 +697,24 @@ func (acc *Account) DebitConnectionFee(cc *CallCost, usefulMoneyBalances Balance
}
}
func (acc *Account) matchConditions(condition, balanceType string) (bool, error) {
func (acc *Account) matchConditions(condition 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
for balanceType, balanceChain := range acc.BalanceMap {
for _, b := range balanceChain {
check, err := cl.Check(&struct {
Type string
*Balance
}{
Type: balanceType,
Balance: b,
})
if err != nil {
return false, err
}
if check {
return true, nil
}
}
}
return false, nil

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, a.BalanceType); matched {
if matched, err := acc.matchConditions(a.ExtraParameters); 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, a.BalanceType); matched {
if matched, err := acc.matchConditions(a.ExtraParameters); 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, a.BalanceType); matched {
if matched, err := acc.matchConditions(a.ExtraParameters); 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, a.BalanceType); matched {
if matched, err := acc.matchConditions(a.ExtraParameters); matched {
return topupResetAction(acc, sq, a, acs)
} else {
return err

View File

@@ -2,7 +2,7 @@ package utils
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
)
@@ -16,11 +16,12 @@ const (
CondEXP = "*exp"
CondOR = "*or"
CondAND = "*and"
CondHAS = "*has"
)
var (
ErrNotNumerical = errors.New("NOT_NUMERICAL")
)
func NewErrInvalidArgument(arg interface{}) error {
return fmt.Errorf("INVALID_ARGUMENT: %v", arg)
}
type condElement interface {
addChild(condElement) error
@@ -87,16 +88,36 @@ type operatorValue struct {
func (ov *operatorValue) addChild(condElement) error { return ErrNotImplemented }
func (ov *operatorValue) checkStruct(o interface{}) (bool, error) {
// no conversion
if ov.operator == CondEQ {
return ov.value == o, nil
}
// StringMap conversion
if ov.operator == CondHAS {
var strMap StringMap
var ok bool
if strMap, ok = o.(StringMap); !ok {
return false, NewErrInvalidArgument(o)
}
var strSlice []interface{}
if strSlice, ok = ov.value.([]interface{}); !ok {
return false, NewErrInvalidArgument(ov.value)
}
for _, str := range strSlice {
if !strMap[str.(string)] {
return false, nil
}
}
return true, nil
}
// float conversion
var of, vf float64
var ok bool
if of, ok = o.(float64); !ok {
return false, ErrNotNumerical
return false, NewErrInvalidArgument(o)
}
if vf, ok = ov.value.(float64); !ok {
return false, ErrNotNumerical
return false, NewErrInvalidArgument(ov.value)
}
switch ov.operator {
case CondGT:
@@ -140,9 +161,13 @@ func (cp *CondLoader) load(a map[string]interface{}, parentElement condElement)
var currentElement condElement
switch t := value.(type) {
case []interface{}:
currentElement = &operatorSlice{operator: key}
for _, e := range t {
cp.load(e.(map[string]interface{}), currentElement)
if key == CondHAS {
currentElement = &operatorValue{operator: key, value: t}
} else {
currentElement = &operatorSlice{operator: key}
for _, e := range t {
cp.load(e.(map[string]interface{}), currentElement)
}
}
case map[string]interface{}:
currentElement = &keyStruct{key: key}
@@ -177,5 +202,8 @@ func (cp *CondLoader) Parse(s string) (err error) {
}
func (cp *CondLoader) Check(o interface{}) (bool, error) {
if cp.rootElement == nil {
return false, ErrParserError
}
return cp.rootElement.checkStruct(o)
}

View File

@@ -1,6 +1,9 @@
package utils
import "testing"
import (
"strings"
"testing"
)
func TestCondLoader(t *testing.T) {
cl := &CondLoader{}
@@ -8,6 +11,15 @@ func TestCondLoader(t *testing.T) {
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
err = cl.Parse(`{"*has":["NAT","RET","EUR"]}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
err = cl.Parse(`{"Field":7, "Other":true}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
}
func TestCondKeyValue(t *testing.T) {
@@ -42,6 +54,20 @@ func TestCondKeyValue(t *testing.T) {
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, "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(`{"Field":7, "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) {
@@ -79,22 +105,26 @@ func TestCondKeyValuePointer(t *testing.T) {
}
func TestCondOperatorValue(t *testing.T) {
root := &operatorValue{operator: "*gt", value: 3.4}
root := &operatorValue{operator: CondGT, 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}
root = &operatorValue{operator: CondEQ, 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}
root = &operatorValue{operator: CondEQ, 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"}
root = &operatorValue{operator: CondEQ, value: "zinc"}
if check, err := root.checkStruct("zinc"); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(root))
}
root = &operatorValue{operator: CondHAS, value: []interface{}{"NAT", "RET", "EUR"}}
if check, err := root.checkStruct(StringMap{"WOR": true, "EUR": true, "NAT": true, "RET": true, "ROM": true}); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", !check, err, ToIJSON(root))
}
}
func TestCondKeyStruct(t *testing.T) {
@@ -119,7 +149,7 @@ func TestCondKeyStruct(t *testing.T) {
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); check || err != ErrNotNumerical {
if check, err := cl.Check(o); check || !strings.HasPrefix(err.Error(), "INVALID_ARGUMENT") {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":{"*gte": 6}}`)
@@ -181,7 +211,7 @@ func TestCondKeyStructPointer(t *testing.T) {
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", ToIJSON(cl.rootElement), err)
}
if check, err := cl.Check(o); check || err != ErrNotNumerical {
if check, err := cl.Check(o); check || !strings.HasPrefix(err.Error(), "INVALID_ARGUMENT") {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":{"*gte": 6}}`)
@@ -261,3 +291,47 @@ func TestCondOperatorSlice(t *testing.T) {
t.Errorf("Error checking struct: %v %v (%v)", check, err, ToIJSON(cl.rootElement))
}
}
func TestCondMixed(t *testing.T) {
o := &struct {
Test string
Field float64
Categories StringMap
Other bool
}{
Test: "test",
Field: 6.0,
Categories: StringMap{"call": true, "data": true, "voice": true},
Other: true,
}
cl := &CondLoader{}
err := cl.Parse(`{"*and":[{"Test":"test"},{"Field":{"*gt":5}},{"Other":true},{"Categories":{"*has":["data", "call"]}}]}`)
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 TestCondBalanceType(t *testing.T) {
type Balance struct {
Value float64
}
o := &struct {
BalanceType string
Balance
}{
BalanceType: MONETARY,
Balance: Balance{Value: 10},
}
cl := &CondLoader{}
err := cl.Parse(`{"BalanceType":"*monetary","Value":10}`)
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))
}
}