mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-12 10:36:24 +05:00
307 lines
7.4 KiB
Go
307 lines
7.4 KiB
Go
package utils
|
|
|
|
/*
|
|
When an action is using *conditional_ form before the execution the engine is checking the ExtraParameters field for condition filter, loads it and checks all the balances in the account for one that is satisfying the condition. If one is fond the action is executed, otherwise it will do nothing for this account.
|
|
|
|
The condition syntax is a json encoded document 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 (also a list)
|
|
|
|
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"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
CondEQ = "*eq"
|
|
CondGT = "*gt"
|
|
CondGTE = "*gte"
|
|
CondLT = "*lt"
|
|
CondLTE = "*lte"
|
|
CondEXP = "*exp"
|
|
CondOR = "*or"
|
|
CondAND = "*and"
|
|
CondHAS = "*has"
|
|
)
|
|
|
|
var 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
|
|
},
|
|
}
|
|
|
|
func NewErrInvalidArgument(arg interface{}) error {
|
|
return fmt.Errorf("INVALID_ARGUMENT: %v", arg)
|
|
}
|
|
|
|
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 CondLoader struct {
|
|
rootElement element
|
|
}
|
|
|
|
func (cp *CondLoader) 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 {
|
|
cp.load(e.(map[string]interface{}), currentElement.(compositeElement))
|
|
}
|
|
}
|
|
case map[string]interface{}:
|
|
currentElement = &keyStruct{key: key}
|
|
//log.Print("map: ", t)
|
|
cp.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 (cp *CondLoader) 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
|
|
}
|
|
cp.rootElement, err = cp.load(a, nil)
|
|
} else {
|
|
cp.rootElement = &trueElement{}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (cp *CondLoader) Check(o interface{}) (bool, error) {
|
|
if cp.rootElement == nil {
|
|
return false, ErrParserError
|
|
}
|
|
return cp.rootElement.checkStruct(o)
|
|
}
|