Skip to content

Commit

Permalink
refactor: merge DecodeMapStruct from api and utils packages (apache#7696
Browse files Browse the repository at this point in the history
)
  • Loading branch information
klesh authored Jul 4, 2024
1 parent 6211126 commit cdfd18a
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 125 deletions.
98 changes: 3 additions & 95 deletions backend/helpers/pluginhelper/api/mapstructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,108 +18,16 @@ limitations under the License.
package api

import (
"encoding/json"
"github.com/spf13/cast"
"reflect"
"time"

"github.com/apache/incubator-devlake/core/errors"
"github.com/apache/incubator-devlake/core/models"
"github.com/apache/incubator-devlake/core/models/common"
"github.com/apache/incubator-devlake/helpers/utils"
"github.com/go-playground/validator/v10"

"github.com/mitchellh/mapstructure"
)

var defaultCustomDecoderHooks = []mapstructure.DecodeHookFunc{decodeHookStringFloat64, decodeHookStringToTime, DecodeHook}

func decodeHookStringFloat64(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
if f.Kind() == reflect.Float64 && t.Kind() == reflect.Struct && t == reflect.TypeOf(common.StringFloat64{}) {
return *common.NewStringFloat64FromAny(data), nil
}
if f.Kind() == reflect.Float64 && t.Kind() == reflect.Ptr && t == reflect.TypeOf(&common.StringFloat64{}) {
return common.NewStringFloat64FromAny(data), nil
}
return data, nil
}

func decodeHookStringToTime(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
if f.Kind() == reflect.String {
if t.Kind() == reflect.Struct && t == reflect.TypeOf(time.Time{}) {
timeData, err := common.ConvertStringToTime(cast.ToString(data))
if err != nil {
return data, err
}
return timeData, nil
}
if t.Kind() == reflect.Ptr && t == reflect.TypeOf(&time.Time{}) {
timeData, err := common.ConvertStringToTime(cast.ToString(data))
if err != nil {
return data, err
}
return &timeData, nil
}
}
return data, nil
}

func DecodeHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
if data == nil {
return nil, nil
}
if t == reflect.TypeOf(json.RawMessage{}) {
return json.Marshal(data)
}
if t != reflect.TypeOf(common.Iso8601Time{}) && t != reflect.TypeOf(time.Time{}) {
return data, nil
}

var tt time.Time
var err error
switch f.Kind() {
case reflect.String:
tt, err = common.ConvertStringToTime(data.(string))
case reflect.Float64:
tt = time.Unix(0, int64(data.(float64))*int64(time.Millisecond))
case reflect.Int64:
tt = time.Unix(0, data.(int64)*int64(time.Millisecond))
case reflect.Struct:
if f == reflect.TypeOf(time.Time{}) || f == reflect.TypeOf(common.Iso8601Time{}) {
return data, nil
}
case reflect.Ptr:
if f == reflect.TypeOf(&time.Time{}) || f == reflect.TypeOf(&common.Iso8601Time{}) {
return data, nil
}
}
if err != nil {
return data, nil
}
switch t {
case reflect.TypeOf(common.Iso8601Time{}):
return common.Iso8601Time{Time: tt}, nil
case reflect.TypeOf(&common.Iso8601Time{}):
return &common.Iso8601Time{Time: tt}, nil
}
return data, nil
}

// DecodeMapStruct with time.Time and Iso8601Time support
func DecodeMapStruct(input map[string]interface{}, result interface{}, zeroFields bool) errors.Error {
result = models.UnwrapObject(result)
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
ZeroFields: zeroFields,
DecodeHook: mapstructure.ComposeDecodeHookFunc(defaultCustomDecoderHooks...),
Result: result,
WeaklyTypedInput: true,
})
if err != nil {
return errors.Convert(err)
}
if err := decoder.Decode(input); err != nil {
return errors.Convert(err)
}
return errors.Convert(err)
func DecodeMapStruct(input interface{}, result interface{}, zeroFields bool) errors.Error {
return utils.DecodeMapStruct(input, result, zeroFields)
}

// Decode decodes `source` into `target`. Pass an optional validator to validate the target.
Expand Down
111 changes: 81 additions & 30 deletions backend/helpers/utils/mapstructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,59 @@ import (

"github.com/apache/incubator-devlake/core/models"
"github.com/apache/incubator-devlake/core/models/common"
"github.com/spf13/cast"

"github.com/apache/incubator-devlake/core/errors"
"github.com/go-playground/validator/v10"

"github.com/mitchellh/mapstructure"
)

func DecodeHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
if data == nil {
return nil, nil
func decodeHookStringFloat64(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
if f.Kind() == reflect.Float64 && t.Kind() == reflect.Struct && t == reflect.TypeOf(common.StringFloat64{}) {
return *common.NewStringFloat64FromAny(data), nil
}
if t == reflect.TypeOf(json.RawMessage{}) {
return json.Marshal(data)
if f.Kind() == reflect.Float64 && t.Kind() == reflect.Ptr && t == reflect.TypeOf(&common.StringFloat64{}) {
return common.NewStringFloat64FromAny(data), nil
}
return data, nil
}

func decodeHookStringToTime(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
if f.Kind() == reflect.String {
if t.Kind() == reflect.Struct && t == reflect.TypeOf(time.Time{}) {
timeData, err := common.ConvertStringToTime(cast.ToString(data))
if err != nil {
return data, err
}
return timeData, nil
}
if t.Kind() == reflect.Ptr && t == reflect.TypeOf(&time.Time{}) {
timeData, err := common.ConvertStringToTime(cast.ToString(data))
if err != nil {
return data, err
}
return &timeData, nil
}
}
return data, nil
}

func zeroesSlice(from reflect.Value, to reflect.Value) (interface{}, error) {
if from.Kind() == reflect.Slice && from.IsNil() {
return from.Interface(), nil
}

if to.CanSet() && to.Kind() == reflect.Slice {
to.Set(reflect.MakeSlice(reflect.SliceOf(to.Type().Elem()), 0, 0))
}

return from.Interface(), nil
}

func decodeUrlValues(from reflect.Type, to reflect.Type, data interface{}) (interface{}, error) {
// to support decoding url.Values (query string) to non-array variables
if t.Kind() != reflect.Slice && t.Kind() != reflect.Array &&
(f.Kind() == reflect.Slice || f.Kind() == reflect.Array) {
if to.Kind() != reflect.Slice && to.Kind() != reflect.Array &&
(from.Kind() == reflect.Slice || from.Kind() == reflect.Array) {
v := reflect.ValueOf(data)
if v.Len() == 1 {
data = v.Index(0).Interface()
Expand All @@ -49,38 +85,67 @@ func DecodeHook(f reflect.Type, t reflect.Type, data interface{}) (interface{},
return result, err
}
}
return data, nil
}

if t != reflect.TypeOf(common.Iso8601Time{}) && t != reflect.TypeOf(time.Time{}) {
func decodeJsonRawMessage(from reflect.Type, to reflect.Type, data interface{}) (interface{}, error) {
if to == reflect.TypeOf(json.RawMessage{}) {
return json.Marshal(data)
}
return data, nil
}

func decodeIso8601Time(from reflect.Type, to reflect.Type, data interface{}) (interface{}, error) {
if data == nil {
return nil, nil
}
if to != reflect.TypeOf(common.Iso8601Time{}) && to != reflect.TypeOf(time.Time{}) {
return data, nil
}

var tt time.Time
var err error

switch f.Kind() {
switch from.Kind() {
case reflect.String:
tt, err = common.ConvertStringToTime(data.(string))
case reflect.Float64:
tt = time.Unix(0, int64(data.(float64))*int64(time.Millisecond))
case reflect.Int64:
tt = time.Unix(0, data.(int64)*int64(time.Millisecond))
case reflect.Struct:
if from == reflect.TypeOf(time.Time{}) || from == reflect.TypeOf(common.Iso8601Time{}) {
return data, nil
}
case reflect.Ptr:
if from == reflect.TypeOf(&time.Time{}) || from == reflect.TypeOf(&common.Iso8601Time{}) {
return data, nil
}
}
if err != nil {
return data, nil
}

if t == reflect.TypeOf(common.Iso8601Time{}) {
switch to {
case reflect.TypeOf(common.Iso8601Time{}):
return common.Iso8601Time{Time: tt}, nil
case reflect.TypeOf(&common.Iso8601Time{}):
return &common.Iso8601Time{Time: tt}, nil
}
return tt, nil
return data, nil
}

// DecodeMapStruct with time.Time and Iso8601Time support
func DecodeMapStruct(input interface{}, result interface{}, zeroFields bool) errors.Error {
result = models.UnwrapObject(result)
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
ZeroFields: zeroFields,
DecodeHook: mapstructure.ComposeDecodeHookFunc(DecodeHook),
ZeroFields: zeroFields,
DecodeHook: mapstructure.ComposeDecodeHookFunc(
decodeHookStringFloat64,
decodeHookStringToTime,
decodeUrlValues,
decodeJsonRawMessage,
decodeIso8601Time,
zeroesSlice,
),
Result: result,
WeaklyTypedInput: true,
})
Expand All @@ -93,17 +158,3 @@ func DecodeMapStruct(input interface{}, result interface{}, zeroFields bool) err
}
return errors.Convert(err)
}

// Decode decodes `source` into `target`. Pass an optional validator to validate the target.
func Decode(source interface{}, target interface{}, vld *validator.Validate) errors.Error {
target = models.UnwrapObject(target)
if err := mapstructure.Decode(source, &target); err != nil {
return errors.Default.Wrap(err, "error decoding map into target type")
}
if vld != nil {
if err := vld.Struct(target); err != nil {
return errors.Default.Wrap(err, "error validating target")
}
}
return nil
}

0 comments on commit cdfd18a

Please sign in to comment.