-
Notifications
You must be signed in to change notification settings - Fork 0
/
tagconfig.go
256 lines (224 loc) · 6.39 KB
/
tagconfig.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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
// MIT Licensed
package tagconfig
// 99% of this work comes from Kelsey Hightower's envconfig (https://github.com/kelseyhightower/envconfig/blob/master/envconfig.go)
// I take no credit for the process bits, all I've changed is adding some interfaces to allow for an implementation that would
// get environment variables, or remote key value store, or config file or or or.
import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"time"
)
// ErrInvalidSpecification indicates that a specification is of the wrong type.
var ErrInvalidSpecification = errors.New("specification must be a struct pointer")
// A ParseError occurs when an environment variable cannot be converted to
// the type required by a struct field during assignment.
type ParseError struct {
FieldName string
TypeName string
Value string
}
// A Decoder is a type that knows how to de-serialize environment variables
// into itself.
type Decoder interface {
Decode(value string) error
}
func (e *ParseError) Error() string {
return fmt.Sprintf("tagconfig.Process: assigning to %[1]s: converting '%[2]s' to type %[3]s", e.FieldName, e.Value, e.TypeName)
}
// TagNameGetter is used for defining a struct tag that contains the value in
// question.
type TagNameGetter interface {
TagName() string
}
// TagValueGetter is an interface to allow for different k,v stores to be passed to the Process func.
// TagName is the name of the tag Process looks for, to query the TagValueGetter
// Get is passed a key to look up and the StructField so it can inspect for other tags it might care about.
type TagValueGetter interface {
TagNameGetter
Get(key string, t reflect.StructField) string
}
// TagValueSetter is used to set a value onto an external source based on the
// tag name.
type TagValueSetter interface {
TagNameGetter
Set(key string, value interface{}, t reflect.StructField) error
}
// Process populates the specified struct based on the TagValueGetter implementation
func Process(v TagValueGetter, spec interface{}) error {
s := reflect.ValueOf(spec)
if s.Kind() != reflect.Ptr {
return ErrInvalidSpecification
}
s = s.Elem()
if s.Kind() != reflect.Struct {
return ErrInvalidSpecification
}
typeOfSpec := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
if !f.CanSet() || typeOfSpec.Field(i).Tag.Get("ignored") == "true" {
continue
}
if typeOfSpec.Field(i).Anonymous && f.Kind() == reflect.Struct {
embeddedPtr := f.Addr().Interface()
if err := Process(v, embeddedPtr); err != nil {
return err
}
f.Set(reflect.ValueOf(embeddedPtr).Elem())
}
// Pull the key from TagValueGetter
key := typeOfSpec.Field(i).Tag.Get(v.TagName())
if key == "" {
continue
}
// Let the TagValueGetter decide how to extract the value and pass
// along the structField so it can inspect potential meta data.
value := v.Get(key, typeOfSpec.Field(i))
def := typeOfSpec.Field(i).Tag.Get("default")
if def != "" && value == "" {
value = def
}
req := typeOfSpec.Field(i).Tag.Get("required")
if value == "" && def == "" {
if req == "true" {
return fmt.Errorf("required key %s missing value", key)
}
continue
}
err := processField(value, f)
if err != nil {
return &ParseError{
FieldName: key,
TypeName: f.Type().String(),
Value: value,
}
}
}
return nil
}
// MustProcess is the same as Process but panics if an error occurs
func MustProcess(v TagValueGetter, spec interface{}) {
if err := Process(v, spec); err != nil {
panic(err)
}
}
func processField(value string, field reflect.Value) error {
typ := field.Type()
decoder := decoderFrom(field)
if decoder != nil {
return decoder.Decode(value)
}
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
if field.IsNil() {
field.Set(reflect.New(typ))
}
field = field.Elem()
}
switch typ.Kind() {
case reflect.String:
field.SetString(value)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
var (
val int64
err error
)
if field.Kind() == reflect.Int64 && typ.PkgPath() == "time" && typ.Name() == "Duration" {
var d time.Duration
d, err = time.ParseDuration(value)
val = int64(d)
} else {
val, err = strconv.ParseInt(value, 0, typ.Bits())
}
if err != nil {
return err
}
field.SetInt(val)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
val, err := strconv.ParseUint(value, 0, typ.Bits())
if err != nil {
return err
}
field.SetUint(val)
case reflect.Bool:
val, err := strconv.ParseBool(value)
if err != nil {
return err
}
field.SetBool(val)
case reflect.Float32, reflect.Float64:
val, err := strconv.ParseFloat(value, typ.Bits())
if err != nil {
return err
}
field.SetFloat(val)
case reflect.Slice:
vals := strings.Split(value, ",")
sl := reflect.MakeSlice(typ, len(vals), len(vals))
for i, val := range vals {
err := processField(val, sl.Index(i))
if err != nil {
return err
}
}
field.Set(sl)
}
return nil
}
func decoderFrom(field reflect.Value) Decoder {
if field.CanInterface() {
dec, ok := field.Interface().(Decoder)
if ok {
return dec
}
}
// also check if pointer-to-type implements Decoder,
// and we can get a pointer to our field
if field.CanAddr() {
field = field.Addr()
dec, ok := field.Interface().(Decoder)
if ok {
return dec
}
}
return nil
}
// PopulateExternalSource is used to fill an external source when a struct
// contains values. An external source can include an in memory store,
// environment variables, a properties store, and so forth. Therefore, if a
// struct contains values, but you want to propagate said values to another
// location, this would allow you to do so based off of how the TagValueSetter
// has been implemented.
func PopulateExternalSource(v TagValueSetter, spec interface{}) error {
s := reflect.ValueOf(spec)
if s.Kind() != reflect.Ptr {
return ErrInvalidSpecification
}
s = s.Elem()
if s.Kind() != reflect.Struct {
return ErrInvalidSpecification
}
typeOfSpec := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
ft := typeOfSpec.Field(i)
if typeOfSpec.Field(i).Anonymous && f.Kind() == reflect.Struct {
embeddedPtr := f.Addr().Interface()
if err := PopulateExternalSource(v, embeddedPtr); err != nil {
return err
}
} else {
t := ft.Tag.Get(v.TagName())
if t != "" {
err := v.Set(t, f.Interface(), ft)
if err != nil {
return err
}
}
}
}
return nil
}