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