Codebase list golang-github-mitchellh-mapstructure / fcb781b
Merge pull request #183 from camdencheek/master Add a value hook func Mitchell Hashimoto authored 3 years ago GitHub committed 3 years ago
3 changed file(s) with 265 addition(s) and 150 deletion(s). Raw diff Collapse all Expand all
1515 // Create variables here so we can reference them with the reflect pkg
1616 var f1 DecodeHookFuncType
1717 var f2 DecodeHookFuncKind
18 var f3 DecodeHookFuncValue
1819
1920 // Fill in the variables into this interface and the rest is done
2021 // automatically using the reflect package.
21 potential := []interface{}{f1, f2}
22 potential := []interface{}{f1, f2, f3}
2223
2324 v := reflect.ValueOf(h)
2425 vt := v.Type()
3738 // that took reflect.Kind instead of reflect.Type.
3839 func DecodeHookExec(
3940 raw DecodeHookFunc,
40 from reflect.Type, to reflect.Type,
41 data interface{}) (interface{}, error) {
41 from reflect.Value, to reflect.Value) (interface{}, error) {
42
4243 switch f := typedDecodeHook(raw).(type) {
4344 case DecodeHookFuncType:
44 return f(from, to, data)
45 return f(from.Type(), to.Type(), from.Interface())
4546 case DecodeHookFuncKind:
46 return f(from.Kind(), to.Kind(), data)
47 return f(from.Kind(), to.Kind(), from.Interface())
48 case DecodeHookFuncValue:
49 return f(from, to)
4750 default:
4851 return nil, errors.New("invalid decode hook signature")
4952 }
5558 // The composed funcs are called in order, with the result of the
5659 // previous transformation.
5760 func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
58 return func(
59 f reflect.Type,
60 t reflect.Type,
61 data interface{}) (interface{}, error) {
61 return func(f reflect.Value, t reflect.Value) (interface{}, error) {
6262 var err error
63 var data interface{}
64 newFrom := f
6365 for _, f1 := range fs {
64 data, err = DecodeHookExec(f1, f, t, data)
66 data, err = DecodeHookExec(f1, newFrom, t)
6567 if err != nil {
6668 return nil, err
6769 }
68
69 // Modify the from kind to be correct with the new data
70 f = nil
71 if val := reflect.ValueOf(data); val.IsValid() {
72 f = val.Type()
73 }
70 newFrom = reflect.ValueOf(data)
7471 }
7572
7673 return data, nil
214211
215212 return data, nil
216213 }
214
215 func RecursiveStructToMapHookFunc() DecodeHookFunc {
216 return func(f reflect.Value, t reflect.Value) (interface{}, error) {
217 if f.Kind() != reflect.Struct {
218 return f.Interface(), nil
219 }
220
221 var i interface{} = struct{}{}
222 if t.Type() != reflect.TypeOf(&i).Elem() {
223 return f.Interface(), nil
224 }
225
226 m := make(map[string]interface{})
227 t.Set(reflect.ValueOf(m))
228
229 return f.Interface(), nil
230 }
231 }
2525 f := ComposeDecodeHookFunc(f1, f2)
2626
2727 result, err := DecodeHookExec(
28 f, reflect.TypeOf(""), reflect.TypeOf([]byte("")), "")
28 f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
2929 if err != nil {
3030 t.Fatalf("bad: %s", err)
3131 }
4646 f := ComposeDecodeHookFunc(f1, f2)
4747
4848 _, err := DecodeHookExec(
49 f, reflect.TypeOf(""), reflect.TypeOf([]byte("")), 42)
49 f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
5050 if err.Error() != "foo" {
5151 t.Fatalf("bad: %s", err)
5252 }
7373 f := ComposeDecodeHookFunc(f1, f2)
7474
7575 _, err := DecodeHookExec(
76 f, reflect.TypeOf(""), reflect.TypeOf([]byte("")), "")
76 f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
7777 if err != nil {
7878 t.Fatalf("bad: %s", err)
7979 }
8585 func TestStringToSliceHookFunc(t *testing.T) {
8686 f := StringToSliceHookFunc(",")
8787
88 strType := reflect.TypeOf("")
89 sliceType := reflect.TypeOf([]byte(""))
90 cases := []struct {
91 f, t reflect.Type
92 data interface{}
93 result interface{}
94 err bool
95 }{
96 {sliceType, sliceType, 42, 42, false},
97 {strType, strType, 42, 42, false},
98 {
99 strType,
100 sliceType,
101 "foo,bar,baz",
88 strValue := reflect.ValueOf("42")
89 sliceValue := reflect.ValueOf([]byte("42"))
90 cases := []struct {
91 f, t reflect.Value
92 result interface{}
93 err bool
94 }{
95 {sliceValue, sliceValue, []byte("42"), false},
96 {strValue, strValue, "42", false},
97 {
98 reflect.ValueOf("foo,bar,baz"),
99 sliceValue,
102100 []string{"foo", "bar", "baz"},
103101 false,
104102 },
105103 {
106 strType,
107 sliceType,
108 "",
104 reflect.ValueOf(""),
105 sliceValue,
109106 []string{},
110107 false,
111108 },
112109 }
113110
114111 for i, tc := range cases {
115 actual, err := DecodeHookExec(f, tc.f, tc.t, tc.data)
112 actual, err := DecodeHookExec(f, tc.f, tc.t)
116113 if tc.err != (err != nil) {
117114 t.Fatalf("case %d: expected err %#v", i, tc.err)
118115 }
127124 func TestStringToTimeDurationHookFunc(t *testing.T) {
128125 f := StringToTimeDurationHookFunc()
129126
130 strType := reflect.TypeOf("")
131 timeType := reflect.TypeOf(time.Duration(5))
132 cases := []struct {
133 f, t reflect.Type
134 data interface{}
135 result interface{}
136 err bool
137 }{
138 {strType, timeType, "5s", 5 * time.Second, false},
139 {strType, timeType, "5", time.Duration(0), true},
140 {strType, strType, "5", "5", false},
141 }
142
143 for i, tc := range cases {
144 actual, err := DecodeHookExec(f, tc.f, tc.t, tc.data)
127 timeValue := reflect.ValueOf(time.Duration(5))
128 strValue := reflect.ValueOf("")
129 cases := []struct {
130 f, t reflect.Value
131 result interface{}
132 err bool
133 }{
134 {reflect.ValueOf("5s"), timeValue, 5 * time.Second, false},
135 {reflect.ValueOf("5"), timeValue, time.Duration(0), true},
136 {reflect.ValueOf("5"), strValue, "5", false},
137 }
138
139 for i, tc := range cases {
140 actual, err := DecodeHookExec(f, tc.f, tc.t)
145141 if tc.err != (err != nil) {
146142 t.Fatalf("case %d: expected err %#v", i, tc.err)
147143 }
154150 }
155151
156152 func TestStringToTimeHookFunc(t *testing.T) {
157 strType := reflect.TypeOf("")
158 timeType := reflect.TypeOf(time.Time{})
159 cases := []struct {
160 f, t reflect.Type
153 strValue := reflect.ValueOf("5")
154 timeValue := reflect.ValueOf(time.Time{})
155 cases := []struct {
156 f, t reflect.Value
161157 layout string
162 data interface{}
163 result interface{}
164 err bool
165 }{
166 {strType, timeType, time.RFC3339, "2006-01-02T15:04:05Z",
158 result interface{}
159 err bool
160 }{
161 {reflect.ValueOf("2006-01-02T15:04:05Z"), timeValue, time.RFC3339,
167162 time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC), false},
168 {strType, timeType, time.RFC3339, "5", time.Time{}, true},
169 {strType, strType, time.RFC3339, "5", "5", false},
163 {strValue, timeValue, time.RFC3339, time.Time{}, true},
164 {strValue, strValue, time.RFC3339, "5", false},
170165 }
171166
172167 for i, tc := range cases {
173168 f := StringToTimeHookFunc(tc.layout)
174 actual, err := DecodeHookExec(f, tc.f, tc.t, tc.data)
169 actual, err := DecodeHookExec(f, tc.f, tc.t)
175170 if tc.err != (err != nil) {
176171 t.Fatalf("case %d: expected err %#v", i, tc.err)
177172 }
184179 }
185180
186181 func TestStringToIPHookFunc(t *testing.T) {
187 strType := reflect.TypeOf("")
188 ipType := reflect.TypeOf(net.IP{})
189 cases := []struct {
190 f, t reflect.Type
191 data interface{}
192 result interface{}
193 err bool
194 }{
195 {strType, ipType, "1.2.3.4",
182 strValue := reflect.ValueOf("5")
183 ipValue := reflect.ValueOf(net.IP{})
184 cases := []struct {
185 f, t reflect.Value
186 result interface{}
187 err bool
188 }{
189 {reflect.ValueOf("1.2.3.4"), ipValue,
196190 net.IPv4(0x01, 0x02, 0x03, 0x04), false},
197 {strType, ipType, "5", net.IP{}, true},
198 {strType, strType, "5", "5", false},
191 {strValue, ipValue, net.IP{}, true},
192 {strValue, strValue, "5", false},
199193 }
200194
201195 for i, tc := range cases {
202196 f := StringToIPHookFunc()
203 actual, err := DecodeHookExec(f, tc.f, tc.t, tc.data)
197 actual, err := DecodeHookExec(f, tc.f, tc.t)
204198 if tc.err != (err != nil) {
205199 t.Fatalf("case %d: expected err %#v", i, tc.err)
206200 }
213207 }
214208
215209 func TestStringToIPNetHookFunc(t *testing.T) {
216 strType := reflect.TypeOf("")
217 ipNetType := reflect.TypeOf(net.IPNet{})
210 strValue := reflect.ValueOf("5")
211 ipNetValue := reflect.ValueOf(net.IPNet{})
218212 var nilNet *net.IPNet = nil
219213
220214 cases := []struct {
221 f, t reflect.Type
222 data interface{}
223 result interface{}
224 err bool
225 }{
226 {strType, ipNetType, "1.2.3.4/24",
215 f, t reflect.Value
216 result interface{}
217 err bool
218 }{
219 {reflect.ValueOf("1.2.3.4/24"), ipNetValue,
227220 &net.IPNet{
228221 IP: net.IP{0x01, 0x02, 0x03, 0x00},
229222 Mask: net.IPv4Mask(0xff, 0xff, 0xff, 0x00),
230223 }, false},
231 {strType, ipNetType, "5", nilNet, true},
232 {strType, strType, "5", "5", false},
224 {strValue, ipNetValue, nilNet, true},
225 {strValue, strValue, "5", false},
233226 }
234227
235228 for i, tc := range cases {
236229 f := StringToIPNetHookFunc()
237 actual, err := DecodeHookExec(f, tc.f, tc.t, tc.data)
230 actual, err := DecodeHookExec(f, tc.f, tc.t)
238231 if tc.err != (err != nil) {
239232 t.Fatalf("case %d: expected err %#v", i, tc.err)
240233 }
249242 func TestWeaklyTypedHook(t *testing.T) {
250243 var f DecodeHookFunc = WeaklyTypedHook
251244
252 boolType := reflect.TypeOf(true)
253 strType := reflect.TypeOf("")
254 sliceType := reflect.TypeOf([]byte(""))
255 cases := []struct {
256 f, t reflect.Type
257 data interface{}
245 strValue := reflect.ValueOf("")
246 cases := []struct {
247 f, t reflect.Value
258248 result interface{}
259249 err bool
260250 }{
261251 // TO STRING
262252 {
263 boolType,
264 strType,
265 false,
253 reflect.ValueOf(false),
254 strValue,
266255 "0",
267256 false,
268257 },
269258
270259 {
271 boolType,
272 strType,
260 reflect.ValueOf(true),
261 strValue,
262 "1",
263 false,
264 },
265
266 {
267 reflect.ValueOf(float32(7)),
268 strValue,
269 "7",
270 false,
271 },
272
273 {
274 reflect.ValueOf(int(7)),
275 strValue,
276 "7",
277 false,
278 },
279
280 {
281 reflect.ValueOf([]uint8("foo")),
282 strValue,
283 "foo",
284 false,
285 },
286
287 {
288 reflect.ValueOf(uint(7)),
289 strValue,
290 "7",
291 false,
292 },
293 }
294
295 for i, tc := range cases {
296 actual, err := DecodeHookExec(f, tc.f, tc.t)
297 if tc.err != (err != nil) {
298 t.Fatalf("case %d: expected err %#v", i, tc.err)
299 }
300 if !reflect.DeepEqual(actual, tc.result) {
301 t.Fatalf(
302 "case %d: expected %#v, got %#v",
303 i, tc.result, actual)
304 }
305 }
306 }
307
308 func TestStructToMapHookFuncTabled(t *testing.T) {
309 var f DecodeHookFunc = RecursiveStructToMapHookFunc()
310
311 type b struct {
312 TestKey string
313 }
314
315 type a struct {
316 Sub b
317 }
318
319 testStruct := a{
320 Sub: b{
321 TestKey: "testval",
322 },
323 }
324
325 testMap := map[string]interface{}{
326 "Sub": map[string]interface{}{
327 "TestKey": "testval",
328 },
329 }
330
331 cases := []struct {
332 name string
333 receiver interface{}
334 input interface{}
335 expected interface{}
336 err bool
337 }{
338 {
339 "map receiver",
340 func() interface{} {
341 var res map[string]interface{}
342 return &res
343 }(),
344 testStruct,
345 &testMap,
346 false,
347 },
348 {
349 "interface receiver",
350 func() interface{} {
351 var res interface{}
352 return &res
353 }(),
354 testStruct,
355 func() interface{} {
356 var exp interface{} = testMap
357 return &exp
358 }(),
359 false,
360 },
361 {
362 "slice receiver errors",
363 func() interface{} {
364 var res []string
365 return &res
366 }(),
367 testStruct,
368 new([]string),
273369 true,
274 "1",
275 false,
276 },
277
278 {
279 reflect.TypeOf(float32(1)),
280 strType,
281 float32(7),
282 "7",
283 false,
284 },
285
286 {
287 reflect.TypeOf(int(1)),
288 strType,
289 int(7),
290 "7",
291 false,
292 },
293
294 {
295 sliceType,
296 strType,
297 []uint8("foo"),
298 "foo",
299 false,
300 },
301
302 {
303 reflect.TypeOf(uint(1)),
304 strType,
305 uint(7),
306 "7",
307 false,
308 },
309 }
310
311 for i, tc := range cases {
312 actual, err := DecodeHookExec(f, tc.f, tc.t, tc.data)
313 if tc.err != (err != nil) {
314 t.Fatalf("case %d: expected err %#v", i, tc.err)
315 }
316 if !reflect.DeepEqual(actual, tc.result) {
317 t.Fatalf(
318 "case %d: expected %#v, got %#v",
319 i, tc.result, actual)
320 }
321 }
322 }
370 },
371 {
372 "slice to slice - no change",
373 func() interface{} {
374 var res []string
375 return &res
376 }(),
377 []string{"a", "b"},
378 &[]string{"a", "b"},
379 false,
380 },
381 {
382 "string to string - no change",
383 func() interface{} {
384 var res string
385 return &res
386 }(),
387 "test",
388 func() *string {
389 s := "test"
390 return &s
391 }(),
392 false,
393 },
394 }
395
396 for _, tc := range cases {
397 t.Run(tc.name, func(t *testing.T) {
398 cfg := &DecoderConfig{
399 DecodeHook: f,
400 Result: tc.receiver,
401 }
402
403 d, err := NewDecoder(cfg)
404 if err != nil {
405 t.Fatalf("unexpected err %#v", err)
406 }
407
408 err = d.Decode(tc.input)
409 if tc.err != (err != nil) {
410 t.Fatalf("expected err %#v", err)
411 }
412
413 if !reflect.DeepEqual(tc.expected, tc.receiver) {
414 t.Fatalf("expected %#v, got %#v",
415 tc.expected, tc.receiver)
416 }
417 })
418
419 }
420 }
178178 // DecodeHookFuncKind is a DecodeHookFunc which knows only the Kinds of the
179179 // source and target types.
180180 type DecodeHookFuncKind func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error)
181
182 // DecodeHookFuncRaw is a DecodeHookFunc which has complete access to both the source and target
183 // values.
184 type DecodeHookFuncValue func(from reflect.Value, to reflect.Value) (interface{}, error)
181185
182186 // DecoderConfig is the configuration that is used to create a new decoder
183187 // and allows customization of various aspects of decoding.
408412 if d.config.DecodeHook != nil {
409413 // We have a DecodeHook, so let's pre-process the input.
410414 var err error
411 input, err = DecodeHookExec(
412 d.config.DecodeHook,
413 inputVal.Type(), outVal.Type(), input)
415 input, err = DecodeHookExec(d.config.DecodeHook, inputVal, outVal)
414416 if err != nil {
415417 return fmt.Errorf("error decoding '%s': %s", name, err)
416418 }