-
Notifications
You must be signed in to change notification settings - Fork 0
/
env.go
157 lines (125 loc) · 3.75 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
145
146
147
148
149
150
151
152
153
154
155
156
157
package env
import (
"os"
"reflect"
"strconv"
)
// Parse is the main function of this package. It takes any valid interface{}
// value as parameter and expects it to be a pointer to any structure.
// It attempts to read the 'env' and 'envDefault'
//
// An error is returned if the value passed as parameter is not a pointer to a
// structure and it returns an ErrInvalidInterface.
func Parse(i interface{}) error {
if isInvalidInterface(i) {
return ErrInvalidInterface
}
elem := reflect.ValueOf(i).Elem()
return setStructValues(&elem)
}
// isInvalidInterface determines whether i is a pointer to a structure.
// Returns true if it's not a pointer to a structure.
func isInvalidInterface(i interface{}) bool {
if i == nil {
return true
}
interfaceValue := reflect.ValueOf(i)
interfaceKind := interfaceValue.Kind()
if interfaceKind != reflect.Ptr {
return true
}
interfaceKind = interfaceValue.Elem().Kind()
return interfaceKind != reflect.Struct
}
// setStructValues iterates over each of the structures elements to determine
// what value must be set to the field. If no env value of default env value
// was found, then we skip the current field.
func setStructValues(structElem *reflect.Value) error {
numFields := structElem.NumField()
structType := structElem.Type()
for i := 0; i < numFields; i++ {
structField := structType.Field(i)
fieldValue := structElem.Field(i)
if fieldValue.Kind() == reflect.Struct {
if err := setValue(&fieldValue, structField.Name, ""); err != nil {
return err
}
continue
}
envValue := getEnvOrDefaultValue(&structField)
if envValue == "" {
continue
}
if err := setValue(&fieldValue, structField.Name, envValue); err != nil {
return err
}
}
return nil
}
// getEnvOrDefaultValue attempts to read the env tags from the structure field,
// and returns the environment value found or the default value.
//
// Two env tags can be set, and those are the 'env' tag and the 'envDefault'
// tags. The 'env' tag contains the name of the environment key, and the
// 'envDefault' tag contains the default value for the structure field.
func getEnvOrDefaultValue(field *reflect.StructField) string {
envKey := field.Tag.Get("env")
defaultValue := field.Tag.Get("envDefault")
if envKey == "" {
return defaultValue
}
envValue := os.Getenv(envKey)
if envValue == "" {
return defaultValue
}
return envValue
}
// setValue function is in charge of determining if the field is an
// exported field (accesible). We return a ErrFieldMustBeAssignable if
// the field is unaccesible.
//
// For each of the supported types, we just parse the string value into the
// corresponding type and assigned it to the field. If there's an error when
// parsing a type, then we return that error.
//
// For structures, we recursively call the Parse function in this package
// to attempt to set all values.
//
// If an unsupported field is found, then we return an
// ErrUnsupportedFieldKind.
func setValue(field *reflect.Value, fieldName string, envValue string) error {
if !field.CanSet() {
return ErrFieldMustBeAssignable(fieldName)
}
fieldKind := field.Kind()
switch fieldKind {
case reflect.String:
field.SetString(envValue)
case reflect.Int:
intValue, err := strconv.ParseInt(envValue, 10, 32)
if err != nil {
return err
}
field.SetInt(intValue)
case reflect.Bool:
boolValue, err := strconv.ParseBool(envValue)
if err != nil {
return err
}
field.SetBool(boolValue)
case reflect.Float32:
floatValue, err := strconv.ParseFloat(envValue, 32)
if err != nil {
return err
}
field.SetFloat(floatValue)
case reflect.Struct:
return Parse(field.Addr().Interface())
default:
return &ErrUnsupportedFieldKind{
FieldName: fieldName,
FieldKind: fieldKind.String(),
}
}
return nil
}