Refactor config package
This commit is contained in:
parent
0ce211b46d
commit
0bdbff89ac
@ -18,11 +18,11 @@ import (
|
|||||||
|
|
||||||
|
|
||||||
type afterLockConfig struct {
|
type afterLockConfig struct {
|
||||||
INITIAL_DELAY int
|
INITIAL_DELAY int `optional`
|
||||||
LOOP_DELAY int
|
LOOP_DELAY int `optional`
|
||||||
LOCK_PATH string
|
LOCK_PATH string `optional`
|
||||||
LOG_PATH string
|
LOG_PATH string
|
||||||
PRINT_STACKTRACES bool
|
PRINT_STACKTRACES bool `optional`
|
||||||
}
|
}
|
||||||
|
|
||||||
var configuration = afterLockConfig{
|
var configuration = afterLockConfig{
|
||||||
@ -46,7 +46,7 @@ func init() {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
defer handleErrors()
|
defer handleErrors()
|
||||||
err := config.Build("AFL", &configuration)
|
err := config.NewBuilder().Build("AFL_", &configuration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
127
config/config.go
127
config/config.go
@ -1,99 +1,95 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"errors"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
var ConfigFetchMethod = os.Environ
|
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 Build(prefix string, configStruct interface{}) (err error) {
|
func (cb *ConfigBuilder) Build(prefix string, configStruct interface{}) 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)
|
assertIsPtr(configStruct)
|
||||||
|
|
||||||
fieldNames := getConfigStructFieldNames(configStruct)
|
fieldNames := getConfigStructFieldNames(configStruct)
|
||||||
config := loadConfig(prefix)
|
config := cb.loadConfig(prefix)
|
||||||
|
|
||||||
structRef := reflect.ValueOf(configStruct)
|
structRef := reflect.ValueOf(configStruct)
|
||||||
|
|
||||||
for _, fieldName := range fieldNames {
|
for _, fieldName := range fieldNames {
|
||||||
value, ok := config[fieldName]
|
value, ok := config[fieldName]
|
||||||
if !ok {
|
if !ok {
|
||||||
// no such field loaded, avoid overwriting default
|
// no such field loaded, avoid overwriting default if field is optional
|
||||||
continue
|
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)
|
field := reflect.Indirect(structRef).FieldByName(fieldName)
|
||||||
typeRef := field.Type()
|
typeRef := field.Type()
|
||||||
ret, err := tryParseString(value, typeRef.Kind())
|
typedValue, err := cb.tryParseString(value, typeRef.Kind())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
field.Set(reflect.ValueOf(ret).Convert(typeRef))
|
field.Set(reflect.ValueOf(typedValue).Convert(typeRef))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryParseString(what string, toType reflect.Kind) (interface{}, error) {
|
func assertIsPtr(configStruct interface{}) {
|
||||||
switch toType {
|
r := reflect.ValueOf(configStruct)
|
||||||
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 {
|
if r.Kind() != reflect.Ptr {
|
||||||
panic(fmt.Errorf("Supplied value is not a pointer to a struct"))
|
panic(fmt.Errorf("supplied value is not a pointer to a struct"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConfigStructFieldNames(configStruct interface{}) []string {
|
func getConfigStructFieldNames(configStruct interface{}) []string {
|
||||||
configFields := []string{}
|
configFields := []string{}
|
||||||
|
structValue := reflect.ValueOf(configStruct).Elem()
|
||||||
|
|
||||||
val := reflect.ValueOf(configStruct).Elem()
|
for i := 0; i < structValue.NumField(); i++ {
|
||||||
for i := 0; i < val.NumField(); i++ {
|
configFields = append(configFields, structValue.Type().Field(i).Name)
|
||||||
configFields = append(configFields, val.Type().Field(i).Name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return configFields
|
return configFields
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadConfig(prefix string) map[string]string {
|
func (cb *ConfigBuilder) loadConfig(prefix string) map[string]string {
|
||||||
prefix = fmt.Sprintf("%s_", prefix)
|
|
||||||
|
|
||||||
config := map[string]string{}
|
config := map[string]string{}
|
||||||
for _, env := range ConfigFetchMethod() {
|
|
||||||
|
for _, env := range cb.ConfigFetchMethod() {
|
||||||
parts := strings.SplitN(env, "=", 2)
|
parts := strings.SplitN(env, "=", 2)
|
||||||
key, value := parts[0], parts[1]
|
key, value := parts[0], parts[1]
|
||||||
if strings.HasPrefix(key, prefix) {
|
if strings.HasPrefix(key, prefix) {
|
||||||
@ -101,15 +97,22 @@ func loadConfig(prefix string) map[string]string {
|
|||||||
config[key] = value
|
config[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckKeysExist returns an error if not all keys are present in config map
|
func (cb *ConfigBuilder) tryParseString(value string, toType reflect.Kind) (interface{}, error) {
|
||||||
func CheckKeysExist(config map[string]string, keys ...string) error {
|
parser, ok := cb.typeParsers[toType]
|
||||||
for _, key := range keys {
|
if !ok {
|
||||||
if _, ok := config[key]; ok {
|
return nil, fmt.Errorf("no parser found for type %v", toType)
|
||||||
return fmt.Errorf("Config key '%s' is not set", key)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package config_test
|
package config_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
|
||||||
|
|
||||||
"kdelsd/config"
|
"kdelsd/config"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -11,39 +13,96 @@ type TestConfig struct {
|
|||||||
INT int
|
INT int
|
||||||
BOOL bool
|
BOOL bool
|
||||||
STR string
|
STR string
|
||||||
OTHER_STR string
|
OTHER_STR string `optional`
|
||||||
|
COMPLEX complex64 `optional`
|
||||||
}
|
}
|
||||||
|
|
||||||
var testConfigInstance = TestConfig{
|
var testConfigReference = TestConfig{
|
||||||
INT: 42,
|
INT: 42,
|
||||||
BOOL: true,
|
BOOL: true,
|
||||||
STR: "sajtok",
|
STR: "sajtok",
|
||||||
OTHER_STR: "default-value",
|
OTHER_STR: "default-value",
|
||||||
}
|
}
|
||||||
|
|
||||||
var testEnv = []string {
|
var testEnv = []string{
|
||||||
"TEST_INT=42",
|
"TEST_INT=42",
|
||||||
"TEST_BOOL=true",
|
"TEST_BOOL=true",
|
||||||
"TEST_STR=sajtok",
|
"TEST_STR=sajtok",
|
||||||
}
|
}
|
||||||
|
|
||||||
func setup() {
|
func configBuilder() *config.ConfigBuilder {
|
||||||
config.ConfigFetchMethod = func() []string {
|
cb := config.NewBuilder()
|
||||||
|
cb.ConfigFetchMethod = func()[]string {
|
||||||
return testEnv
|
return testEnv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return cb
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func TestConfigBuilder(t *testing.T) {
|
||||||
|
c := TestConfig{}
|
||||||
|
c.OTHER_STR = testConfigReference.OTHER_STR
|
||||||
|
builder := configBuilder()
|
||||||
|
|
||||||
|
err := builder.Build("TEST_", &c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Config building should not fail\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c != testConfigReference {
|
||||||
|
t.Fatalf("Parsed config shoudl equal reference config\n%v != %v\n", c, testConfigReference)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildConfig(t *testing.T) {
|
func TestConfigBuilderParsers(t *testing.T) {
|
||||||
setup()
|
|
||||||
c := TestConfig{}
|
c := TestConfig{}
|
||||||
c.OTHER_STR = testConfigInstance.OTHER_STR
|
builder := configBuilder()
|
||||||
|
builder.ConfigFetchMethod = func()[]string {
|
||||||
|
return append(testEnv, "TEST_COMPLEX=42+42i")
|
||||||
|
}
|
||||||
|
|
||||||
err := config.Build("TEST", &c)
|
err := builder.Build("TEST_", &c)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("CONFIG should not be parseable, since type has no default parser\n")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "complex64") {
|
||||||
|
t.Fatalf("Error should contain the type that failed to parse\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.RegisterParser(reflect.Complex64, func(value string)(interface{}, error) {
|
||||||
|
num, err := strconv.ParseComplex(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return num, nil
|
||||||
|
})
|
||||||
|
err = builder.Build("TEST_", &c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
t.Fatalf("Parsing type should not fail after registering a parser for it\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigBuilderOptional(t *testing.T) {
|
||||||
|
type RequiredConfig struct {
|
||||||
|
REQUIRED_STR string
|
||||||
|
}
|
||||||
|
c := RequiredConfig{}
|
||||||
|
builder := config.NewBuilder()
|
||||||
|
|
||||||
|
err := builder.Build("TEST_", &c)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Fields not marked with the `optional` tag should be mandatory\n")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "REQUIRED_STR") {
|
||||||
|
t.Fatalf("Error should contain the name of the field\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c != testConfigInstance {
|
builder.ConfigFetchMethod = func()[]string {
|
||||||
t.Errorf("%v != %v", c, testConfigInstance)
|
return append(testEnv, "TEST_REQUIRED_STR=prrrr")
|
||||||
}
|
}
|
||||||
}
|
err = builder.Build("TEST_", &c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Config building should not fail after providing the mandatory field\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user