]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package mapstructure |
2 | ||
3 | import ( | |
4 | "errors" | |
107c1cdb ND |
5 | "fmt" |
6 | "net" | |
bae9f6d2 JC |
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. | |
15 | func 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. | |
39 | func DecodeHookExec( | |
40 | raw DecodeHookFunc, | |
41 | from reflect.Type, to reflect.Type, | |
42 | data interface{}) (interface{}, error) { | |
bae9f6d2 JC |
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. | |
58 | func 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. | |
83 | func 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. | |
103 | func 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 | ||
107c1cdb ND |
120 | // StringToIPHookFunc returns a DecodeHookFunc that converts |
121 | // strings to net.IP | |
122 | func 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 | |
146 | func 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. | |
166 | func 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. | |
bae9f6d2 JC |
188 | func 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 | |
bae9f6d2 | 199 | } |
107c1cdb | 200 | return "0", nil |
bae9f6d2 JC |
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 | } |