-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathenv.go
144 lines (120 loc) · 3.91 KB
/
env.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
package env
import (
"fmt"
"os"
"reflect"
"strconv"
)
type configType string
const (
configTypeEnvironment configType = "environment"
)
// Setter is called for any complex struct field with an
// implementation, allowing developers to override Set
// behaviour.
type Setter interface {
Set(string) error
}
// Set sets the fields of a struct from environment config.
// If a field is unexported or required configuration is not
// found, an error will be returned.
func Set(i interface{}) (err error) {
return SetPrefix(i, "")
}
// SetPrefix sets the fields of a struct from environment config
// with a given prefix. If a field is unexported or required
// configuration is not found, an error will be returned.
func SetPrefix(i interface{}, prefix string) (err error) {
v := reflect.ValueOf(i)
// Don't try to process a non-pointer value.
if v.Kind() != reflect.Ptr || v.IsNil() {
return fmt.Errorf("%s is not a pointer", v.Kind())
}
v = v.Elem()
t := reflect.TypeOf(i).Elem()
for i := 0; i < t.NumField(); i++ {
if err = processField(prefix, t.Field(i), v.Field(i)); err != nil {
return
}
}
return
}
// processField will lookup the "env" tag for the property
// and attempt to set it. If not found, another check for the
// "required" tag will be performed to decided whether an error
// needs to be returned.
func processField(prefix string, t reflect.StructField, v reflect.Value) (err error) {
envTag, ok := t.Tag.Lookup("env")
if !ok {
return
}
// If the field is unexported or just not settable, bail at
// this point because subsequent operations will fail.
if !v.CanSet() {
return fmt.Errorf("field '%s' cannot be set", t.Name)
}
// Lookup the environment variable and if found, set and
// return
env, ok := os.LookupEnv(prefix + envTag)
if ok {
return setField(t, v, env)
}
// If the value isn't found in the environment, look for a
// user-defined default value
d, ok := t.Tag.Lookup("default")
if ok {
return setField(t, v, d)
}
// An env tag has been provided but a matching environment
// variable cannot be found, determine if we should return
// an error or if a missing variable is ok/expected.
return processMissing(t, envTag, configTypeEnvironment)
}
func setField(t reflect.StructField, v reflect.Value, value string) (err error) {
// If field implements the Setter interface, invoke it now and
// don't continue attempting to set the primitive values.
if _, ok := v.Interface().(Setter); ok {
instance := reflect.New(t.Type.Elem())
v.Set(instance)
// Re-assert the type with the newed-up instance and call.
setter := v.Interface().(Setter)
if err = setter.Set(value); err != nil {
return fmt.Errorf("error in custom setter: %v", err)
}
return
}
// If the given type is a slice, create a slice and return,
// otherwise, we're dealing with a primitive type
if v.Kind() == reflect.Slice {
return setSlice(t, v, value)
}
if err = setBuiltInField(v, value); err != nil {
return fmt.Errorf("error setting %q: %v", t.Name, err)
}
return
}
// ProcessMissing returns an error if a required tag is found
// and is set to true. A different error will be returned if
// the required tag was present but the value could not be parsed
// to a Boolean value.
func processMissing(t reflect.StructField, envTag string, ct configType) (err error) {
reqTag, ok := t.Tag.Lookup("required")
if !ok {
// No required tag was found, this field doesn't expect
// an env tag to be provided.
return nil
}
var b bool
if b, err = strconv.ParseBool(reqTag); err != nil {
// The value provided for the required tag is not a valid
// Boolean, so inform the user.
return fmt.Errorf("invalid required tag %q: %v", reqTag, err)
}
if b {
// The value provided for the required tag is valid and is
// set to true, so the user needs to know that a required
// environment variable could not be found.
return fmt.Errorf("%s %s configuration was missing", envTag, ct)
}
return
}