blob: 1f0abc65ab7e2703badf7a7bebd8cb68ba7a9a11 [file] [log] [blame]
Holger Hildebrandtfa074992020-03-27 15:42:06 +00001package 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}