blob: 1daf38987944a94555b5d1d07f14b8dc56143e13 [file] [log] [blame]
gunjan54fbb81c2016-07-09 03:31:27 -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 "errors"
9 "fmt"
10 "reflect"
11 "strconv"
12 "strings"
13 "syscall"
14 "time"
15)
16
17// ErrInvalidSpecification indicates that a specification is of the wrong type.
18var ErrInvalidSpecification = errors.New("specification must be a struct pointer")
19
20// A ParseError occurs when an environment variable cannot be converted to
21// the type required by a struct field during assignment.
22type ParseError struct {
23 KeyName string
24 FieldName string
25 TypeName string
26 Value string
27}
28
29// A Decoder is a type that knows how to de-serialize environment variables
30// into itself.
31type Decoder interface {
32 Decode(value string) error
33}
34
35func (e *ParseError) Error() string {
36 return fmt.Sprintf("envconfig.Process: assigning %[1]s to %[2]s: converting '%[3]s' to type %[4]s", e.KeyName, e.FieldName, e.Value, e.TypeName)
37}
38
39// Process populates the specified struct based on environment variables
40func Process(prefix string, spec interface{}) error {
41 s := reflect.ValueOf(spec)
42
43 if s.Kind() != reflect.Ptr {
44 return ErrInvalidSpecification
45 }
46 s = s.Elem()
47 if s.Kind() != reflect.Struct {
48 return ErrInvalidSpecification
49 }
50 typeOfSpec := s.Type()
51 for i := 0; i < s.NumField(); i++ {
52 f := s.Field(i)
53 if !f.CanSet() || typeOfSpec.Field(i).Tag.Get("ignored") == "true" {
54 continue
55 }
56
57 if typeOfSpec.Field(i).Anonymous && f.Kind() == reflect.Struct {
58 embeddedPtr := f.Addr().Interface()
59 if err := Process(prefix, embeddedPtr); err != nil {
60 return err
61 }
62 f.Set(reflect.ValueOf(embeddedPtr).Elem())
63 }
64
65 alt := typeOfSpec.Field(i).Tag.Get("envconfig")
66 fieldName := typeOfSpec.Field(i).Name
67 if alt != "" {
68 fieldName = alt
69 }
70 key := strings.ToUpper(fmt.Sprintf("%s_%s", prefix, fieldName))
71 // `os.Getenv` cannot differentiate between an explicitly set empty value
72 // and an unset value. `os.LookupEnv` is preferred to `syscall.Getenv`,
73 // but it is only available in go1.5 or newer.
74 value, ok := syscall.Getenv(key)
75 if !ok && alt != "" {
76 key := strings.ToUpper(fieldName)
77 value, ok = syscall.Getenv(key)
78 }
79
80 def := typeOfSpec.Field(i).Tag.Get("default")
81 if def != "" && !ok {
82 value = def
83 }
84
85 req := typeOfSpec.Field(i).Tag.Get("required")
86 if !ok && def == "" {
87 if req == "true" {
88 return fmt.Errorf("required key %s missing value", key)
89 }
90 continue
91 }
92
93 err := processField(value, f)
94 if err != nil {
95 return &ParseError{
96 KeyName: key,
97 FieldName: fieldName,
98 TypeName: f.Type().String(),
99 Value: value,
100 }
101 }
102 }
103 return nil
104}
105
106// MustProcess is the same as Process but panics if an error occurs
107func MustProcess(prefix string, spec interface{}) {
108 if err := Process(prefix, spec); err != nil {
109 panic(err)
110 }
111}
112
113func processField(value string, field reflect.Value) error {
114 typ := field.Type()
115
116 decoder := decoderFrom(field)
117 if decoder != nil {
118 return decoder.Decode(value)
119 }
120
121 if typ.Kind() == reflect.Ptr {
122 typ = typ.Elem()
123 if field.IsNil() {
124 field.Set(reflect.New(typ))
125 }
126 field = field.Elem()
127 }
128
129 switch typ.Kind() {
130 case reflect.String:
131 field.SetString(value)
132 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
133 var (
134 val int64
135 err error
136 )
137 if field.Kind() == reflect.Int64 && typ.PkgPath() == "time" && typ.Name() == "Duration" {
138 var d time.Duration
139 d, err = time.ParseDuration(value)
140 val = int64(d)
141 } else {
142 val, err = strconv.ParseInt(value, 0, typ.Bits())
143 }
144 if err != nil {
145 return err
146 }
147
148 field.SetInt(val)
149 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
150 val, err := strconv.ParseUint(value, 0, typ.Bits())
151 if err != nil {
152 return err
153 }
154 field.SetUint(val)
155 case reflect.Bool:
156 val, err := strconv.ParseBool(value)
157 if err != nil {
158 return err
159 }
160 field.SetBool(val)
161 case reflect.Float32, reflect.Float64:
162 val, err := strconv.ParseFloat(value, typ.Bits())
163 if err != nil {
164 return err
165 }
166 field.SetFloat(val)
167 case reflect.Slice:
168 vals := strings.Split(value, ",")
169 sl := reflect.MakeSlice(typ, len(vals), len(vals))
170 for i, val := range vals {
171 err := processField(val, sl.Index(i))
172 if err != nil {
173 return err
174 }
175 }
176 field.Set(sl)
177 }
178
179 return nil
180}
181
182func decoderFrom(field reflect.Value) Decoder {
183 if field.CanInterface() {
184 dec, ok := field.Interface().(Decoder)
185 if ok {
186 return dec
187 }
188 }
189
190 // also check if pointer-to-type implements Decoder,
191 // and we can get a pointer to our field
192 if field.CanAddr() {
193 field = field.Addr()
194 dec, ok := field.Interface().(Decoder)
195 if ok {
196 return dec
197 }
198 }
199
200 return nil
201}