Add poc to parse config from env. Should refactor (famous last words)
This commit is contained in:
		
							
								
								
									
										115
									
								
								config/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								config/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										49
									
								
								config/config_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								config/config_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user