blob: 3ad5e7d4b99831999a7c7b5091a46e23af7e1145 [file] [log] [blame]
Jonathan Hartf86817b2018-08-17 10:35:54 -07001// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
2// Use of this source code is governed by the MIT License that can be found in
3// the LICENSE file.
4
5package envconfig
6
7import (
8 "encoding"
9 "errors"
10 "fmt"
11 "reflect"
12 "regexp"
13 "strconv"
14 "strings"
15 "time"
16)
17
18// ErrInvalidSpecification indicates that a specification is of the wrong type.
19var ErrInvalidSpecification = errors.New("specification must be a struct pointer")
20
21// A ParseError occurs when an environment variable cannot be converted to
22// the type required by a struct field during assignment.
23type ParseError struct {
24 KeyName string
25 FieldName string
26 TypeName string
27 Value string
28 Err error
29}
30
31// Decoder has the same semantics as Setter, but takes higher precedence.
32// It is provided for historical compatibility.
33type Decoder interface {
34 Decode(value string) error
35}
36
37// Setter is implemented by types can self-deserialize values.
38// Any type that implements flag.Value also implements Setter.
39type Setter interface {
40 Set(value string) error
41}
42
43func (e *ParseError) Error() string {
44 return fmt.Sprintf("envconfig.Process: assigning %[1]s to %[2]s: converting '%[3]s' to type %[4]s. details: %[5]s", e.KeyName, e.FieldName, e.Value, e.TypeName, e.Err)
45}
46
47// varInfo maintains information about the configuration variable
48type varInfo struct {
49 Name string
50 Alt string
51 Key string
52 Field reflect.Value
53 Tags reflect.StructTag
54}
55
56// GatherInfo gathers information about the specified struct
57func gatherInfo(prefix string, spec interface{}) ([]varInfo, error) {
58 expr := regexp.MustCompile("([^A-Z]+|[A-Z][^A-Z]+|[A-Z]+)")
59 s := reflect.ValueOf(spec)
60
61 if s.Kind() != reflect.Ptr {
62 return nil, ErrInvalidSpecification
63 }
64 s = s.Elem()
65 if s.Kind() != reflect.Struct {
66 return nil, ErrInvalidSpecification
67 }
68 typeOfSpec := s.Type()
69
70 // over allocate an info array, we will extend if needed later
71 infos := make([]varInfo, 0, s.NumField())
72 for i := 0; i < s.NumField(); i++ {
73 f := s.Field(i)
74 ftype := typeOfSpec.Field(i)
75 if !f.CanSet() || ftype.Tag.Get("ignored") == "true" {
76 continue
77 }
78
79 for f.Kind() == reflect.Ptr {
80 if f.IsNil() {
81 if f.Type().Elem().Kind() != reflect.Struct {
82 // nil pointer to a non-struct: leave it alone
83 break
84 }
85 // nil pointer to struct: create a zero instance
86 f.Set(reflect.New(f.Type().Elem()))
87 }
88 f = f.Elem()
89 }
90
91 // Capture information about the config variable
92 info := varInfo{
93 Name: ftype.Name,
94 Field: f,
95 Tags: ftype.Tag,
96 Alt: strings.ToUpper(ftype.Tag.Get("envconfig")),
97 }
98
99 // Default to the field name as the env var name (will be upcased)
100 info.Key = info.Name
101
102 // Best effort to un-pick camel casing as separate words
103 if ftype.Tag.Get("split_words") == "true" {
104 words := expr.FindAllStringSubmatch(ftype.Name, -1)
105 if len(words) > 0 {
106 var name []string
107 for _, words := range words {
108 name = append(name, words[0])
109 }
110
111 info.Key = strings.Join(name, "_")
112 }
113 }
114 if info.Alt != "" {
115 info.Key = info.Alt
116 }
117 if prefix != "" {
118 info.Key = fmt.Sprintf("%s_%s", prefix, info.Key)
119 }
120 info.Key = strings.ToUpper(info.Key)
121 infos = append(infos, info)
122
123 if f.Kind() == reflect.Struct {
124 // honor Decode if present
125 if decoderFrom(f) == nil && setterFrom(f) == nil && textUnmarshaler(f) == nil {
126 innerPrefix := prefix
127 if !ftype.Anonymous {
128 innerPrefix = info.Key
129 }
130
131 embeddedPtr := f.Addr().Interface()
132 embeddedInfos, err := gatherInfo(innerPrefix, embeddedPtr)
133 if err != nil {
134 return nil, err
135 }
136 infos = append(infos[:len(infos)-1], embeddedInfos...)
137
138 continue
139 }
140 }
141 }
142 return infos, nil
143}
144
145// Process populates the specified struct based on environment variables
146func Process(prefix string, spec interface{}) error {
147 infos, err := gatherInfo(prefix, spec)
148
149 for _, info := range infos {
150
151 // `os.Getenv` cannot differentiate between an explicitly set empty value
152 // and an unset value. `os.LookupEnv` is preferred to `syscall.Getenv`,
153 // but it is only available in go1.5 or newer. We're using Go build tags
154 // here to use os.LookupEnv for >=go1.5
155 value, ok := lookupEnv(info.Key)
156 if !ok && info.Alt != "" {
157 value, ok = lookupEnv(info.Alt)
158 }
159
160 def := info.Tags.Get("default")
161 if def != "" && !ok {
162 value = def
163 }
164
165 req := info.Tags.Get("required")
166 if !ok && def == "" {
167 if req == "true" {
168 return fmt.Errorf("required key %s missing value", info.Key)
169 }
170 continue
171 }
172
173 err := processField(value, info.Field)
174 if err != nil {
175 return &ParseError{
176 KeyName: info.Key,
177 FieldName: info.Name,
178 TypeName: info.Field.Type().String(),
179 Value: value,
180 Err: err,
181 }
182 }
183 }
184
185 return err
186}
187
188// MustProcess is the same as Process but panics if an error occurs
189func MustProcess(prefix string, spec interface{}) {
190 if err := Process(prefix, spec); err != nil {
191 panic(err)
192 }
193}
194
195func processField(value string, field reflect.Value) error {
196 typ := field.Type()
197
198 decoder := decoderFrom(field)
199 if decoder != nil {
200 return decoder.Decode(value)
201 }
202 // look for Set method if Decode not defined
203 setter := setterFrom(field)
204 if setter != nil {
205 return setter.Set(value)
206 }
207
208 if t := textUnmarshaler(field); t != nil {
209 return t.UnmarshalText([]byte(value))
210 }
211
212 if typ.Kind() == reflect.Ptr {
213 typ = typ.Elem()
214 if field.IsNil() {
215 field.Set(reflect.New(typ))
216 }
217 field = field.Elem()
218 }
219
220 switch typ.Kind() {
221 case reflect.String:
222 field.SetString(value)
223 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
224 var (
225 val int64
226 err error
227 )
228 if field.Kind() == reflect.Int64 && typ.PkgPath() == "time" && typ.Name() == "Duration" {
229 var d time.Duration
230 d, err = time.ParseDuration(value)
231 val = int64(d)
232 } else {
233 val, err = strconv.ParseInt(value, 0, typ.Bits())
234 }
235 if err != nil {
236 return err
237 }
238
239 field.SetInt(val)
240 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
241 val, err := strconv.ParseUint(value, 0, typ.Bits())
242 if err != nil {
243 return err
244 }
245 field.SetUint(val)
246 case reflect.Bool:
247 val, err := strconv.ParseBool(value)
248 if err != nil {
249 return err
250 }
251 field.SetBool(val)
252 case reflect.Float32, reflect.Float64:
253 val, err := strconv.ParseFloat(value, typ.Bits())
254 if err != nil {
255 return err
256 }
257 field.SetFloat(val)
258 case reflect.Slice:
259 vals := strings.Split(value, ",")
260 sl := reflect.MakeSlice(typ, len(vals), len(vals))
261 for i, val := range vals {
262 err := processField(val, sl.Index(i))
263 if err != nil {
264 return err
265 }
266 }
267 field.Set(sl)
268 }
269
270 return nil
271}
272
273func interfaceFrom(field reflect.Value, fn func(interface{}, *bool)) {
274 // it may be impossible for a struct field to fail this check
275 if !field.CanInterface() {
276 return
277 }
278 var ok bool
279 fn(field.Interface(), &ok)
280 if !ok && field.CanAddr() {
281 fn(field.Addr().Interface(), &ok)
282 }
283}
284
285func decoderFrom(field reflect.Value) (d Decoder) {
286 interfaceFrom(field, func(v interface{}, ok *bool) { d, *ok = v.(Decoder) })
287 return d
288}
289
290func setterFrom(field reflect.Value) (s Setter) {
291 interfaceFrom(field, func(v interface{}, ok *bool) { s, *ok = v.(Setter) })
292 return s
293}
294
295func textUnmarshaler(field reflect.Value) (t encoding.TextUnmarshaler) {
296 interfaceFrom(field, func(v interface{}, ok *bool) { t, *ok = v.(encoding.TextUnmarshaler) })
297 return t
298}