/
decode.go
122 lines (109 loc) · 3.27 KB
/
decode.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package cfg
import (
"bytes"
"errors"
"fmt"
"reflect"
)
// tagKey is used as the key for struct field tags
const tagKey = "cfg"
// Unmarshal parses the config data and stores the result in the
// value pointed to by v. v must be a pointer to a struct.
//
// Unmarshal matches incoming keys to either the struct field name or its
// tag, preferring an exact match but also accepting a case-insensitive match.
// Only exported fields can be populated. The tag value "-" is used to skip
// a field.
//
// If the type indicated in the struct field does not match the type in the
// config the field is skipped. Eg. the field type is int but contains a non
// numerical string value in the config data.
func Unmarshal(data []byte, v interface{}) error {
// Parse the config
buf := bytes.NewBuffer(data)
c, err := NewConfigFromReader(buf)
if err != nil {
return fmt.Errorf("cfg: error parsing data %s", err)
}
return UnmarshalFromConfig(c, v)
}
// UnmarshalFromConfig strores the data in config in the value pointed to by v.
// v must be a pointer to a struct.
//
// UnmarshalFromConfig matches incoming keys to either the struct field name
// or its tag, preferring an exact match but also accepting
// a case-insensitive match.
// Only exported fields can be populated. The tag value "-" is used to skip
// a field.
//
// If the type indicated in the struct field does not match the type in the
// config the field is skipped. Eg. the field type is int but contains a non
// numerical string value in the config data.
func UnmarshalFromConfig(c *Config, v interface{}) error {
// Check that the type v we will populate is a struct
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr || rv.Elem().Kind() != reflect.Struct {
return errors.New("cfg: interface must be a pointer to struct")
}
// Dereference the pointer if it is one
rv = rv.Elem()
// Loop through all fields of the struct
for i := 0; i < rv.NumField(); i++ {
fv := rv.Field(i) // Save the Value of the field
sf := rv.Type().Field(i) // Save the StructField of the field
// Check if the field should be skipped
if sf.PkgPath != "" { // unexported
continue
}
tag := sf.Tag.Get(tagKey)
if tag == "-" {
continue
}
// Loop through all keys and match them against the field
// set the value if it matches.
for key := range c.values {
// Check so the tag, or the name case insensitive matches, if not
// go on to the next key
if key != tag && bytes.EqualFold([]byte(key), []byte(sf.Name)) == false {
continue
}
err := setValue(&fv, c, key)
if err != nil {
return fmt.Errorf("cfg: error setting field value: %s", err)
}
}
}
return nil
}
// setValue updates the field value in fv to the data extracted from config
// with key.
func setValue(fv *reflect.Value, c *Config, key string) error {
// Update the value with the correct type
switch fv.Kind() {
case reflect.Int:
val, err := c.GetInt(key)
if err != nil {
return err
}
fv.SetInt(int64(val))
case reflect.Float64:
val, err := c.GetFloat(key)
if err != nil {
return err
}
fv.SetFloat(val)
case reflect.Bool:
val, err := c.GetBool(key)
if err != nil {
return err
}
fv.SetBool(val)
case reflect.String:
val, err := c.GetString(key)
if err != nil {
return err
}
fv.SetString(val)
}
return nil
}