mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
868 lines
21 KiB
Go
868 lines
21 KiB
Go
/*
|
|
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
|
|
Copyright (C) ITsysCOM GmbH
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
package utils
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/ericlagergren/decimal"
|
|
)
|
|
|
|
// StringToInterface will parse string into supported types
|
|
// if no other conversion possible, original string will be returned
|
|
func StringToInterface(s string) any {
|
|
if s == EmptyString {
|
|
return s
|
|
}
|
|
// int64
|
|
if i, err := strconv.ParseInt(s, 10, 64); err == nil {
|
|
return i
|
|
}
|
|
// bool
|
|
if b, err := strconv.ParseBool(s); err == nil {
|
|
return b
|
|
}
|
|
// float64
|
|
if f, err := strconv.ParseFloat(s, 64); err == nil {
|
|
return f
|
|
}
|
|
// time.Time
|
|
if t, err := ParseTimeDetectLayout(s, "Local"); err == nil {
|
|
return t
|
|
}
|
|
// time.Duration
|
|
if d, err := time.ParseDuration(s); err == nil {
|
|
return d
|
|
}
|
|
// string
|
|
return s
|
|
}
|
|
|
|
// ReflectFieldInterface parses intf attepting to return the field value or error otherwise
|
|
// Supports "ExtraFields" where additional fields are dynamically inserted in map with field name: extraFieldsLabel
|
|
func ReflectFieldInterface(intf any, fldName, extraFieldsLabel string) (retIf any, err error) {
|
|
v := reflect.ValueOf(intf)
|
|
if v.Kind() == reflect.Ptr {
|
|
v = v.Elem()
|
|
}
|
|
var field reflect.Value
|
|
switch v.Kind() {
|
|
case reflect.Struct:
|
|
field = v.FieldByName(fldName)
|
|
case reflect.Map:
|
|
field = v.MapIndex(reflect.ValueOf(fldName))
|
|
if !field.IsValid() { // Not looking in extra fields anymore
|
|
return nil, ErrNotFound
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("Unsupported field kind: %v", v.Kind())
|
|
}
|
|
|
|
if !field.IsValid() {
|
|
if extraFieldsLabel == "" {
|
|
return nil, ErrNotFound
|
|
}
|
|
mpVal := v.FieldByName(extraFieldsLabel)
|
|
if !mpVal.IsValid() || mpVal.Kind() != reflect.Map {
|
|
return nil, ErrNotFound
|
|
}
|
|
field = mpVal.MapIndex(reflect.ValueOf(fldName))
|
|
if !field.IsValid() {
|
|
return nil, ErrNotFound
|
|
}
|
|
}
|
|
return field.Interface(), nil
|
|
}
|
|
|
|
// ReflectFieldAsString parses intf and attepting to return the field as string or error otherwise
|
|
// Supports "ExtraFields" where additional fields are dynamically inserted in map with field name: extraFieldsLabel
|
|
func ReflectFieldAsString(intf any, fldName, extraFieldsLabel string) (string, error) {
|
|
field, err := ReflectFieldInterface(intf, fldName, extraFieldsLabel)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
vOf := reflect.ValueOf(field)
|
|
switch vOf.Kind() {
|
|
case reflect.String:
|
|
return vOf.String(), nil
|
|
case reflect.Int, reflect.Int64:
|
|
return strconv.FormatInt(vOf.Int(), 10), nil
|
|
case reflect.Float64:
|
|
return strconv.FormatFloat(vOf.Float(), 'f', -1, 64), nil
|
|
default:
|
|
return "", fmt.Errorf("Cannot convert to string field type: %s", vOf.Kind().String())
|
|
}
|
|
}
|
|
|
|
func IfaceAsTime(itm any, timezone string) (t time.Time, err error) {
|
|
switch v := itm.(type) {
|
|
case time.Time:
|
|
return v, nil
|
|
case string:
|
|
return ParseTimeDetectLayout(v, timezone)
|
|
default:
|
|
err = fmt.Errorf("cannot convert field: %+v to time.Time", itm)
|
|
}
|
|
return
|
|
}
|
|
|
|
func IfaceAsBig(itm any) (b *decimal.Big, err error) {
|
|
switch it := itm.(type) {
|
|
case time.Duration:
|
|
return decimal.New(int64(it), 0), nil
|
|
case int: // check every int type
|
|
return decimal.New(int64(it), 0), nil
|
|
case int8:
|
|
return decimal.New(int64(it), 0), nil
|
|
case int16:
|
|
return decimal.New(int64(it), 0), nil
|
|
case int32:
|
|
return decimal.New(int64(it), 0), nil
|
|
case int64:
|
|
return decimal.New(it, 0), nil
|
|
case uint:
|
|
return new(decimal.Big).SetUint64(uint64(it)), nil
|
|
case uint8:
|
|
return new(decimal.Big).SetUint64(uint64(it)), nil
|
|
case uint16:
|
|
return new(decimal.Big).SetUint64(uint64(it)), nil
|
|
case uint32:
|
|
return new(decimal.Big).SetUint64(uint64(it)), nil
|
|
case uint64:
|
|
return new(decimal.Big).SetUint64(it), nil
|
|
case float32: // automatically hitting here also ints
|
|
return new(decimal.Big).SetFloat64(float64(it)), nil
|
|
case float64: // automatically hitting here also ints
|
|
return new(decimal.Big).SetFloat64(it), nil
|
|
case string:
|
|
if strings.HasSuffix(it, NsSuffix) ||
|
|
strings.HasSuffix(it, UsSuffix) ||
|
|
strings.HasSuffix(it, µSuffix) ||
|
|
strings.HasSuffix(it, MsSuffix) ||
|
|
strings.HasSuffix(it, SSuffix) ||
|
|
strings.HasSuffix(it, MSuffix) ||
|
|
strings.HasSuffix(it, HSuffix) {
|
|
var tm time.Duration
|
|
if tm, err = time.ParseDuration(it); err != nil {
|
|
return
|
|
}
|
|
return decimal.New(int64(tm), 0), nil
|
|
}
|
|
z, ok := new(decimal.Big).SetString(it)
|
|
// verify ok and check if the value was converted successfuly
|
|
// and the big is a valid number
|
|
if !ok || z.IsNaN(0) {
|
|
return nil, fmt.Errorf("can't convert <%+v> to decimal", it)
|
|
}
|
|
return z, nil
|
|
default:
|
|
err = fmt.Errorf("cannot convert field: %+v to time.Duration", it)
|
|
}
|
|
return
|
|
}
|
|
|
|
func IfaceAsDuration(itm any) (d time.Duration, err error) {
|
|
switch it := itm.(type) {
|
|
case time.Duration:
|
|
return it, nil
|
|
case int: // check every int type
|
|
return time.Duration(int64(it)), nil
|
|
case int8:
|
|
return time.Duration(int64(it)), nil
|
|
case int16:
|
|
return time.Duration(int64(it)), nil
|
|
case int32:
|
|
return time.Duration(int64(it)), nil
|
|
case int64:
|
|
return time.Duration(int64(it)), nil
|
|
case uint:
|
|
return time.Duration(int64(it)), nil
|
|
case uint8:
|
|
return time.Duration(int64(it)), nil
|
|
case uint16:
|
|
return time.Duration(int64(it)), nil
|
|
case uint32:
|
|
return time.Duration(int64(it)), nil
|
|
case uint64:
|
|
return time.Duration(int64(it)), nil
|
|
case float32: // automatically hitting here also ints
|
|
return time.Duration(int64(it)), nil
|
|
case float64: // automatically hitting here also ints
|
|
return time.Duration(int64(it)), nil
|
|
case string:
|
|
return ParseDurationWithNanosecs(it)
|
|
default:
|
|
err = fmt.Errorf("cannot convert field: %+v to time.Duration", it)
|
|
}
|
|
return
|
|
}
|
|
|
|
// IfaceAsTInt converts interface to type int
|
|
func IfaceAsTInt(itm any) (i int, err error) {
|
|
switch it := itm.(type) {
|
|
case int:
|
|
return it, nil
|
|
case time.Duration:
|
|
return int(it.Nanoseconds()), nil
|
|
case int32:
|
|
return int(it), nil
|
|
case int64:
|
|
return int(it), nil
|
|
case float32:
|
|
return int(it), nil
|
|
case float64:
|
|
return int(it), nil
|
|
case string:
|
|
return strconv.Atoi(it)
|
|
default:
|
|
err = fmt.Errorf("cannot convert field<%T>: %+v to int", it, it)
|
|
}
|
|
return
|
|
}
|
|
|
|
func IfaceAsInt64(itm any) (i int64, err error) {
|
|
switch it := itm.(type) {
|
|
case int:
|
|
return int64(it), nil
|
|
case time.Duration:
|
|
return it.Nanoseconds(), nil
|
|
case int32:
|
|
return int64(it), nil
|
|
case int64:
|
|
return it, nil
|
|
case string:
|
|
return strconv.ParseInt(it, 10, 64)
|
|
default:
|
|
err = fmt.Errorf("cannot convert field: %+v to int", it)
|
|
}
|
|
return
|
|
}
|
|
|
|
// same function as IfaceAsInt64 but if the value is float round it to int64 instead of returning error
|
|
func IfaceAsTInt64(itm any) (i int64, err error) {
|
|
switch it := itm.(type) {
|
|
case int:
|
|
return int64(it), nil
|
|
case time.Duration:
|
|
return it.Nanoseconds(), nil
|
|
case int32:
|
|
return int64(it), nil
|
|
case int64:
|
|
return it, nil
|
|
case float32:
|
|
return int64(it), nil
|
|
case float64:
|
|
return int64(it), nil
|
|
case string:
|
|
return strconv.ParseInt(it, 10, 64)
|
|
default:
|
|
err = fmt.Errorf("cannot convert field<%T>: %+v to int", it, it)
|
|
}
|
|
return
|
|
}
|
|
|
|
func IfaceAsFloat64(itm any) (f float64, err error) {
|
|
switch it := itm.(type) {
|
|
case float64:
|
|
return it, nil
|
|
case time.Duration:
|
|
return float64(it.Nanoseconds()), nil
|
|
case int:
|
|
return float64(it), nil
|
|
case int64:
|
|
return float64(it), nil
|
|
case string:
|
|
return strconv.ParseFloat(it, 64)
|
|
default:
|
|
err = fmt.Errorf("cannot convert field: %+v to float64", it)
|
|
}
|
|
return
|
|
}
|
|
func IfaceAsTFloat64(itm any) (f float64, err error) {
|
|
switch it := itm.(type) {
|
|
case float64:
|
|
return it, nil
|
|
case time.Duration:
|
|
return float64(it.Nanoseconds()), nil
|
|
case int:
|
|
return float64(it), nil
|
|
case int64:
|
|
return float64(it), nil
|
|
case string:
|
|
if strings.HasSuffix(it, SSuffix) || strings.HasSuffix(it, MSuffix) || strings.HasSuffix(it, HSuffix) {
|
|
var tm time.Duration
|
|
if tm, err = time.ParseDuration(it); err != nil {
|
|
return
|
|
}
|
|
return float64(tm), nil
|
|
}
|
|
return strconv.ParseFloat(it, 64)
|
|
default:
|
|
err = fmt.Errorf("cannot convert field: %+v to float64", it)
|
|
}
|
|
return
|
|
}
|
|
|
|
func IfaceAsBool(itm any) (b bool, err error) {
|
|
switch v := itm.(type) {
|
|
case bool:
|
|
return v, nil
|
|
case string:
|
|
return strconv.ParseBool(v)
|
|
case int:
|
|
return v > 0, nil
|
|
case int64:
|
|
return v > 0, nil
|
|
case float64:
|
|
return v > 0, nil
|
|
default:
|
|
err = fmt.Errorf("cannot convert field: %+v to bool", itm)
|
|
}
|
|
return
|
|
}
|
|
|
|
func IfaceAsString(fld any) (out string) {
|
|
switch value := fld.(type) {
|
|
case nil:
|
|
return
|
|
case int:
|
|
return strconv.Itoa(value)
|
|
case int32:
|
|
return strconv.FormatInt(int64(value), 10)
|
|
case int64:
|
|
return strconv.FormatInt(value, 10)
|
|
case uint32:
|
|
return strconv.FormatUint(uint64(value), 10)
|
|
case uint64:
|
|
return strconv.FormatUint(value, 10)
|
|
case bool:
|
|
return strconv.FormatBool(value)
|
|
case float32:
|
|
return strconv.FormatFloat(float64(value), 'f', -1, 64)
|
|
case float64:
|
|
return strconv.FormatFloat(value, 'f', -1, 64)
|
|
case []uint8:
|
|
return string(value) // byte is an alias for uint8 conversions implicit
|
|
case time.Duration:
|
|
return value.String()
|
|
case time.Time:
|
|
return value.Format(time.RFC3339)
|
|
case net.IP:
|
|
return value.String()
|
|
case string:
|
|
return value
|
|
default: // Maybe we are lucky and the value converts to string
|
|
return ToJSON(fld)
|
|
}
|
|
}
|
|
|
|
// IfaceAsSliceString is trying to convert the interface to a slice of strings
|
|
func IfaceAsSliceString(fld any) (out []string, err error) {
|
|
switch value := fld.(type) {
|
|
case nil:
|
|
return
|
|
case []int:
|
|
out = make([]string, len(value))
|
|
for i, val := range value {
|
|
out[i] = strconv.Itoa(val)
|
|
}
|
|
case []int32:
|
|
out = make([]string, len(value))
|
|
for i, val := range value {
|
|
out[i] = strconv.FormatInt(int64(val), 10)
|
|
}
|
|
case []int64:
|
|
out = make([]string, len(value))
|
|
for i, val := range value {
|
|
out[i] = strconv.FormatInt(val, 10)
|
|
}
|
|
case []uint:
|
|
out = make([]string, len(value))
|
|
for i, val := range value {
|
|
out[i] = strconv.FormatUint(uint64(val), 10)
|
|
}
|
|
case []uint32:
|
|
out = make([]string, len(value))
|
|
for i, val := range value {
|
|
out[i] = strconv.FormatUint(uint64(val), 10)
|
|
}
|
|
case []uint64:
|
|
out = make([]string, len(value))
|
|
for i, val := range value {
|
|
out[i] = strconv.FormatUint(val, 10)
|
|
}
|
|
case []bool:
|
|
out = make([]string, len(value))
|
|
for i, val := range value {
|
|
out[i] = strconv.FormatBool(val)
|
|
}
|
|
case []float32:
|
|
out = make([]string, len(value))
|
|
for i, val := range value {
|
|
out[i] = strconv.FormatFloat(float64(val), 'f', -1, 64)
|
|
}
|
|
case []float64:
|
|
out = make([]string, len(value))
|
|
for i, val := range value {
|
|
out[i] = strconv.FormatFloat(val, 'f', -1, 64)
|
|
}
|
|
case [][]uint8:
|
|
out = make([]string, len(value))
|
|
for i, val := range value {
|
|
out[i] = string(val) // byte is an alias for uint8 conversions implicit
|
|
}
|
|
case []time.Duration:
|
|
out = make([]string, len(value))
|
|
for i, val := range value {
|
|
out[i] = val.String()
|
|
}
|
|
case []time.Time:
|
|
out = make([]string, len(value))
|
|
for i, val := range value {
|
|
out[i] = val.Format(time.RFC3339)
|
|
}
|
|
case []net.IP:
|
|
out = make([]string, len(value))
|
|
for i, val := range value {
|
|
out[i] = val.String()
|
|
}
|
|
case []string:
|
|
out = value
|
|
case []any:
|
|
out = make([]string, len(value))
|
|
for i, val := range value {
|
|
out[i] = IfaceAsString(val)
|
|
}
|
|
default:
|
|
err = fmt.Errorf("cannot convert field: %T to []string", value)
|
|
}
|
|
return
|
|
}
|
|
|
|
func GetUniformType(item any) (any, error) {
|
|
valItm := reflect.ValueOf(item)
|
|
switch valItm.Kind() { // convert evreting to float64
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return float64(valItm.Int()), nil
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
return float64(valItm.Uint()), nil
|
|
case reflect.Float32, reflect.Float64:
|
|
return valItm.Float(), nil
|
|
case reflect.Struct: // used only for time
|
|
return valItm.Interface(), nil
|
|
default:
|
|
return nil, errors.New("incomparable")
|
|
}
|
|
}
|
|
|
|
func GetBasicType(item any) any {
|
|
valItm := reflect.ValueOf(item)
|
|
switch valItm.Kind() { // convert evreting to float64
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return valItm.Int()
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
return valItm.Uint()
|
|
case reflect.Float32, reflect.Float64:
|
|
return valItm.Float()
|
|
default:
|
|
return item
|
|
}
|
|
}
|
|
|
|
// GreaterThan attempts to compare two items
|
|
// returns the result or error if not comparable
|
|
func GreaterThan(item, oItem any, orEqual bool) (gte bool, err error) {
|
|
item = GetBasicType(item)
|
|
oItem = GetBasicType(oItem)
|
|
typItem := reflect.TypeOf(item)
|
|
typOItem := reflect.TypeOf(oItem)
|
|
if typItem != typOItem {
|
|
if item, err = GetUniformType(item); err == nil { // overwrite type only if possible
|
|
typItem = reflect.TypeOf(item)
|
|
}
|
|
if oItem, err = GetUniformType(oItem); err == nil {
|
|
typOItem = reflect.TypeOf(oItem)
|
|
}
|
|
}
|
|
if !typItem.Comparable() ||
|
|
!typOItem.Comparable() ||
|
|
typItem != typOItem {
|
|
return false, fmt.Errorf("incomparable: <%+v> with <%+v>", item, oItem)
|
|
}
|
|
switch tVal := item.(type) {
|
|
case float64:
|
|
tOVal := oItem.(float64)
|
|
if orEqual {
|
|
gte = tVal >= tOVal
|
|
} else {
|
|
gte = tVal > tOVal
|
|
}
|
|
case uint64:
|
|
tOVal := oItem.(uint64)
|
|
if orEqual {
|
|
gte = tVal >= tOVal
|
|
} else {
|
|
gte = tVal > tOVal
|
|
}
|
|
case int64:
|
|
tOVal := oItem.(int64)
|
|
if orEqual {
|
|
gte = tVal >= tOVal
|
|
} else {
|
|
gte = tVal > tOVal
|
|
}
|
|
case time.Time:
|
|
tOVal := oItem.(time.Time)
|
|
if orEqual {
|
|
gte = tVal == tOVal
|
|
}
|
|
if !gte {
|
|
gte = tVal.After(tOVal)
|
|
}
|
|
default: // unsupported comparison
|
|
err = fmt.Errorf("unsupported comparison type: %v, kind: %v", typItem, typItem.Kind())
|
|
}
|
|
return
|
|
}
|
|
|
|
func EqualTo(item, oItem any) (eq bool, err error) {
|
|
item = GetBasicType(item)
|
|
oItem = GetBasicType(oItem)
|
|
typItem := reflect.TypeOf(item)
|
|
typOItem := reflect.TypeOf(oItem)
|
|
if typItem != typOItem {
|
|
if item, err = GetUniformType(item); err == nil { // overwrite type only if possible
|
|
typItem = reflect.TypeOf(item)
|
|
}
|
|
if oItem, err = GetUniformType(oItem); err == nil {
|
|
typOItem = reflect.TypeOf(oItem)
|
|
}
|
|
}
|
|
if !typItem.Comparable() ||
|
|
!typOItem.Comparable() ||
|
|
typItem != typOItem {
|
|
return false, fmt.Errorf("incomparable: <%+v> with <%+v>", item, oItem)
|
|
}
|
|
switch tVal := item.(type) {
|
|
case float64:
|
|
tOVal := oItem.(float64)
|
|
eq = tVal == tOVal
|
|
case uint64:
|
|
tOVal := oItem.(uint64)
|
|
eq = tVal == tOVal
|
|
case int64:
|
|
tOVal := oItem.(int64)
|
|
eq = tVal == tOVal
|
|
case time.Time:
|
|
tOVal := oItem.(time.Time)
|
|
eq = tVal == tOVal
|
|
case string:
|
|
tOVal := oItem.(string)
|
|
eq = tVal == tOVal
|
|
default: // unsupported comparison
|
|
err = fmt.Errorf("unsupported comparison type: %v, kind: %v", typItem, typItem.Kind())
|
|
}
|
|
return
|
|
}
|
|
|
|
// Sum attempts to sum multiple items
|
|
// returns the result or error if not comparable
|
|
func Sum(items ...any) (sum any, err error) {
|
|
//we need at least 2 items to sum them
|
|
if len(items) < 2 {
|
|
return nil, ErrNotEnoughParameters
|
|
}
|
|
|
|
switch dt := items[0].(type) {
|
|
case time.Duration:
|
|
s := dt
|
|
for _, item := range items[1:] {
|
|
if itmVal, err := IfaceAsDuration(item); err != nil {
|
|
return nil, err
|
|
} else {
|
|
s += itmVal
|
|
}
|
|
}
|
|
sum = s
|
|
case time.Time:
|
|
s := dt
|
|
for _, item := range items[1:] {
|
|
if itmVal, err := IfaceAsDuration(item); err != nil {
|
|
return nil, err
|
|
} else {
|
|
s = s.Add(itmVal)
|
|
}
|
|
}
|
|
sum = s
|
|
case float64:
|
|
s := dt
|
|
for _, item := range items[1:] {
|
|
if itmVal, err := IfaceAsFloat64(item); err != nil {
|
|
return nil, err
|
|
} else {
|
|
s += itmVal
|
|
}
|
|
}
|
|
sum = s
|
|
case int64:
|
|
s := dt
|
|
for _, item := range items[1:] {
|
|
if itmVal, err := IfaceAsInt64(item); err != nil {
|
|
return nil, err
|
|
} else {
|
|
s += itmVal
|
|
}
|
|
}
|
|
sum = s
|
|
case int:
|
|
s := int64(dt)
|
|
for _, item := range items[1:] {
|
|
if itmVal, err := IfaceAsInt64(item); err != nil {
|
|
return nil, err
|
|
} else {
|
|
s += itmVal
|
|
}
|
|
}
|
|
sum = s
|
|
}
|
|
return
|
|
}
|
|
|
|
// Difference attempts to sum multiple items
|
|
// returns the result or error if not comparable
|
|
func Difference(tm string, items ...any) (diff any, err error) {
|
|
//we need at least 2 items to diff them
|
|
if len(items) < 2 {
|
|
return nil, ErrNotEnoughParameters
|
|
}
|
|
switch dt := items[0].(type) {
|
|
case time.Duration:
|
|
d := dt
|
|
for _, item := range items[1:] {
|
|
if itmVal, err := IfaceAsDuration(item); err != nil {
|
|
return nil, err
|
|
} else {
|
|
d -= itmVal
|
|
}
|
|
}
|
|
diff = d
|
|
case time.Time:
|
|
d := dt
|
|
for i, item := range items[1:] {
|
|
if itmVal, err := IfaceAsTime(item, tm); err == nil {
|
|
diff = d.Sub(itmVal)
|
|
if len(items) == i+1 {
|
|
return diff, nil
|
|
}
|
|
items[i] = diff
|
|
return Difference(tm, items[i:]...)
|
|
}
|
|
|
|
if itmVal, err := IfaceAsDuration(item); err != nil {
|
|
return nil, err
|
|
} else {
|
|
d = d.Add(-itmVal)
|
|
}
|
|
}
|
|
diff = d
|
|
case float64:
|
|
d := dt
|
|
for _, item := range items[1:] {
|
|
if itmVal, err := IfaceAsFloat64(item); err != nil {
|
|
return nil, err
|
|
} else {
|
|
d -= itmVal
|
|
}
|
|
}
|
|
diff = d
|
|
case int64:
|
|
d := dt
|
|
for _, item := range items[1:] {
|
|
if itmVal, err := IfaceAsInt64(item); err != nil {
|
|
return nil, err
|
|
} else {
|
|
d -= itmVal
|
|
}
|
|
}
|
|
diff = d
|
|
case int:
|
|
d := int64(dt)
|
|
for _, item := range items[1:] {
|
|
if itmVal, err := IfaceAsInt64(item); err != nil {
|
|
return nil, err
|
|
} else {
|
|
d -= itmVal
|
|
}
|
|
}
|
|
diff = d
|
|
default: // unsupported comparison
|
|
return nil, fmt.Errorf("unsupported type")
|
|
}
|
|
return
|
|
}
|
|
|
|
// Multiply attempts to multiply multiple items
|
|
// returns the result or error if not comparable
|
|
func Multiply(items ...any) (mlt any, err error) {
|
|
//we need at least 2 items to diff them
|
|
if len(items) < 2 {
|
|
return nil, ErrNotEnoughParameters
|
|
}
|
|
switch dt := items[0].(type) {
|
|
case float64:
|
|
m := dt
|
|
for _, item := range items[1:] {
|
|
if itmVal, err := IfaceAsFloat64(item); err != nil {
|
|
return nil, err
|
|
} else {
|
|
m *= itmVal
|
|
}
|
|
}
|
|
mlt = m
|
|
case int64:
|
|
m := dt
|
|
for _, item := range items[1:] {
|
|
if itmVal, err := IfaceAsInt64(item); err != nil {
|
|
return nil, err
|
|
} else {
|
|
m *= itmVal
|
|
}
|
|
}
|
|
mlt = m
|
|
case int:
|
|
m := int64(dt)
|
|
for _, item := range items[1:] {
|
|
if itmVal, err := IfaceAsInt64(item); err != nil {
|
|
return nil, err
|
|
} else {
|
|
m *= itmVal
|
|
}
|
|
}
|
|
mlt = m
|
|
default: // unsupported comparison
|
|
return nil, fmt.Errorf("unsupported type")
|
|
}
|
|
return
|
|
}
|
|
|
|
// Divide attempts to divide multiple items
|
|
// returns the result or error if not comparable
|
|
func Divide(items ...any) (div any, err error) {
|
|
//we need at least 2 items to diff them
|
|
if len(items) < 2 {
|
|
return nil, ErrNotEnoughParameters
|
|
}
|
|
switch dt := items[0].(type) {
|
|
case float64:
|
|
d := dt
|
|
for _, item := range items[1:] {
|
|
if itmVal, err := IfaceAsFloat64(item); err != nil {
|
|
return nil, err
|
|
} else {
|
|
d /= itmVal
|
|
}
|
|
}
|
|
div = d
|
|
case int64:
|
|
d := dt
|
|
for _, item := range items[1:] {
|
|
if itmVal, err := IfaceAsInt64(item); err != nil {
|
|
return nil, err
|
|
} else {
|
|
d /= itmVal
|
|
}
|
|
}
|
|
div = d
|
|
case int:
|
|
d := int64(dt)
|
|
for _, item := range items[1:] {
|
|
if itmVal, err := IfaceAsInt64(item); err != nil {
|
|
return nil, err
|
|
} else {
|
|
d /= itmVal
|
|
}
|
|
}
|
|
div = d
|
|
default: // unsupported comparison
|
|
return nil, fmt.Errorf("unsupported type")
|
|
}
|
|
return
|
|
}
|
|
|
|
// ReflectFieldMethodInterface parses intf attepting to return the field value or error otherwise
|
|
// Supports "ExtraFields" where additional fields are dynamically inserted in map with field name: extraFieldsLabel
|
|
func ReflectFieldMethodInterface(obj any, fldName string) (retIf any, err error) {
|
|
v := reflect.ValueOf(obj)
|
|
if v.Kind() == reflect.Ptr {
|
|
v = v.Elem()
|
|
}
|
|
var field reflect.Value
|
|
switch v.Kind() {
|
|
case reflect.Struct:
|
|
field = v.FieldByName(fldName)
|
|
case reflect.Map:
|
|
field = v.MapIndex(reflect.ValueOf(fldName))
|
|
case reflect.Slice, reflect.Array:
|
|
//convert fldName to int
|
|
idx, err := strconv.Atoi(fldName)
|
|
if err == nil {
|
|
if idx >= v.Len() {
|
|
return nil, fmt.Errorf("index out of range")
|
|
}
|
|
field = v.Index(idx)
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unsupported field kind: %v", v.Kind())
|
|
}
|
|
if !field.IsValid() {
|
|
// handle function with pointer
|
|
v = reflect.ValueOf(obj)
|
|
field = v.MethodByName(fldName)
|
|
if !field.IsValid() {
|
|
return nil, ErrNotFound
|
|
} else {
|
|
if field.Type().NumIn() != 0 {
|
|
return nil, fmt.Errorf("invalid function called")
|
|
}
|
|
if field.Type().NumOut() > 2 {
|
|
return nil, fmt.Errorf("invalid function called")
|
|
}
|
|
// the function have two parameters in return and check if the second is of type error
|
|
if field.Type().NumOut() == 2 {
|
|
errorInterface := reflect.TypeOf((*error)(nil)).Elem()
|
|
if !field.Type().Out(1).Implements(errorInterface) {
|
|
return nil, fmt.Errorf("invalid function called")
|
|
}
|
|
}
|
|
fields := field.Call([]reflect.Value{})
|
|
if len(fields) == 2 && !fields[1].IsNil() {
|
|
return fields[0].Interface(), fields[1].Interface().(error)
|
|
}
|
|
return fields[0].Interface(), nil
|
|
}
|
|
}
|
|
return field.Interface(), nil
|
|
}
|