1package mapstructure
2
3import (
4	"encoding"
5	"errors"
6	"fmt"
7	"net"
8	"reflect"
9	"strconv"
10	"strings"
11	"time"
12)
13
14// typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns
15// it into the proper DecodeHookFunc type, such as DecodeHookFuncType.
16func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc {
17	// Create variables here so we can reference them with the reflect pkg
18	var f1 DecodeHookFuncType
19	var f2 DecodeHookFuncKind
20	var f3 DecodeHookFuncValue
21
22	// Fill in the variables into this interface and the rest is done
23	// automatically using the reflect package.
24	potential := []interface{}{f1, f2, f3}
25
26	v := reflect.ValueOf(h)
27	vt := v.Type()
28	for _, raw := range potential {
29		pt := reflect.ValueOf(raw).Type()
30		if vt.ConvertibleTo(pt) {
31			return v.Convert(pt).Interface()
32		}
33	}
34
35	return nil
36}
37
38// DecodeHookExec executes the given decode hook. This should be used
39// since it'll naturally degrade to the older backwards compatible DecodeHookFunc
40// that took reflect.Kind instead of reflect.Type.
41func DecodeHookExec(
42	raw DecodeHookFunc,
43	from reflect.Value, to reflect.Value) (interface{}, error) {
44
45	switch f := typedDecodeHook(raw).(type) {
46	case DecodeHookFuncType:
47		return f(from.Type(), to.Type(), from.Interface())
48	case DecodeHookFuncKind:
49		return f(from.Kind(), to.Kind(), from.Interface())
50	case DecodeHookFuncValue:
51		return f(from, to)
52	default:
53		return nil, errors.New("invalid decode hook signature")
54	}
55}
56
57// ComposeDecodeHookFunc creates a single DecodeHookFunc that
58// automatically composes multiple DecodeHookFuncs.
59//
60// The composed funcs are called in order, with the result of the
61// previous transformation.
62func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
63	return func(f reflect.Value, t reflect.Value) (interface{}, error) {
64		var err error
65		var data interface{}
66		newFrom := f
67		for _, f1 := range fs {
68			data, err = DecodeHookExec(f1, newFrom, t)
69			if err != nil {
70				return nil, err
71			}
72			newFrom = reflect.ValueOf(data)
73		}
74
75		return data, nil
76	}
77}
78
79// StringToSliceHookFunc returns a DecodeHookFunc that converts
80// string to []string by splitting on the given sep.
81func StringToSliceHookFunc(sep string) DecodeHookFunc {
82	return func(
83		f reflect.Kind,
84		t reflect.Kind,
85		data interface{}) (interface{}, error) {
86		if f != reflect.String || t != reflect.Slice {
87			return data, nil
88		}
89
90		raw := data.(string)
91		if raw == "" {
92			return []string{}, nil
93		}
94
95		return strings.Split(raw, sep), nil
96	}
97}
98
99// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts
100// strings to time.Duration.
101func StringToTimeDurationHookFunc() DecodeHookFunc {
102	return func(
103		f reflect.Type,
104		t reflect.Type,
105		data interface{}) (interface{}, error) {
106		if f.Kind() != reflect.String {
107			return data, nil
108		}
109		if t != reflect.TypeOf(time.Duration(5)) {
110			return data, nil
111		}
112
113		// Convert it by parsing
114		return time.ParseDuration(data.(string))
115	}
116}
117
118// StringToIPHookFunc returns a DecodeHookFunc that converts
119// strings to net.IP
120func StringToIPHookFunc() DecodeHookFunc {
121	return func(
122		f reflect.Type,
123		t reflect.Type,
124		data interface{}) (interface{}, error) {
125		if f.Kind() != reflect.String {
126			return data, nil
127		}
128		if t != reflect.TypeOf(net.IP{}) {
129			return data, nil
130		}
131
132		// Convert it by parsing
133		ip := net.ParseIP(data.(string))
134		if ip == nil {
135			return net.IP{}, fmt.Errorf("failed parsing ip %v", data)
136		}
137
138		return ip, nil
139	}
140}
141
142// StringToIPNetHookFunc returns a DecodeHookFunc that converts
143// strings to net.IPNet
144func StringToIPNetHookFunc() DecodeHookFunc {
145	return func(
146		f reflect.Type,
147		t reflect.Type,
148		data interface{}) (interface{}, error) {
149		if f.Kind() != reflect.String {
150			return data, nil
151		}
152		if t != reflect.TypeOf(net.IPNet{}) {
153			return data, nil
154		}
155
156		// Convert it by parsing
157		_, net, err := net.ParseCIDR(data.(string))
158		return net, err
159	}
160}
161
162// StringToTimeHookFunc returns a DecodeHookFunc that converts
163// strings to time.Time.
164func StringToTimeHookFunc(layout string) DecodeHookFunc {
165	return func(
166		f reflect.Type,
167		t reflect.Type,
168		data interface{}) (interface{}, error) {
169		if f.Kind() != reflect.String {
170			return data, nil
171		}
172		if t != reflect.TypeOf(time.Time{}) {
173			return data, nil
174		}
175
176		// Convert it by parsing
177		return time.Parse(layout, data.(string))
178	}
179}
180
181// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to
182// the decoder.
183//
184// Note that this is significantly different from the WeaklyTypedInput option
185// of the DecoderConfig.
186func WeaklyTypedHook(
187	f reflect.Kind,
188	t reflect.Kind,
189	data interface{}) (interface{}, error) {
190	dataVal := reflect.ValueOf(data)
191	switch t {
192	case reflect.String:
193		switch f {
194		case reflect.Bool:
195			if dataVal.Bool() {
196				return "1", nil
197			}
198			return "0", nil
199		case reflect.Float32:
200			return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil
201		case reflect.Int:
202			return strconv.FormatInt(dataVal.Int(), 10), nil
203		case reflect.Slice:
204			dataType := dataVal.Type()
205			elemKind := dataType.Elem().Kind()
206			if elemKind == reflect.Uint8 {
207				return string(dataVal.Interface().([]uint8)), nil
208			}
209		case reflect.Uint:
210			return strconv.FormatUint(dataVal.Uint(), 10), nil
211		}
212	}
213
214	return data, nil
215}
216
217func RecursiveStructToMapHookFunc() DecodeHookFunc {
218	return func(f reflect.Value, t reflect.Value) (interface{}, error) {
219		if f.Kind() != reflect.Struct {
220			return f.Interface(), nil
221		}
222
223		var i interface{} = struct{}{}
224		if t.Type() != reflect.TypeOf(&i).Elem() {
225			return f.Interface(), nil
226		}
227
228		m := make(map[string]interface{})
229		t.Set(reflect.ValueOf(m))
230
231		return f.Interface(), nil
232	}
233}
234
235// TextUnmarshallerHookFunc returns a DecodeHookFunc that applies
236// strings to the UnmarshalText function, when the target type
237// implements the encoding.TextUnmarshaler interface
238func TextUnmarshallerHookFunc() DecodeHookFuncType {
239	return func(
240		f reflect.Type,
241		t reflect.Type,
242		data interface{}) (interface{}, error) {
243		if f.Kind() != reflect.String {
244			return data, nil
245		}
246		result := reflect.New(t).Interface()
247		unmarshaller, ok := result.(encoding.TextUnmarshaler)
248		if !ok {
249			return data, nil
250		}
251		if err := unmarshaller.UnmarshalText([]byte(data.(string))); err != nil {
252			return nil, err
253		}
254		return result, nil
255	}
256}
257