119 lines
2.9 KiB
Go
119 lines
2.9 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
|
|
type ConfigBuilder struct {
|
|
ConfigFetchMethod func()[]string
|
|
typeParsers map[reflect.Kind]func(string)(interface{}, error)
|
|
}
|
|
|
|
func NewBuilder() *ConfigBuilder {
|
|
return &ConfigBuilder{
|
|
ConfigFetchMethod: os.Environ,
|
|
typeParsers: map[reflect.Kind]func(string)(interface{}, error) {
|
|
reflect.String: func(value string) (interface{}, error) {
|
|
return value, nil
|
|
},
|
|
reflect.Int: func(value string) (interface{}, error) {
|
|
i, err := strconv.Atoi(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return i, nil
|
|
},
|
|
reflect.Bool: func(value string) (interface{}, error) {
|
|
b, err := strconv.ParseBool(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return b, nil
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (cb *ConfigBuilder) Build(prefix string, configStruct interface{}) error {
|
|
assertIsPtr(configStruct)
|
|
|
|
fieldNames := getConfigStructFieldNames(configStruct)
|
|
config := cb.loadConfig(prefix)
|
|
structRef := reflect.ValueOf(configStruct)
|
|
|
|
for _, fieldName := range fieldNames {
|
|
value, ok := config[fieldName]
|
|
if !ok {
|
|
// no such field loaded, avoid overwriting default if field is optional
|
|
field, _ := reflect.TypeOf(configStruct).Elem().FieldByName(fieldName)
|
|
if strings.Contains(string(field.Tag), "optional") {
|
|
continue
|
|
}
|
|
return fmt.Errorf("no config found for required field '%s'", fieldName)
|
|
}
|
|
field := reflect.Indirect(structRef).FieldByName(fieldName)
|
|
typeRef := field.Type()
|
|
typedValue, err := cb.tryParseString(value, typeRef.Kind())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
field.Set(reflect.ValueOf(typedValue).Convert(typeRef))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func assertIsPtr(configStruct interface{}) {
|
|
r := reflect.ValueOf(configStruct)
|
|
if r.Kind() != reflect.Ptr {
|
|
panic(fmt.Errorf("supplied value is not a pointer to a struct"))
|
|
}
|
|
}
|
|
|
|
func getConfigStructFieldNames(configStruct interface{}) []string {
|
|
configFields := []string{}
|
|
structValue := reflect.ValueOf(configStruct).Elem()
|
|
|
|
for i := 0; i < structValue.NumField(); i++ {
|
|
configFields = append(configFields, structValue.Type().Field(i).Name)
|
|
}
|
|
|
|
return configFields
|
|
}
|
|
|
|
func (cb *ConfigBuilder) loadConfig(prefix string) map[string]string {
|
|
config := map[string]string{}
|
|
|
|
for _, env := range cb.ConfigFetchMethod() {
|
|
parts := strings.SplitN(env, "=", 2)
|
|
key, value := parts[0], parts[1]
|
|
if strings.HasPrefix(key, prefix) {
|
|
key = strings.TrimPrefix(key, prefix)
|
|
config[key] = value
|
|
}
|
|
}
|
|
|
|
return config
|
|
}
|
|
|
|
func (cb *ConfigBuilder) tryParseString(value string, toType reflect.Kind) (interface{}, error) {
|
|
parser, ok := cb.typeParsers[toType]
|
|
if !ok {
|
|
return nil, fmt.Errorf("no parser found for type %v", toType)
|
|
}
|
|
parsed_val, err := parser(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return parsed_val, nil
|
|
}
|
|
|
|
func (cb *ConfigBuilder) RegisterParser(toType reflect.Kind, parser func(string)(interface{}, error)) {
|
|
cb.typeParsers[toType] = parser
|
|
}
|