| package mapstructure |
| |
| import ( |
| "errors" |
| "fmt" |
| "net" |
| "reflect" |
| "strconv" |
| "strings" |
| "time" |
| ) |
| |
| // typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns |
| // it into the proper DecodeHookFunc type, such as DecodeHookFuncType. |
| func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc { |
| // Create variables here so we can reference them with the reflect pkg |
| var f1 DecodeHookFuncType |
| var f2 DecodeHookFuncKind |
| |
| // Fill in the variables into this interface and the rest is done |
| // automatically using the reflect package. |
| potential := []interface{}{f1, f2} |
| |
| v := reflect.ValueOf(h) |
| vt := v.Type() |
| for _, raw := range potential { |
| pt := reflect.ValueOf(raw).Type() |
| if vt.ConvertibleTo(pt) { |
| return v.Convert(pt).Interface() |
| } |
| } |
| |
| return nil |
| } |
| |
| // DecodeHookExec executes the given decode hook. This should be used |
| // since it'll naturally degrade to the older backwards compatible DecodeHookFunc |
| // that took reflect.Kind instead of reflect.Type. |
| func DecodeHookExec( |
| raw DecodeHookFunc, |
| from reflect.Type, to reflect.Type, |
| data interface{}) (interface{}, error) { |
| switch f := typedDecodeHook(raw).(type) { |
| case DecodeHookFuncType: |
| return f(from, to, data) |
| case DecodeHookFuncKind: |
| return f(from.Kind(), to.Kind(), data) |
| default: |
| return nil, errors.New("invalid decode hook signature") |
| } |
| } |
| |
| // ComposeDecodeHookFunc creates a single DecodeHookFunc that |
| // automatically composes multiple DecodeHookFuncs. |
| // |
| // The composed funcs are called in order, with the result of the |
| // previous transformation. |
| func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc { |
| return func( |
| f reflect.Type, |
| t reflect.Type, |
| data interface{}) (interface{}, error) { |
| var err error |
| for _, f1 := range fs { |
| data, err = DecodeHookExec(f1, f, t, data) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Modify the from kind to be correct with the new data |
| f = nil |
| if val := reflect.ValueOf(data); val.IsValid() { |
| f = val.Type() |
| } |
| } |
| |
| return data, nil |
| } |
| } |
| |
| // StringToSliceHookFunc returns a DecodeHookFunc that converts |
| // string to []string by splitting on the given sep. |
| func StringToSliceHookFunc(sep string) DecodeHookFunc { |
| return func( |
| f reflect.Kind, |
| t reflect.Kind, |
| data interface{}) (interface{}, error) { |
| if f != reflect.String || t != reflect.Slice { |
| return data, nil |
| } |
| |
| raw := data.(string) |
| if raw == "" { |
| return []string{}, nil |
| } |
| |
| return strings.Split(raw, sep), nil |
| } |
| } |
| |
| // StringToTimeDurationHookFunc returns a DecodeHookFunc that converts |
| // strings to time.Duration. |
| func StringToTimeDurationHookFunc() DecodeHookFunc { |
| return func( |
| f reflect.Type, |
| t reflect.Type, |
| data interface{}) (interface{}, error) { |
| if f.Kind() != reflect.String { |
| return data, nil |
| } |
| if t != reflect.TypeOf(time.Duration(5)) { |
| return data, nil |
| } |
| |
| // Convert it by parsing |
| return time.ParseDuration(data.(string)) |
| } |
| } |
| |
| // StringToIPHookFunc returns a DecodeHookFunc that converts |
| // strings to net.IP |
| func StringToIPHookFunc() DecodeHookFunc { |
| return func( |
| f reflect.Type, |
| t reflect.Type, |
| data interface{}) (interface{}, error) { |
| if f.Kind() != reflect.String { |
| return data, nil |
| } |
| if t != reflect.TypeOf(net.IP{}) { |
| return data, nil |
| } |
| |
| // Convert it by parsing |
| ip := net.ParseIP(data.(string)) |
| if ip == nil { |
| return net.IP{}, fmt.Errorf("failed parsing ip %v", data) |
| } |
| |
| return ip, nil |
| } |
| } |
| |
| // StringToIPNetHookFunc returns a DecodeHookFunc that converts |
| // strings to net.IPNet |
| func StringToIPNetHookFunc() DecodeHookFunc { |
| return func( |
| f reflect.Type, |
| t reflect.Type, |
| data interface{}) (interface{}, error) { |
| if f.Kind() != reflect.String { |
| return data, nil |
| } |
| if t != reflect.TypeOf(net.IPNet{}) { |
| return data, nil |
| } |
| |
| // Convert it by parsing |
| _, net, err := net.ParseCIDR(data.(string)) |
| return net, err |
| } |
| } |
| |
| // StringToTimeHookFunc returns a DecodeHookFunc that converts |
| // strings to time.Time. |
| func StringToTimeHookFunc(layout string) DecodeHookFunc { |
| return func( |
| f reflect.Type, |
| t reflect.Type, |
| data interface{}) (interface{}, error) { |
| if f.Kind() != reflect.String { |
| return data, nil |
| } |
| if t != reflect.TypeOf(time.Time{}) { |
| return data, nil |
| } |
| |
| // Convert it by parsing |
| return time.Parse(layout, data.(string)) |
| } |
| } |
| |
| // WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to |
| // the decoder. |
| // |
| // Note that this is significantly different from the WeaklyTypedInput option |
| // of the DecoderConfig. |
| func WeaklyTypedHook( |
| f reflect.Kind, |
| t reflect.Kind, |
| data interface{}) (interface{}, error) { |
| dataVal := reflect.ValueOf(data) |
| switch t { |
| case reflect.String: |
| switch f { |
| case reflect.Bool: |
| if dataVal.Bool() { |
| return "1", nil |
| } |
| return "0", nil |
| case reflect.Float32: |
| return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil |
| case reflect.Int: |
| return strconv.FormatInt(dataVal.Int(), 10), nil |
| case reflect.Slice: |
| dataType := dataVal.Type() |
| elemKind := dataType.Elem().Kind() |
| if elemKind == reflect.Uint8 { |
| return string(dataVal.Interface().([]uint8)), nil |
| } |
| case reflect.Uint: |
| return strconv.FormatUint(dataVal.Uint(), 10), nil |
| } |
| } |
| |
| return data, nil |
| } |