From 0177d349df77cc51571ce9e860b329631e8cd63f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20T=C3=B3th?= Date: Fri, 15 Oct 2021 22:14:59 +0200 Subject: [PATCH] Add poc to parse config from env. Should refactor (famous last words) --- config/config.go | 115 ++++++++++++++++++++++++++++++++++++++++++ config/config_test.go | 49 ++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 config/config.go create mode 100644 config/config_test.go diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..773eaca --- /dev/null +++ b/config/config.go @@ -0,0 +1,115 @@ +package config + +import ( + "os" + "strings" + "fmt" + "reflect" + "errors" + "strconv" +) + + +var ConfigFetchMethod = os.Environ + + +func Build(prefix string, configStruct interface{}) (err error) { + defer func() { + if e := recover(); e != nil { + switch r := e.(type) { + case string: + err = errors.New(r) + case error: + err = r + default: + err = errors.New("Undefined panic") + } + } + }() + assertIsPtr(configStruct) + + fieldNames := getConfigStructFieldNames(configStruct) + config := loadConfig(prefix) + + structRef := reflect.ValueOf(configStruct) + for _, fieldName := range fieldNames { + value, ok := config[fieldName] + if !ok { + // no such field loaded, avoid overwriting default + continue + } + field := reflect.Indirect(structRef).FieldByName(fieldName) + typeRef := field.Type() + ret, err := tryParseString(value, typeRef.Kind()) + if err != nil { + return err + } + field.Set(reflect.ValueOf(ret).Convert(typeRef)) + } + + return nil +} + +func tryParseString(what string, toType reflect.Kind) (interface{}, error) { + switch toType { + case reflect.String: + return what, nil + case reflect.Int: + i, err := strconv.Atoi(what) + if err != nil { + return nil, err + } + return i, nil + case reflect.Bool: + b, err := strconv.ParseBool(what) + if err != nil { + return nil, err + } + return b, nil + default: + return nil, fmt.Errorf("Failed to parse value %v", what) + } +} + +func assertIsPtr(what interface{}) { + r := reflect.ValueOf(what) + if r.Kind() != reflect.Ptr { + panic(fmt.Errorf("Supplied value is not a pointer to a struct")) + } +} + +func getConfigStructFieldNames(configStruct interface{}) []string { + configFields := []string{} + + val := reflect.ValueOf(configStruct).Elem() + for i := 0; i < val.NumField(); i++ { + configFields = append(configFields, val.Type().Field(i).Name) + } + + return configFields +} + +func loadConfig(prefix string) map[string]string { + prefix = fmt.Sprintf("%s_", prefix) + + config := map[string]string{} + for _, env := range 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 +} + +// CheckKeysExist returns an error if not all keys are present in config map +func CheckKeysExist(config map[string]string, keys ...string) error { + for _, key := range keys { + if _, ok := config[key]; ok { + return fmt.Errorf("Config key '%s' is not set", key) + } + } + return nil +} \ No newline at end of file diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000..d703771 --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,49 @@ +package config_test + +import ( + "testing" + + "kdelsd/config" +) + + +type TestConfig struct { + INT int + BOOL bool + STR string + OTHER_STR string +} + +var testConfigInstance = TestConfig{ + INT: 42, + BOOL: true, + STR: "sajtok", + OTHER_STR: "default-value", +} + +var testEnv = []string { + "TEST_INT=42", + "TEST_BOOL=true", + "TEST_STR=sajtok", +} + +func setup() { + config.ConfigFetchMethod = func() []string { + return testEnv + } +} + +func TestBuildConfig(t *testing.T) { + setup() + c := TestConfig{} + c.OTHER_STR = testConfigInstance.OTHER_STR + + err := config.Build("TEST", &c) + if err != nil { + panic(err) + } + + if c != testConfigInstance { + t.Errorf("%v != %v", c, testConfigInstance) + } +} \ No newline at end of file