Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}