|
1 // Copyright 2018 Frank Schroeder. All rights reserved. |
|
2 // Use of this source code is governed by a BSD-style |
|
3 // license that can be found in the LICENSE file. |
|
4 |
|
5 package properties |
|
6 |
|
7 import ( |
|
8 "fmt" |
|
9 "reflect" |
|
10 "strconv" |
|
11 "strings" |
|
12 "time" |
|
13 ) |
|
14 |
|
15 // Decode assigns property values to exported fields of a struct. |
|
16 // |
|
17 // Decode traverses v recursively and returns an error if a value cannot be |
|
18 // converted to the field type or a required value is missing for a field. |
|
19 // |
|
20 // The following type dependent decodings are used: |
|
21 // |
|
22 // String, boolean, numeric fields have the value of the property key assigned. |
|
23 // The property key name is the name of the field. A different key and a default |
|
24 // value can be set in the field's tag. Fields without default value are |
|
25 // required. If the value cannot be converted to the field type an error is |
|
26 // returned. |
|
27 // |
|
28 // time.Duration fields have the result of time.ParseDuration() assigned. |
|
29 // |
|
30 // time.Time fields have the vaule of time.Parse() assigned. The default layout |
|
31 // is time.RFC3339 but can be set in the field's tag. |
|
32 // |
|
33 // Arrays and slices of string, boolean, numeric, time.Duration and time.Time |
|
34 // fields have the value interpreted as a comma separated list of values. The |
|
35 // individual values are trimmed of whitespace and empty values are ignored. A |
|
36 // default value can be provided as a semicolon separated list in the field's |
|
37 // tag. |
|
38 // |
|
39 // Struct fields are decoded recursively using the field name plus "." as |
|
40 // prefix. The prefix (without dot) can be overridden in the field's tag. |
|
41 // Default values are not supported in the field's tag. Specify them on the |
|
42 // fields of the inner struct instead. |
|
43 // |
|
44 // Map fields must have a key of type string and are decoded recursively by |
|
45 // using the field's name plus ".' as prefix and the next element of the key |
|
46 // name as map key. The prefix (without dot) can be overridden in the field's |
|
47 // tag. Default values are not supported. |
|
48 // |
|
49 // Examples: |
|
50 // |
|
51 // // Field is ignored. |
|
52 // Field int `properties:"-"` |
|
53 // |
|
54 // // Field is assigned value of 'Field'. |
|
55 // Field int |
|
56 // |
|
57 // // Field is assigned value of 'myName'. |
|
58 // Field int `properties:"myName"` |
|
59 // |
|
60 // // Field is assigned value of key 'myName' and has a default |
|
61 // // value 15 if the key does not exist. |
|
62 // Field int `properties:"myName,default=15"` |
|
63 // |
|
64 // // Field is assigned value of key 'Field' and has a default |
|
65 // // value 15 if the key does not exist. |
|
66 // Field int `properties:",default=15"` |
|
67 // |
|
68 // // Field is assigned value of key 'date' and the date |
|
69 // // is in format 2006-01-02 |
|
70 // Field time.Time `properties:"date,layout=2006-01-02"` |
|
71 // |
|
72 // // Field is assigned the non-empty and whitespace trimmed |
|
73 // // values of key 'Field' split by commas. |
|
74 // Field []string |
|
75 // |
|
76 // // Field is assigned the non-empty and whitespace trimmed |
|
77 // // values of key 'Field' split by commas and has a default |
|
78 // // value ["a", "b", "c"] if the key does not exist. |
|
79 // Field []string `properties:",default=a;b;c"` |
|
80 // |
|
81 // // Field is decoded recursively with "Field." as key prefix. |
|
82 // Field SomeStruct |
|
83 // |
|
84 // // Field is decoded recursively with "myName." as key prefix. |
|
85 // Field SomeStruct `properties:"myName"` |
|
86 // |
|
87 // // Field is decoded recursively with "Field." as key prefix |
|
88 // // and the next dotted element of the key as map key. |
|
89 // Field map[string]string |
|
90 // |
|
91 // // Field is decoded recursively with "myName." as key prefix |
|
92 // // and the next dotted element of the key as map key. |
|
93 // Field map[string]string `properties:"myName"` |
|
94 func (p *Properties) Decode(x interface{}) error { |
|
95 t, v := reflect.TypeOf(x), reflect.ValueOf(x) |
|
96 if t.Kind() != reflect.Ptr || v.Elem().Type().Kind() != reflect.Struct { |
|
97 return fmt.Errorf("not a pointer to struct: %s", t) |
|
98 } |
|
99 if err := dec(p, "", nil, nil, v); err != nil { |
|
100 return err |
|
101 } |
|
102 return nil |
|
103 } |
|
104 |
|
105 func dec(p *Properties, key string, def *string, opts map[string]string, v reflect.Value) error { |
|
106 t := v.Type() |
|
107 |
|
108 // value returns the property value for key or the default if provided. |
|
109 value := func() (string, error) { |
|
110 if val, ok := p.Get(key); ok { |
|
111 return val, nil |
|
112 } |
|
113 if def != nil { |
|
114 return *def, nil |
|
115 } |
|
116 return "", fmt.Errorf("missing required key %s", key) |
|
117 } |
|
118 |
|
119 // conv converts a string to a value of the given type. |
|
120 conv := func(s string, t reflect.Type) (val reflect.Value, err error) { |
|
121 var v interface{} |
|
122 |
|
123 switch { |
|
124 case isDuration(t): |
|
125 v, err = time.ParseDuration(s) |
|
126 |
|
127 case isTime(t): |
|
128 layout := opts["layout"] |
|
129 if layout == "" { |
|
130 layout = time.RFC3339 |
|
131 } |
|
132 v, err = time.Parse(layout, s) |
|
133 |
|
134 case isBool(t): |
|
135 v, err = boolVal(s), nil |
|
136 |
|
137 case isString(t): |
|
138 v, err = s, nil |
|
139 |
|
140 case isFloat(t): |
|
141 v, err = strconv.ParseFloat(s, 64) |
|
142 |
|
143 case isInt(t): |
|
144 v, err = strconv.ParseInt(s, 10, 64) |
|
145 |
|
146 case isUint(t): |
|
147 v, err = strconv.ParseUint(s, 10, 64) |
|
148 |
|
149 default: |
|
150 return reflect.Zero(t), fmt.Errorf("unsupported type %s", t) |
|
151 } |
|
152 if err != nil { |
|
153 return reflect.Zero(t), err |
|
154 } |
|
155 return reflect.ValueOf(v).Convert(t), nil |
|
156 } |
|
157 |
|
158 // keydef returns the property key and the default value based on the |
|
159 // name of the struct field and the options in the tag. |
|
160 keydef := func(f reflect.StructField) (string, *string, map[string]string) { |
|
161 _key, _opts := parseTag(f.Tag.Get("properties")) |
|
162 |
|
163 var _def *string |
|
164 if d, ok := _opts["default"]; ok { |
|
165 _def = &d |
|
166 } |
|
167 if _key != "" { |
|
168 return _key, _def, _opts |
|
169 } |
|
170 return f.Name, _def, _opts |
|
171 } |
|
172 |
|
173 switch { |
|
174 case isDuration(t) || isTime(t) || isBool(t) || isString(t) || isFloat(t) || isInt(t) || isUint(t): |
|
175 s, err := value() |
|
176 if err != nil { |
|
177 return err |
|
178 } |
|
179 val, err := conv(s, t) |
|
180 if err != nil { |
|
181 return err |
|
182 } |
|
183 v.Set(val) |
|
184 |
|
185 case isPtr(t): |
|
186 return dec(p, key, def, opts, v.Elem()) |
|
187 |
|
188 case isStruct(t): |
|
189 for i := 0; i < v.NumField(); i++ { |
|
190 fv := v.Field(i) |
|
191 fk, def, opts := keydef(t.Field(i)) |
|
192 if !fv.CanSet() { |
|
193 return fmt.Errorf("cannot set %s", t.Field(i).Name) |
|
194 } |
|
195 if fk == "-" { |
|
196 continue |
|
197 } |
|
198 if key != "" { |
|
199 fk = key + "." + fk |
|
200 } |
|
201 if err := dec(p, fk, def, opts, fv); err != nil { |
|
202 return err |
|
203 } |
|
204 } |
|
205 return nil |
|
206 |
|
207 case isArray(t): |
|
208 val, err := value() |
|
209 if err != nil { |
|
210 return err |
|
211 } |
|
212 vals := split(val, ";") |
|
213 a := reflect.MakeSlice(t, 0, len(vals)) |
|
214 for _, s := range vals { |
|
215 val, err := conv(s, t.Elem()) |
|
216 if err != nil { |
|
217 return err |
|
218 } |
|
219 a = reflect.Append(a, val) |
|
220 } |
|
221 v.Set(a) |
|
222 |
|
223 case isMap(t): |
|
224 valT := t.Elem() |
|
225 m := reflect.MakeMap(t) |
|
226 for postfix := range p.FilterStripPrefix(key + ".").m { |
|
227 pp := strings.SplitN(postfix, ".", 2) |
|
228 mk, mv := pp[0], reflect.New(valT) |
|
229 if err := dec(p, key+"."+mk, nil, nil, mv); err != nil { |
|
230 return err |
|
231 } |
|
232 m.SetMapIndex(reflect.ValueOf(mk), mv.Elem()) |
|
233 } |
|
234 v.Set(m) |
|
235 |
|
236 default: |
|
237 return fmt.Errorf("unsupported type %s", t) |
|
238 } |
|
239 return nil |
|
240 } |
|
241 |
|
242 // split splits a string on sep, trims whitespace of elements |
|
243 // and omits empty elements |
|
244 func split(s string, sep string) []string { |
|
245 var a []string |
|
246 for _, v := range strings.Split(s, sep) { |
|
247 if v = strings.TrimSpace(v); v != "" { |
|
248 a = append(a, v) |
|
249 } |
|
250 } |
|
251 return a |
|
252 } |
|
253 |
|
254 // parseTag parses a "key,k=v,k=v,..." |
|
255 func parseTag(tag string) (key string, opts map[string]string) { |
|
256 opts = map[string]string{} |
|
257 for i, s := range strings.Split(tag, ",") { |
|
258 if i == 0 { |
|
259 key = s |
|
260 continue |
|
261 } |
|
262 |
|
263 pp := strings.SplitN(s, "=", 2) |
|
264 if len(pp) == 1 { |
|
265 opts[pp[0]] = "" |
|
266 } else { |
|
267 opts[pp[0]] = pp[1] |
|
268 } |
|
269 } |
|
270 return key, opts |
|
271 } |
|
272 |
|
273 func isArray(t reflect.Type) bool { return t.Kind() == reflect.Array || t.Kind() == reflect.Slice } |
|
274 func isBool(t reflect.Type) bool { return t.Kind() == reflect.Bool } |
|
275 func isDuration(t reflect.Type) bool { return t == reflect.TypeOf(time.Second) } |
|
276 func isMap(t reflect.Type) bool { return t.Kind() == reflect.Map } |
|
277 func isPtr(t reflect.Type) bool { return t.Kind() == reflect.Ptr } |
|
278 func isString(t reflect.Type) bool { return t.Kind() == reflect.String } |
|
279 func isStruct(t reflect.Type) bool { return t.Kind() == reflect.Struct } |
|
280 func isTime(t reflect.Type) bool { return t == reflect.TypeOf(time.Time{}) } |
|
281 func isFloat(t reflect.Type) bool { |
|
282 return t.Kind() == reflect.Float32 || t.Kind() == reflect.Float64 |
|
283 } |
|
284 func isInt(t reflect.Type) bool { |
|
285 return t.Kind() == reflect.Int || t.Kind() == reflect.Int8 || t.Kind() == reflect.Int16 || t.Kind() == reflect.Int32 || t.Kind() == reflect.Int64 |
|
286 } |
|
287 func isUint(t reflect.Type) bool { |
|
288 return t.Kind() == reflect.Uint || t.Kind() == reflect.Uint8 || t.Kind() == reflect.Uint16 || t.Kind() == reflect.Uint32 || t.Kind() == reflect.Uint64 |
|
289 } |