-
Notifications
You must be signed in to change notification settings - Fork 1
/
config_loader.go
136 lines (126 loc) · 4.13 KB
/
config_loader.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
package shared
import (
"fmt"
"github.com/DIMO-Network/yaml"
"github.com/ethereum/go-ethereum/common"
"net/url"
"os"
"reflect"
"strconv"
"strings"
"github.com/pkg/errors"
)
// LoadConfig fills in all the values in the Settings from local yml file (for dev) and env vars (for deployments)
func LoadConfig[S any](filePath string) (S, error) {
file, err := os.ReadFile(filePath)
var settings S
// if no file found, ignore as we could be running in higher level environment. We could make this more explicit with a cli parameter w/ the filename
if err != nil {
fmt.Printf("looks like running in higher level env as could not read file: %s \n", filePath)
} else {
settings, err = loadFromYaml[S](file)
if err != nil {
return settings, errors.Wrap(err, "could not load yaml")
}
}
err = loadFromEnvVars(&settings) // override with any env vars found
return settings, err
}
// loadFromYaml loads yaml from text into object per type. Uses reflection to find yaml type metadata for lookup
func loadFromYaml[S any](yamlFile []byte) (S, error) {
var settings S
err := yaml.Unmarshal(yamlFile, &settings)
if err != nil {
return settings, errors.Wrap(err, "could not unmarshall yaml file to settings")
}
return settings, nil
}
// loadFromEnvVars uses reflection to find yaml type metadata and lookup those values in environment variables for corresponding property.
// settings must be passed in as a pointer
func loadFromEnvVars[S any](settings S) error {
if reflect.ValueOf(settings).Kind() != reflect.Ptr {
return errors.New("settings must be passed in as a pointer")
}
valueOfConfig := reflect.ValueOf(settings).Elem()
typeOfT := valueOfConfig.Type()
var err error
// iterate over all struct fields
for i := 0; i < valueOfConfig.NumField(); i++ {
field := valueOfConfig.Field(i)
fieldYamlName := typeOfT.Field(i).Tag.Get("yaml")
if fieldYamlName == "-" { // ignore property
continue
}
// handle specific types we want to support
specialTypes := []reflect.Type{
reflect.TypeOf(url.URL{}),
reflect.TypeOf(common.Address{}),
}
if field.Kind() == reflect.Struct &&
// exclude any special types
!containsType(specialTypes, typeOfT.Field(i).Type) {
// iterate through the fields - like above, prepend fieldYamlName
for i := 0; i < field.NumField(); i++ {
subField := field.Field(i)
subFieldYamlName := field.Type().Field(i).Tag.Get("yaml")
if !strings.HasSuffix(fieldYamlName, ",inline") {
subFieldYamlName = fieldYamlName + "_" + subFieldYamlName
}
err = matchEnvVarToField(subFieldYamlName, subField)
}
} else {
// any scalar property should just be handled here
err = matchEnvVarToField(fieldYamlName, field)
}
}
return err
}
func containsType(types []reflect.Type, t reflect.Type) bool {
for _, typ := range types {
if typ == t {
return true
}
}
return false
}
// matchEnvVarToField updates the field with the corresponding env variable value.
// check if env var with same field yaml name exists, if so, set the value from the env var
func matchEnvVarToField(envVarName string, field reflect.Value) error { // otherwise need to pass in field.Kind() and return any for the value.
var err error
// check if env var with same field yaml name exists, if so, set the value from the env var
if env, exists := os.LookupEnv(envVarName); exists {
var val any
switch field.Kind() {
case reflect.String:
val = env
case reflect.Bool:
val, err = strconv.ParseBool(env)
case reflect.Int:
val, err = strconv.Atoi(env)
case reflect.Int64:
val, err = strconv.ParseInt(env, 10, 64)
default:
// support special types
switch field.Type() {
case reflect.TypeOf(url.URL{}):
if urlParsed, err := url.Parse(env); err == nil {
val = *urlParsed
} else {
return err
}
case reflect.TypeOf(common.Address{}):
if !common.IsHexAddress(env) {
return fmt.Errorf("environment variable %s is not a valid eth address", env)
}
val = common.HexToAddress(env)
default:
return fmt.Errorf("unknown setting type: %s", field.Type().Name())
}
}
// now set the field with the val
if val != nil {
field.Set(reflect.ValueOf(val))
}
}
return err
}