This commit is contained in:
DanB
2016-03-11 11:06:52 +01:00
10 changed files with 856 additions and 33 deletions

View File

@@ -25,8 +25,8 @@ import (
"time"
"github.com/cgrates/cgrates/cache2go"
"github.com/cgrates/cgrates/structmatcher"
"github.com/cgrates/cgrates/utils"
"github.com/cgrates/structmatcher"
"strings"
)

View File

@@ -57,10 +57,23 @@ type DebitInfo struct {
AccountID string // used when debited from shared balance
}
func (bi *DebitInfo) Equal(other *DebitInfo) bool {
return bi.Unit.Equal(other.Unit) &&
bi.Monetary.Equal(other.Monetary) &&
bi.AccountID == other.AccountID
func (di *DebitInfo) Equal(other *DebitInfo) bool {
return di.Unit.Equal(other.Unit) &&
di.Monetary.Equal(other.Monetary) &&
di.AccountID == other.AccountID
}
func (di *DebitInfo) Clone() *DebitInfo {
nDi := &DebitInfo{
AccountID: di.AccountID,
}
if di.Unit != nil {
nDi.Unit = di.Unit.Clone()
}
if di.Monetary != nil {
nDi.Monetary = di.Monetary.Clone()
}
return nDi
}
type MonetaryInfo struct {
@@ -69,6 +82,11 @@ type MonetaryInfo struct {
RateInterval *RateInterval
}
func (mi *MonetaryInfo) Clone() *MonetaryInfo {
newMi := *mi
return &newMi
}
func (mi *MonetaryInfo) Equal(other *MonetaryInfo) bool {
if mi == nil && other == nil {
return true
@@ -89,6 +107,11 @@ type UnitInfo struct {
RateInterval *RateInterval
}
func (ui *UnitInfo) Clone() *UnitInfo {
newUi := *ui
return &newUi
}
func (ui *UnitInfo) Equal(other *UnitInfo) bool {
if ui == nil && other == nil {
return true
@@ -237,12 +260,14 @@ func (tss *TimeSpans) Decompress() { // must be pointer receiver
}
func (incr *Increment) Clone() *Increment {
nIncr := &Increment{
Duration: incr.Duration,
Cost: incr.Cost,
BalanceInfo: incr.BalanceInfo,
nInc := &Increment{
Duration: incr.Duration,
Cost: incr.Cost,
}
return nIncr
if incr.BalanceInfo != nil {
nInc.BalanceInfo = incr.BalanceInfo.Clone()
}
return nInc
}
func (incr *Increment) Equal(other *Increment) bool {
@@ -297,8 +322,19 @@ func (incs *Increments) Compress() { // must be pointer receiver
func (incs *Increments) Decompress() { // must be pointer receiver
var cIncrs Increments
for _, cIncr := range *incs {
for i := 0; i < cIncr.GetCompressFactor(); i++ {
cIncrs = append(cIncrs, cIncr.Clone())
cf := cIncr.GetCompressFactor()
for i := 0; i < cf; i++ {
incr := cIncr.Clone()
// set right Values
if incr.BalanceInfo != nil {
if incr.BalanceInfo.Monetary != nil {
incr.BalanceInfo.Monetary.Value += (float64(cf-(i+1)) * incr.Cost)
}
if incr.BalanceInfo.Unit != nil {
incr.BalanceInfo.Unit.Value += (float64(cf-(i+1)) * incr.BalanceInfo.Unit.Consumed)
}
}
cIncrs = append(cIncrs, incr)
}
}
*incs = cIncrs

View File

@@ -1511,42 +1511,42 @@ func TestTSIncrementsCompressDecompress(t *testing.T) {
Increments: Increments{
&Increment{
Duration: time.Minute,
Cost: 10.4,
Cost: 2,
BalanceInfo: &DebitInfo{
Unit: &UnitInfo{UUID: "1", DestinationID: "1", Consumed: 2.3, TOR: utils.VOICE, RateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}},
Monetary: &MonetaryInfo{UUID: "2"},
Unit: &UnitInfo{UUID: "1", Value: 25, DestinationID: "1", Consumed: 1, TOR: utils.VOICE, RateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}},
Monetary: &MonetaryInfo{UUID: "2", Value: 98},
AccountID: "3"},
},
&Increment{
Duration: time.Minute,
Cost: 10.4,
Cost: 2,
BalanceInfo: &DebitInfo{
Unit: &UnitInfo{UUID: "1", DestinationID: "1", Consumed: 2.3, TOR: utils.VOICE, RateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}},
Monetary: &MonetaryInfo{UUID: "2"},
Unit: &UnitInfo{UUID: "1", Value: 24, DestinationID: "1", Consumed: 1, TOR: utils.VOICE, RateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}},
Monetary: &MonetaryInfo{UUID: "2", Value: 96},
AccountID: "3"},
},
&Increment{
Duration: time.Minute,
Cost: 10.4,
Cost: 2,
BalanceInfo: &DebitInfo{
Unit: &UnitInfo{UUID: "1", DestinationID: "1", Consumed: 2.3, TOR: utils.VOICE, RateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}},
Monetary: &MonetaryInfo{UUID: "2"},
Unit: &UnitInfo{UUID: "1", Value: 23, DestinationID: "1", Consumed: 1, TOR: utils.VOICE, RateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}},
Monetary: &MonetaryInfo{UUID: "2", Value: 94},
AccountID: "3"},
},
&Increment{
Duration: time.Minute,
Cost: 10.4,
Cost: 2,
BalanceInfo: &DebitInfo{
Unit: &UnitInfo{UUID: "1", DestinationID: "1", Consumed: 2.3, TOR: utils.VOICE, RateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 1111 * time.Second, RateUnit: time.Second}}}}},
Monetary: &MonetaryInfo{UUID: "2"},
Unit: &UnitInfo{UUID: "1", Value: 22, DestinationID: "1", Consumed: 1, TOR: utils.VOICE, RateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 1111 * time.Second, RateUnit: time.Second}}}}},
Monetary: &MonetaryInfo{UUID: "2", Value: 92},
AccountID: "3"},
},
&Increment{
Duration: time.Minute,
Cost: 10.4,
Cost: 2,
BalanceInfo: &DebitInfo{
Unit: &UnitInfo{UUID: "1", DestinationID: "1", Consumed: 2.3, TOR: utils.VOICE, RateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}},
Monetary: &MonetaryInfo{UUID: "2"},
Unit: &UnitInfo{UUID: "1", Value: 21, DestinationID: "1", Consumed: 1, TOR: utils.VOICE, RateInterval: &RateInterval{Rating: &RIRate{Rates: RateGroups{&Rate{GroupIntervalStart: 0, Value: 100, RateIncrement: 10 * time.Second, RateUnit: time.Second}}}}},
Monetary: &MonetaryInfo{UUID: "2", Value: 90},
AccountID: "3"},
},
},
@@ -1554,11 +1554,11 @@ func TestTSIncrementsCompressDecompress(t *testing.T) {
}
tss.Compress()
if len(tss[0].Increments) != 3 {
t.Error("Error compressing timespan: ", tss[0].Increments)
t.Error("Error compressing timespan: ", utils.ToIJSON(tss[0]))
}
tss.Decompress()
if len(tss[0].Increments) != 5 {
t.Error("Error decompressing timespans: ", tss[0].Increments)
t.Error("Error decompressing timespans: ", utils.ToIJSON(tss[0]))
}
}

View File

@@ -8,8 +8,8 @@ import (
"strings"
"time"
"github.com/cgrates/cgrates/structmatcher"
"github.com/cgrates/cgrates/utils"
"github.com/cgrates/structmatcher"
)
type TpReader struct {

2
glide.lock generated
View File

@@ -13,8 +13,6 @@ imports:
version: 3d6beed663452471dec3ca194137a30d379d9e8f
- name: github.com/cgrates/rpcclient
version: 79661b1e514823a9ac93b2b9e97e037ee190ba47
- name: github.com/cgrates/structmatcher
version: 98feee0bab15ce165540fe5f0fa006db2e9f898c
- name: github.com/DisposaBoy/JsonConfigReader
version: 33a99fdf1d5ee1f79b5077e9c06f955ad356d5f4
- name: github.com/fiorix/go-diameter

View File

@@ -6,7 +6,6 @@ import:
- package: github.com/cgrates/kamevapi
- package: github.com/cgrates/osipsdagram
- package: github.com/cgrates/rpcclient
- package: github.com/cgrates/structmatcher
- package: github.com/fiorix/go-diameter
subpackages:
- diam

View File

@@ -0,0 +1,7 @@
language: go
go:
- 1.5
branches:
only: master

27
structmatcher/README.md Normal file
View File

@@ -0,0 +1,27 @@
# structmatcher
Query language for matching structures
[![Build Status](https://secure.travis-ci.org/cgrates/structmatcher.png)](http://travis-ci.org/cgrates/structmatcher)
The StructMatcher type will parse a condition string and match it against a given structure.
The condition syntax is a json encoded string similar to mongodb query language.
Examples:
- {"Weight":{"*gt":50}} checks for a balance with weight greater than 50
- {"*or":[{"Value":{"*eq":0}},{"Value":{"*gte":100}}] checks for a balance with value equal to 0 or equal or highr than 100
Available operators:
- *eq: equal
- *gt: greater than
- *gte: greater or equal than
- *lt: less then
- *lte: less or equal than
- *exp: expired
- *or: logical or
- *and: logical and
- *has: receives a list of elements and checks that the elements are present in the specified field (StringMap type)
Equal (*eq) and local and (*and) operators are implicit for shortcuts. In this way:
{"*and":[{"Value":{"*eq":3}},{"Weight":{"*eq":10}}]} is equivalent to: {"Value":3, "Weight":10}.

View File

@@ -0,0 +1,327 @@
package structmatcher
/*
The condition syntax is a json encoded string similar to mongodb query language.
Examples:
- {"Weight":{"*gt":50}} checks for a balance with weight greater than 50
- {"*or":[{"Value":{"*eq":0}},{"Value":{"*gte":100}}] checks for a balance with value equal to 0 or equal or highr than 100
Available operators:
- *eq: equal
- *gt: greater than
- *gte: greater or equal than
- *lt: less then
- *lte: less or equal than
- *exp: expired
- *or: logical or
- *and: logical and
- *has: receives a list of elements and checks that the elements are present in the specified field (StringMap type)
Equal (*eq) and local and (*and) operators are implicit for shortcuts. In this way:
{"*and":[{"Value":{"*eq":3}},{"Weight":{"*eq":10}}]} is equivalent to: {"Value":3, "Weight":10}.
*/
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
"time"
"github.com/cgrates/cgrates/utils"
)
const (
CondEQ = "*eq"
CondGT = "*gt"
CondGTE = "*gte"
CondLT = "*lt"
CondLTE = "*lte"
CondEXP = "*exp"
CondOR = "*or"
CondAND = "*and"
CondHAS = "*has"
CondRSR = "*rsr"
)
func NewErrInvalidArgument(arg interface{}) error {
return fmt.Errorf("INVALID_ARGUMENT: %v", arg)
}
type StringMap map[string]bool
var (
ErrParserError = errors.New("PARSER_ERROR")
operatorMap = map[string]func(field, value interface{}) (bool, error){
CondEQ: func(field, value interface{}) (bool, error) {
return value == field, nil
},
CondGT: func(field, value interface{}) (bool, error) {
var of, vf float64
var ok bool
if of, ok = field.(float64); !ok {
return false, NewErrInvalidArgument(field)
}
if vf, ok = value.(float64); !ok {
return false, NewErrInvalidArgument(value)
}
return of > vf, nil
},
CondGTE: func(field, value interface{}) (bool, error) {
var of, vf float64
var ok bool
if of, ok = field.(float64); !ok {
return false, NewErrInvalidArgument(field)
}
if vf, ok = value.(float64); !ok {
return false, NewErrInvalidArgument(value)
}
return of >= vf, nil
},
CondLT: func(field, value interface{}) (bool, error) {
var of, vf float64
var ok bool
if of, ok = field.(float64); !ok {
return false, NewErrInvalidArgument(field)
}
if vf, ok = value.(float64); !ok {
return false, NewErrInvalidArgument(value)
}
return of < vf, nil
},
CondLTE: func(field, value interface{}) (bool, error) {
var of, vf float64
var ok bool
if of, ok = field.(float64); !ok {
return false, NewErrInvalidArgument(field)
}
if vf, ok = value.(float64); !ok {
return false, NewErrInvalidArgument(value)
}
return of <= vf, nil
},
CondEXP: func(field, value interface{}) (bool, error) {
var expDate time.Time
var ok bool
if expDate, ok = field.(time.Time); !ok {
return false, NewErrInvalidArgument(field)
}
var expired bool
if expired, ok = value.(bool); !ok {
return false, NewErrInvalidArgument(value)
}
if expired { // check for expiration
return !expDate.IsZero() && expDate.Before(time.Now()), nil
} else { // check not expired
return expDate.IsZero() || expDate.After(time.Now()), nil
}
},
CondHAS: func(field, value interface{}) (bool, error) {
var strMap StringMap
var ok bool
if strMap, ok = field.(StringMap); !ok {
return false, NewErrInvalidArgument(field)
}
var strSlice []interface{}
if strSlice, ok = value.([]interface{}); !ok {
return false, NewErrInvalidArgument(value)
}
for _, str := range strSlice {
if !strMap[str.(string)] {
return false, nil
}
}
return true, nil
},
CondRSR: func(field, value interface{}) (bool, error) {
fltr, err := utils.NewRSRFilter(value.(string))
if err != nil {
return false, err
}
return fltr.Pass(fmt.Sprintf("%v", field)), nil
},
}
)
type compositeElement interface {
element
addChild(element) error
}
type element interface {
checkStruct(interface{}) (bool, error)
}
type operatorSlice struct {
operator string
slice []element
}
func (os *operatorSlice) addChild(ce element) error {
os.slice = append(os.slice, ce)
return nil
}
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
}
type keyStruct struct {
key string
elem element
}
func (ks *keyStruct) addChild(ce element) 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)
if !value.IsValid() {
return false, NewErrInvalidArgument(ks.key)
}
return ks.elem.checkStruct(value.Interface())
}
type operatorValue struct {
operator string
value interface{}
}
func (ov *operatorValue) checkStruct(o interface{}) (bool, error) {
if f, ok := operatorMap[ov.operator]; ok {
return f(o, ov.value)
}
return false, nil
}
type keyValue struct {
key string
value interface{}
}
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)
if !value.IsValid() {
return false, NewErrInvalidArgument(kv.key)
}
return value.Interface() == kv.value, nil
}
type trueElement struct{}
func (te *trueElement) checkStruct(o interface{}) (bool, error) {
return true, nil
}
func isOperator(s string) bool {
return strings.HasPrefix(s, "*")
}
func notEmpty(x interface{}) bool {
return !reflect.DeepEqual(x, reflect.Zero(reflect.TypeOf(x)).Interface())
}
type StructMatcher struct {
rootElement element
}
func NewStructMatcher(q string) (sm *StructMatcher, err error) {
sm = &StructMatcher{}
err = sm.Parse(q)
return
}
func (sm *StructMatcher) load(a map[string]interface{}, parentElement compositeElement) (element, error) {
for key, value := range a {
var currentElement element
switch t := value.(type) {
case []interface{}:
if key == CondHAS {
currentElement = &operatorValue{operator: key, value: t}
} else {
currentElement = &operatorSlice{operator: key}
for _, e := range t {
sm.load(e.(map[string]interface{}), currentElement.(compositeElement))
}
}
case map[string]interface{}:
currentElement = &keyStruct{key: key}
//log.Print("map: ", t)
sm.load(t, currentElement.(compositeElement))
case interface{}:
if isOperator(key) {
currentElement = &operatorValue{operator: key, value: t}
} else {
currentElement = &keyValue{key: key, value: t}
}
//log.Print("generic interface: ", t)
default:
return nil, ErrParserError
}
if parentElement != nil { // normal recurrent action
parentElement.addChild(currentElement)
} else {
if len(a) > 1 { // we have more keys in the map
parentElement = &operatorSlice{operator: CondAND}
parentElement.addChild(currentElement)
} else { // it was only one key value
return currentElement, nil
}
}
}
return parentElement, nil
}
func (sm *StructMatcher) Parse(s string) (err error) {
a := make(map[string]interface{})
if len(s) != 0 {
if err := json.Unmarshal([]byte([]byte(s)), &a); err != nil {
return err
}
sm.rootElement, err = sm.load(a, nil)
} else {
sm.rootElement = &trueElement{}
}
return
}
func (sm *StructMatcher) Match(o interface{}) (bool, error) {
if sm.rootElement == nil {
return false, ErrParserError
}
return sm.rootElement.checkStruct(o)
}

View File

@@ -0,0 +1,429 @@
package structmatcher
import (
"encoding/json"
"strings"
"testing"
"time"
)
func toJSON(v interface{}) string {
b, _ := json.MarshalIndent(v, "", " ")
return string(b)
}
func TestStructMatcher(t *testing.T) {
cl := &StructMatcher{}
err := cl.Parse(`{"*or":[{"test":1},{"field":{"*gt":1}},{"best":"coco"}]}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
err = cl.Parse(`{"*has":["NAT","RET","EUR"]}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
err = cl.Parse(`{"Field":7, "Other":true}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
err = cl.Parse(``)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
}
func TestStructMatcherKeyValue(t *testing.T) {
o := struct {
Test string
Field float64
Other bool
ExpDate time.Time
}{
Test: "test",
Field: 6.0,
Other: true,
ExpDate: time.Date(2016, 1, 19, 20, 47, 0, 0, time.UTC),
}
cl := &StructMatcher{}
err := cl.Parse(`{"Test":"test"}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":6}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Other":true}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":6, "Other":true}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":7, "Other":true}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":6, "Other":false}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Other":true, "Field":{"*gt":5}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Other":true, "Field":{"*gt":7}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(``)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"ExpDate":{"*exp":true}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"ExpDate":{"*exp":false}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"*and":[{"Field":{"*gte":50}},{"Test":{"*eq":"test"}}]}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"WrongFieldName":{"*eq":1}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); check || err == nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
}
func TestStructMatcherKeyValuePointer(t *testing.T) {
o := &struct {
Test string
Field float64
Other bool
}{
Test: "test",
Field: 6.0,
Other: true,
}
cl := &StructMatcher{}
err := cl.Parse(`{"Test":"test"}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":6}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Other":true}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
}
func TestStructMatcherOperatorValue(t *testing.T) {
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, toJSON(root))
}
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, toJSON(root))
}
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, toJSON(root))
}
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, toJSON(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, toJSON(root))
}
}
func TestStructMatcherKeyStruct(t *testing.T) {
o := struct {
Test string
Field float64
Other bool
}{
Test: "test",
Field: 6.0,
Other: true,
}
cl := &StructMatcher{}
err := cl.Parse(`{"Field":{"*gt": 5}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Test":{"*gt": 5}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); check || !strings.HasPrefix(err.Error(), "INVALID_ARGUMENT") {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":{"*gte": 6}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":{"*lt": 7}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":{"*lte": 6}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":{"*eq": 6}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Test":{"*eq": "test"}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
}
func TestStructMatcherKeyStructPointer(t *testing.T) {
o := &struct {
Test string
Field float64
Other bool
}{
Test: "test",
Field: 6.0,
Other: true,
}
cl := &StructMatcher{}
err := cl.Parse(`{"Field":{"*gt": 5}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Test":{"*gt": 5}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); check || !strings.HasPrefix(err.Error(), "INVALID_ARGUMENT") {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":{"*gte": 6}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":{"*lt": 7}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":{"*lte": 6}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Field":{"*eq": 6}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"Test":{"*eq": "test"}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
}
func TestStructMatcherOperatorSlice(t *testing.T) {
o := &struct {
Test string
Field float64
Other bool
}{
Test: "test",
Field: 6.0,
Other: true,
}
cl := &StructMatcher{}
err := cl.Parse(`{"*or":[{"Test":"test"},{"Field":{"*gt":5}},{"Other":true}]}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"*or":[{"Test":"test"},{"Field":{"*gt":7}},{"Other":false}]}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"*and":[{"Test":"test"},{"Field":{"*gt":5}},{"Other":true}]}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"*and":[{"Test":"test"},{"Field":{"*gt":7}},{"Other":false}]}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
}
func TestStructMatcherMixed(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 := &StructMatcher{}
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)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", check, err, toJSON(cl.rootElement))
}
}
func TestStructMatcherBalanceType(t *testing.T) {
type Balance struct {
Value float64
}
o := &struct {
BalanceType string
Balance
}{
BalanceType: "*monetary",
Balance: Balance{Value: 10},
}
cl := &StructMatcher{}
err := cl.Parse(`{"BalanceType":"*monetary","Value":10}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", !check, err, toJSON(cl.rootElement))
}
}
func TestStructMatcherRSR(t *testing.T) {
o := &struct {
BalanceType string
}{
BalanceType: "*monetary",
}
cl := &StructMatcher{}
err := cl.Parse(`{"BalanceType":{"*rsr":"^*mon"}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); !check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", !check, err, toJSON(cl.rootElement))
}
err = cl.Parse(`{"BalanceType":{"*rsr":"^*min"}}`)
if err != nil {
t.Errorf("Error loading structure: %+v (%v)", toJSON(cl.rootElement), err)
}
if check, err := cl.Match(o); check || err != nil {
t.Errorf("Error checking struct: %v %v (%v)", !check, err, toJSON(cl.rootElement))
}
}