From 924a0933f2e39e4aba1ff2bb10aba3c35cf0763b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 28 Nov 2019 12:35:49 +0200 Subject: [PATCH] Updated ApierV1.SetAccountActionTriggers --- apier/v1/apier_it_test.go | 9 +- apier/v1/triggers.go | 270 +++++++++++++++++++++--------------- apier/v2/apierv2_it_test.go | 24 ++-- apier/v2/triggers.go | 194 -------------------------- utils/consts.go | 18 +++ utils/reflect.go | 78 +++++++++++ utils/struct.go | 22 +++ 7 files changed, 300 insertions(+), 315 deletions(-) delete mode 100644 apier/v2/triggers.go diff --git a/apier/v1/apier_it_test.go b/apier/v1/apier_it_test.go index e02ffbcb1..65782d8e5 100644 --- a/apier/v1/apier_it_test.go +++ b/apier/v1/apier_it_test.go @@ -1119,7 +1119,14 @@ func TestApierSetAccountActionTriggers(t *testing.T) { t.Errorf("Unexpected action triggers received %v", reply) } var setReply string - setReq := AttrSetAccountActionTriggers{Tenant: "cgrates.org", Account: "dan2", UniqueID: reply[0].UniqueID, ActivationDate: utils.StringPointer("2016-02-05T18:00:00Z")} + setReq := AttrSetAccountActionTriggers{ + Tenant: "cgrates.org", + Account: "dan2", + UniqueID: reply[0].UniqueID, + ActionTrigger: map[string]interface{}{ + utils.ActivationDate: "2016-02-05T18:00:00Z", + }, + } if err := rater.Call(utils.ApierV1ResetAccountActionTriggers, setReq, &setReply); err != nil { t.Error("Got error on ApierV1.ResetActionTiming: ", err.Error()) } else if setReply != utils.OK { diff --git a/apier/v1/triggers.go b/apier/v1/triggers.go index c5898a54b..e3424a36e 100644 --- a/apier/v1/triggers.go +++ b/apier/v1/triggers.go @@ -19,6 +19,7 @@ along with this program. If not, see package v1 import ( + "errors" "strings" "time" @@ -185,34 +186,154 @@ func (api *ApierV1) ResetAccountActionTriggers(attr AttrResetAccountActionTrigge } type AttrSetAccountActionTriggers struct { - Tenant string - Account string - GroupID string - UniqueID string - ThresholdType *string - ThresholdValue *float64 - Recurrent *bool - Executed *bool - MinSleep *string - ExpirationDate *string - ActivationDate *string - BalanceID *string - BalanceType *string - BalanceDestinationIds *[]string - BalanceWeight *float64 - BalanceExpirationDate *string - BalanceTimingTags *[]string - BalanceRatingSubject *string - BalanceCategories *[]string - BalanceSharedGroups *[]string - BalanceBlocker *bool - BalanceDisabled *bool - MinQueuedItems *int - ActionsID *string + Tenant string + Account string + GroupID string + UniqueID string + ActionTrigger map[string]interface{} } -func (api *ApierV1) SetAccountActionTriggers(attr AttrSetAccountActionTriggers, reply *string) error { +// UpdateActionTrigger updates the ActionTrigger if is matching +func (attr *AttrSetAccountActionTriggers) UpdateActionTrigger(at *engine.ActionTrigger, timezone string) (updated bool, err error) { + if at == nil { + return false, errors.New("Empty ActionTrigger") + } + if at.ID == utils.EmptyString { // New AT, update it's data + if attr.GroupID == utils.EmptyString { + return false, utils.NewErrMandatoryIeMissing(utils.GroupID) + } + if missing := utils.MissingMapFields(attr.ActionTrigger, []string{"ThresholdType", "ThresholdValue"}); len(missing) != 0 { + return false, utils.NewErrMandatoryIeMissing(missing...) + } + at.ID = attr.GroupID + if attr.UniqueID != utils.EmptyString { + at.UniqueID = attr.UniqueID + } + } + if attr.GroupID != utils.EmptyString && attr.GroupID != at.ID { + return + } + if attr.UniqueID != utils.EmptyString && attr.UniqueID != at.UniqueID { + return + } + // at matches + updated = true + if thr, has := attr.ActionTrigger[utils.ThresholdType]; has { + at.ThresholdType = utils.IfaceAsString(thr) + } + if thr, has := attr.ActionTrigger[utils.ThresholdValue]; has { + if at.ThresholdValue, err = utils.IfaceAsFloat64(thr); err != nil { + return + } + } + if rec, has := attr.ActionTrigger[utils.Recurrent]; has { + if at.Recurrent, err = utils.IfaceAsBool(rec); err != nil { + return + } + } + if exec, has := attr.ActionTrigger[utils.Executed]; has { + if at.Executed, err = utils.IfaceAsBool(exec); err != nil { + return + } + } + if minS, has := attr.ActionTrigger[utils.MinSleep]; has { + if at.MinSleep, err = utils.IfaceAsDuration(minS); err != nil { + return + } + } + if exp, has := attr.ActionTrigger[utils.ExpirationDate]; has { + if at.ExpirationDate, err = utils.IfaceAsTime(exp, timezone); err != nil { + return + } + } + if act, has := attr.ActionTrigger[utils.ActivationDate]; has { + if at.ActivationDate, err = utils.IfaceAsTime(act, timezone); err != nil { + return + } + } + if at.Balance == nil { + at.Balance = &engine.BalanceFilter{} + } + if bid, has := attr.ActionTrigger[utils.BalanceID]; has { + at.Balance.ID = utils.StringPointer(utils.IfaceAsString(bid)) + } + if btype, has := attr.ActionTrigger[utils.BalanceType]; has { + at.Balance.Type = utils.StringPointer(utils.IfaceAsString(btype)) + } + if bdest, has := attr.ActionTrigger[utils.BalanceDestinationIds]; has { + var bdIds []string + if bdIds, err = utils.IfaceAsSliceString(bdest); err != nil { + return + } + at.Balance.DestinationIDs = utils.StringMapPointer(utils.NewStringMap(bdIds...)) + } + if bweight, has := attr.ActionTrigger[utils.BalanceWeight]; has { + var bw float64 + if bw, err = utils.IfaceAsFloat64(bweight); err != nil { + return + } + at.Balance.Weight = utils.Float64Pointer(bw) + } + if exp, has := attr.ActionTrigger[utils.BalanceExpirationDate]; has { + var balanceExpTime time.Time + if balanceExpTime, err = utils.IfaceAsTime(exp, timezone); err != nil { + return + } + at.Balance.ExpirationDate = utils.TimePointer(balanceExpTime) + } + if bTimeTag, has := attr.ActionTrigger[utils.BalanceTimingTags]; has { + var timeTag []string + if timeTag, err = utils.IfaceAsSliceString(bTimeTag); err != nil { + return + } + at.Balance.TimingIDs = utils.StringMapPointer(utils.NewStringMap(timeTag...)) + } + if brs, has := attr.ActionTrigger[utils.BalanceRatingSubject]; has { + at.Balance.RatingSubject = utils.StringPointer(utils.IfaceAsString(brs)) + } + if bcat, has := attr.ActionTrigger[utils.BalanceCategories]; has { + var cat []string + if cat, err = utils.IfaceAsSliceString(bcat); err != nil { + return + } + at.Balance.Categories = utils.StringMapPointer(utils.NewStringMap(cat...)) + } + if bsg, has := attr.ActionTrigger[utils.BalanceSharedGroups]; has { + var shrgrps []string + if shrgrps, err = utils.IfaceAsSliceString(bsg); err != nil { + return + } + at.Balance.SharedGroups = utils.StringMapPointer(utils.NewStringMap(shrgrps...)) + } + if bb, has := attr.ActionTrigger[utils.BalanceBlocker]; has { + var bBlocker bool + if bBlocker, err = utils.IfaceAsBool(bb); err != nil { + return + } + at.Balance.Blocker = utils.BoolPointer(bBlocker) + } + if bd, has := attr.ActionTrigger[utils.BalanceDisabled]; has { + var bDis bool + if bDis, err = utils.IfaceAsBool(bd); err != nil { + return + } + at.Balance.Disabled = utils.BoolPointer(bDis) + } + if minQ, has := attr.ActionTrigger[utils.MinQueuedItems]; has { + var mQ int64 + if mQ, err = utils.IfaceAsInt64(minQ); err != nil { + return + } + at.MinQueuedItems = int(mQ) + } + if accID, has := attr.ActionTrigger[utils.ActionsID]; has { + at.ActionsID = utils.IfaceAsString(accID) + } + return +} +// SetAccountActionTriggers updates or creates if not present the ActionTrigger for an Account +func (api *ApierV1) SetAccountActionTriggers(attr AttrSetAccountActionTriggers, reply *string) error { if missing := utils.MissingStructFields(&attr, []string{"Tenant", "Account"}); len(missing) != 0 { return utils.NewErrMandatoryIeMissing(missing...) } @@ -224,92 +345,23 @@ func (api *ApierV1) SetAccountActionTriggers(attr AttrSetAccountActionTriggers, } else { return 0, err } + var foundOne bool for _, at := range account.ActionTriggers { - if (attr.UniqueID == "" || at.UniqueID == attr.UniqueID) && - (attr.GroupID == "" || at.ID == attr.GroupID) { - // we have a winner - if attr.ThresholdType != nil { - at.ThresholdType = *attr.ThresholdType - } - if attr.ThresholdValue != nil { - at.ThresholdValue = *attr.ThresholdValue - } - if attr.Recurrent != nil { - at.Recurrent = *attr.Recurrent - } - if attr.Executed != nil { - at.Executed = *attr.Executed - } - if attr.MinSleep != nil { - minSleep, err := utils.ParseDurationWithNanosecs(*attr.MinSleep) - if err != nil { - return 0, err - } - at.MinSleep = minSleep - } - if attr.ExpirationDate != nil { - expTime, err := utils.ParseTimeDetectLayout(*attr.ExpirationDate, - api.Config.GeneralCfg().DefaultTimezone) - if err != nil { - return 0, err - } - at.ExpirationDate = expTime - } - if attr.ActivationDate != nil { - actTime, err := utils.ParseTimeDetectLayout(*attr.ActivationDate, - api.Config.GeneralCfg().DefaultTimezone) - if err != nil { - return 0, err - } - at.ActivationDate = actTime - } - at.Balance = &engine.BalanceFilter{} - if attr.BalanceID != nil { - at.Balance.ID = attr.BalanceID - } - if attr.BalanceType != nil { - at.Balance.Type = attr.BalanceType - } - if attr.BalanceDestinationIds != nil { - at.Balance.DestinationIDs = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceDestinationIds...)) - } - if attr.BalanceWeight != nil { - at.Balance.Weight = attr.BalanceWeight - } - if attr.BalanceExpirationDate != nil { - balanceExpTime, err := utils.ParseTimeDetectLayout(*attr.BalanceExpirationDate, - api.Config.GeneralCfg().DefaultTimezone) - if err != nil { - return 0, err - } - at.Balance.ExpirationDate = &balanceExpTime - } - if attr.BalanceTimingTags != nil { - at.Balance.TimingIDs = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceTimingTags...)) - } - if attr.BalanceRatingSubject != nil { - at.Balance.RatingSubject = attr.BalanceRatingSubject - } - if attr.BalanceCategories != nil { - at.Balance.Categories = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceCategories...)) - } - if attr.BalanceSharedGroups != nil { - at.Balance.SharedGroups = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceSharedGroups...)) - } - if attr.BalanceBlocker != nil { - at.Balance.Blocker = attr.BalanceBlocker - } - if attr.BalanceDisabled != nil { - at.Balance.Disabled = attr.BalanceDisabled - } - if attr.MinQueuedItems != nil { - at.MinQueuedItems = *attr.MinQueuedItems - } - if attr.ActionsID != nil { - at.ActionsID = *attr.ActionsID - } + if updated, err := attr.UpdateActionTrigger(at, + api.Config.GeneralCfg().DefaultTimezone); err != nil { + return 0, err + } else if updated && !foundOne { + foundOne = true + } + } + if !foundOne { // Did not find one to update, create a new AT + at := new(engine.ActionTrigger) + if updated, err := attr.UpdateActionTrigger(at, + api.Config.GeneralCfg().DefaultTimezone); err != nil { + return 0, err + } else if updated { // Adding a new AT + account.ActionTriggers = append(account.ActionTriggers, at) } - } account.ExecuteActionTriggers(nil) return 0, api.DataManager.SetAccount(account) diff --git a/apier/v2/apierv2_it_test.go b/apier/v2/apierv2_it_test.go index 14d339c35..c1698abfb 100644 --- a/apier/v2/apierv2_it_test.go +++ b/apier/v2/apierv2_it_test.go @@ -145,14 +145,16 @@ func TestApierV2itSetAction(t *testing.T) { } func TestApierV2itSetAccountActionTriggers(t *testing.T) { - attrs := AttrSetAccountActionTriggers{ - Tenant: "cgrates.org", - Account: "dan", - GroupID: utils.StringPointer("MONITOR_MAX_BALANCE"), - ThresholdType: utils.StringPointer(utils.TRIGGER_MAX_BALANCE), - ThresholdValue: utils.Float64Pointer(50), - BalanceType: utils.StringPointer(utils.MONETARY), - ActionsID: utils.StringPointer("DISABLE_ACCOUNT"), + attrs := v1.AttrSetAccountActionTriggers{ + Tenant: "cgrates.org", + Account: "dan", + GroupID: "MONITOR_MAX_BALANCE", + ActionTrigger: map[string]interface{}{ + utils.ThresholdType: utils.TRIGGER_MAX_BALANCE, + utils.ThresholdValue: 50, + utils.BalanceType: utils.MONETARY, + utils.ActionsID: "DISABLE_ACCOUNT", + }, } var reply string if err := apierRPC.Call(utils.ApierV2SetAccountActionTriggers, attrs, &reply); err != nil { @@ -161,16 +163,16 @@ func TestApierV2itSetAccountActionTriggers(t *testing.T) { var ats engine.ActionTriggers if err := apierRPC.Call(utils.ApierV2GetAccountActionTriggers, utils.TenantAccount{Tenant: "cgrates.org", Account: "dan"}, &ats); err != nil { t.Error(err) - } else if len(ats) != 1 || ats[0].ID != *attrs.GroupID || ats[0].ThresholdValue != 50.0 { + } else if len(ats) != 1 || ats[0].ID != attrs.GroupID || ats[0].ThresholdValue != 50.0 { t.Errorf("Received: %+v", ats) } - attrs.ThresholdValue = utils.Float64Pointer(55) // Change the threshold + attrs.ActionTrigger[utils.ThresholdValue] = 55 // Change the threshold if err := apierRPC.Call(utils.ApierV2SetAccountActionTriggers, attrs, &reply); err != nil { t.Error(err) } if err := apierRPC.Call(utils.ApierV2GetAccountActionTriggers, utils.TenantAccount{Tenant: "cgrates.org", Account: "dan"}, &ats); err != nil { t.Error(err) - } else if len(ats) != 1 || ats[0].ID != *attrs.GroupID || ats[0].ThresholdValue != 55.0 { + } else if len(ats) != 1 || ats[0].ID != attrs.GroupID || ats[0].ThresholdValue != 55.0 { t.Errorf("Received: %+v", ats) } } diff --git a/apier/v2/triggers.go b/apier/v2/triggers.go deleted file mode 100644 index 2c28a5dfb..000000000 --- a/apier/v2/triggers.go +++ /dev/null @@ -1,194 +0,0 @@ -/* -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 -*/ - -package v2 - -import ( - "errors" - - "github.com/cgrates/cgrates/config" - "github.com/cgrates/cgrates/engine" - "github.com/cgrates/cgrates/guardian" - "github.com/cgrates/cgrates/utils" -) - -type AttrSetAccountActionTriggers struct { - Tenant string - Account string - GroupID *string - UniqueID *string - ThresholdType *string - ThresholdValue *float64 - Recurrent *bool - Executed *bool - MinSleep *string - ExpirationDate *string - ActivationDate *string - BalanceID *string - BalanceType *string - BalanceDestinationIds *[]string - BalanceWeight *float64 - BalanceExpirationDate *string - BalanceTimingTags *[]string - BalanceRatingSubject *string - BalanceCategories *[]string - BalanceSharedGroups *[]string - BalanceBlocker *bool - BalanceDisabled *bool - MinQueuedItems *int - ActionsID *string -} - -func (attr *AttrSetAccountActionTriggers) UpdateActionTrigger(at *engine.ActionTrigger, timezone string) (updated bool, err error) { - if at == nil { - return false, errors.New("Empty ActionTrigger") - } - if at.ID == "" { // New AT, update it's data - if missing := utils.MissingStructFields(attr, []string{"GroupID", "ThresholdType", "ThresholdValue"}); len(missing) != 0 { - return false, utils.NewErrMandatoryIeMissing(missing...) - } - at.ID = *attr.GroupID - if attr.UniqueID != nil { - at.UniqueID = *attr.UniqueID - } - } - if attr.GroupID != nil && *attr.GroupID != at.ID { - return - } - if attr.UniqueID != nil && *attr.UniqueID != at.UniqueID { - return - } - // at matches - updated = true - if attr.ThresholdType != nil { - at.ThresholdType = *attr.ThresholdType - } - if attr.ThresholdValue != nil { - at.ThresholdValue = *attr.ThresholdValue - } - if attr.Recurrent != nil { - at.Recurrent = *attr.Recurrent - } - if attr.Executed != nil { - at.Executed = *attr.Executed - } - if attr.MinSleep != nil { - if at.MinSleep, err = utils.ParseDurationWithNanosecs(*attr.MinSleep); err != nil { - return - } - } - if attr.ExpirationDate != nil { - if at.ExpirationDate, err = utils.ParseTimeDetectLayout(*attr.ExpirationDate, timezone); err != nil { - return - } - } - if attr.ActivationDate != nil { - if at.ActivationDate, err = utils.ParseTimeDetectLayout(*attr.ActivationDate, timezone); err != nil { - return - } - } - if at.Balance == nil { - at.Balance = &engine.BalanceFilter{} - } - if attr.BalanceID != nil { - at.Balance.ID = attr.BalanceID - } - if attr.BalanceType != nil { - at.Balance.Type = attr.BalanceType - } - if attr.BalanceDestinationIds != nil { - at.Balance.DestinationIDs = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceDestinationIds...)) - } - if attr.BalanceWeight != nil { - at.Balance.Weight = attr.BalanceWeight - } - if attr.BalanceExpirationDate != nil { - balanceExpTime, err := utils.ParseTimeDetectLayout(*attr.BalanceExpirationDate, timezone) - if err != nil { - return false, err - } - at.Balance.ExpirationDate = &balanceExpTime - } - if attr.BalanceTimingTags != nil { - at.Balance.TimingIDs = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceTimingTags...)) - } - if attr.BalanceRatingSubject != nil { - at.Balance.RatingSubject = attr.BalanceRatingSubject - } - if attr.BalanceCategories != nil { - at.Balance.Categories = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceCategories...)) - } - if attr.BalanceSharedGroups != nil { - at.Balance.SharedGroups = utils.StringMapPointer(utils.NewStringMap(*attr.BalanceSharedGroups...)) - } - if attr.BalanceBlocker != nil { - at.Balance.Blocker = attr.BalanceBlocker - } - if attr.BalanceDisabled != nil { - at.Balance.Disabled = attr.BalanceDisabled - } - if attr.MinQueuedItems != nil { - at.MinQueuedItems = *attr.MinQueuedItems - } - if attr.ActionsID != nil { - at.ActionsID = *attr.ActionsID - } - return -} - -// SetAccountActionTriggers Updates or Creates ActionTriggers for an Account -func (self *ApierV2) SetAccountActionTriggers(attr AttrSetAccountActionTriggers, reply *string) error { - if missing := utils.MissingStructFields(&attr, []string{"Tenant", "Account"}); len(missing) != 0 { - return utils.NewErrMandatoryIeMissing(missing...) - } - accID := utils.ConcatenatedKey(attr.Tenant, attr.Account) - var account *engine.Account - _, err := guardian.Guardian.Guard(func() (interface{}, error) { - if acc, err := self.DataManager.GetAccount(accID); err == nil { - account = acc - } else { - return 0, err - } - var foundOne bool - for _, at := range account.ActionTriggers { - if updated, err := attr.UpdateActionTrigger(at, - self.Config.GeneralCfg().DefaultTimezone); err != nil { - return 0, err - } else if updated && !foundOne { - foundOne = true - } - } - if !foundOne { // Did not find one to update, create a new AT - at := new(engine.ActionTrigger) - if updated, err := attr.UpdateActionTrigger(at, - self.Config.GeneralCfg().DefaultTimezone); err != nil { - return 0, err - } else if updated { // Adding a new AT - account.ActionTriggers = append(account.ActionTriggers, at) - } - } - account.ExecuteActionTriggers(nil) - return 0, self.DataManager.SetAccount(account) - }, config.CgrConfig().GeneralCfg().LockingTimeout, utils.ACCOUNT_PREFIX+accID) - if err != nil { - *reply = err.Error() - return err - } - *reply = utils.OK - return nil -} diff --git a/utils/consts.go b/utils/consts.go index 5f47768ab..b828a9dbb 100755 --- a/utils/consts.go +++ b/utils/consts.go @@ -412,6 +412,15 @@ const ( StatID = "StatID" BalanceType = "BalanceType" BalanceID = "BalanceID" + BalanceDestinationIds = "BalanceDestinationIds" + BalanceWeight = "BalanceWeight" + BalanceExpirationDate = "BalanceExpirationDate" + BalanceTimingTags = "BalanceTimingTags" + BalanceRatingSubject = "BalanceRatingSubject" + BalanceCategories = "BalanceCategories" + BalanceSharedGroups = "BalanceSharedGroups" + BalanceBlocker = "BalanceBlocker" + BalanceDisabled = "BalanceDisabled" Units = "Units" AccountUpdate = "AccountUpdate" BalanceUpdate = "BalanceUpdate" @@ -572,6 +581,15 @@ const ( ExportPath = "ExportPath" ExportID = "ExportID" ExportFileName = "ExportFileName" + GroupID = "GroupID" + ThresholdType = "ThresholdType" + ThresholdValue = "ThresholdValue" + Recurrent = "Recurrent" + Executed = "Executed" + MinSleep = "MinSleep" + ActivationDate = "ActivationDate" + ExpirationDate = "ExpirationDate" + MinQueuedItems = "MinQueuedItems" ) // Migrator Action diff --git a/utils/reflect.go b/utils/reflect.go index 79338b1b5..87a06b98f 100644 --- a/utils/reflect.go +++ b/utils/reflect.go @@ -272,6 +272,84 @@ func IfaceAsString(fld interface{}) (out string) { } } +// IfaceAsSliceString is trying to convert the interface to a slice of strings +func IfaceAsSliceString(fld interface{}) (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 []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 []interface{}: + out = make([]string, len(value)) + for i, val := range value { + out[i] = IfaceAsString(val) + } + default: // Maybe we are lucky and the value converts to string + err = fmt.Errorf("cannot convert field: %+v to bool", value) + } + return +} + // AsMapStringIface converts an item (mostly struct) as map[string]interface{} func AsMapStringIface(item interface{}) (map[string]interface{}, error) { out := make(map[string]interface{}) diff --git a/utils/struct.go b/utils/struct.go index dccd01fed..81d17f946 100644 --- a/utils/struct.go +++ b/utils/struct.go @@ -68,6 +68,28 @@ func NonemptyStructFields(s interface{}) map[string]interface{} { return fields } +// MissingMapFields detects missing field values based on mandatory field names from a map[string]interface{} +func MissingMapFields(s map[string]interface{}, mandatories []string) []string { + missing := []string{} + for _, fieldName := range mandatories { + if fldval, has := s[fieldName]; !has { + missing = append(missing, fieldName) + } else { + fld := reflect.ValueOf(fldval) + // sanitize the string fields before checking + if fld.Kind() == reflect.String && fld.CanSet() { + fld.SetString(strings.TrimSpace(fld.String())) + } + if (fld.Kind() == reflect.String && fld.String() == "") || + ((fld.Kind() == reflect.Slice || fld.Kind() == reflect.Map) && fld.Len() == 0) || + (fld.Kind() == reflect.Int && fld.Int() == 0) { + missing = append(missing, fieldName) + } + } + } + return missing +} + // Converts a struct to map /*func StrucToMap(s interface{}) map[string]interface{} { mp := make(map[string]interface{})