mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-16 05:39:54 +05:00
Merge branch 'master' of https://github.com/cgrates/cgrates
This commit is contained in:
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
2
glide.lock
generated
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
7
structmatcher/.travis.yml
Normal file
7
structmatcher/.travis.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.5
|
||||
|
||||
branches:
|
||||
only: master
|
||||
27
structmatcher/README.md
Normal file
27
structmatcher/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# structmatcher
|
||||
Query language for matching structures
|
||||
|
||||
[](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}.
|
||||
327
structmatcher/structmatcher.go
Normal file
327
structmatcher/structmatcher.go
Normal 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)
|
||||
}
|
||||
429
structmatcher/structmatcher_test.go
Normal file
429
structmatcher/structmatcher_test.go
Normal 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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user