From 3552acd38bfa2572146744e8fe4f87c2f261c9c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Schw=C3=B6rer?= Date: Thu, 15 Dec 2022 12:36:24 +0100 Subject: [PATCH] v0.0.36 --- confext/confParser.go | 136 +++++++++++++++++++++++++++++++++++++ confext/confParser_test.go | 116 +++++++++++++++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 confext/confParser.go create mode 100644 confext/confParser_test.go diff --git a/confext/confParser.go b/confext/confParser.go new file mode 100644 index 0000000..d2996dd --- /dev/null +++ b/confext/confParser.go @@ -0,0 +1,136 @@ +package confext + +import ( + "errors" + "fmt" + "gogs.mikescher.com/BlackForestBytes/goext/timeext" + "math/bits" + "os" + "reflect" + "strconv" + "time" +) + +// ApplyEnvOverrides overrides field values from environment variables +// +// fields must be tagged with `env:"env_key"` +func ApplyEnvOverrides[T any](c *T) error { + + rval := reflect.ValueOf(c).Elem() + rtyp := rval.Type() + + for i := 0; i < rtyp.NumField(); i++ { + + rsfield := rtyp.Field(i) + rvfield := rval.Field(i) + + envkey := rsfield.Tag.Get("env") + if envkey == "" { + continue + } + + envval, efound := os.LookupEnv(envkey) + if !efound { + continue + } + + if rvfield.Type() == reflect.TypeOf("") { + + rvfield.Set(reflect.ValueOf(envval)) + + fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", envkey, envval) + + } else if rvfield.Type() == reflect.TypeOf(int(0)) { + + envint, err := strconv.ParseInt(envval, 10, bits.UintSize) + if err != nil { + return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to int (value := '%s')", envkey, envval)) + } + + rvfield.Set(reflect.ValueOf(int(envint))) + + fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", envkey, envval) + + } else if rvfield.Type() == reflect.TypeOf(int64(0)) { + + envint, err := strconv.ParseInt(envval, 10, 64) + if err != nil { + return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to int64 (value := '%s')", envkey, envval)) + } + + rvfield.Set(reflect.ValueOf(int64(envint))) + + fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", envkey, envval) + + } else if rvfield.Type() == reflect.TypeOf(int32(0)) { + + envint, err := strconv.ParseInt(envval, 10, 32) + if err != nil { + return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to int32 (value := '%s')", envkey, envval)) + } + + rvfield.Set(reflect.ValueOf(int32(envint))) + + fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", envkey, envval) + + } else if rvfield.Type() == reflect.TypeOf(int8(0)) { + + envint, err := strconv.ParseInt(envval, 10, 8) + if err != nil { + return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to int32 (value := '%s')", envkey, envval)) + } + + rvfield.Set(reflect.ValueOf(int8(envint))) + + fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", envkey, envval) + + } else if rvfield.Type() == reflect.TypeOf(time.Duration(0)) { + + dur, err := timeext.ParseDurationShortString(envval) + if err != nil { + return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to duration (value := '%s')", envkey, envval)) + } + + rvfield.Set(reflect.ValueOf(dur)) + + fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", envkey, dur.String()) + + } else if rvfield.Type() == reflect.TypeOf(time.UnixMilli(0)) { + + tim, err := time.Parse(time.RFC3339Nano, envval) + if err != nil { + return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to time.time (value := '%s')", envkey, envval)) + } + + rvfield.Set(reflect.ValueOf(tim)) + + fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", envkey, tim.String()) + + } else if rvfield.Type().ConvertibleTo(reflect.TypeOf(int(0))) { + + envint, err := strconv.ParseInt(envval, 10, 8) + if err != nil { + return errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to <%s, ,int> (value := '%s')", rvfield.Type().Name(), envkey, envval)) + } + + envcvl := reflect.ValueOf(envint).Convert(rvfield.Type()) + + rvfield.Set(envcvl) + + fmt.Printf("[CONF] Overwrite config '%s' with '%v'\n", envkey, envcvl.Interface()) + + } else if rvfield.Type().ConvertibleTo(reflect.TypeOf("")) { + + envcvl := reflect.ValueOf(envval).Convert(rvfield.Type()) + + rvfield.Set(envcvl) + + fmt.Printf("[CONF] Overwrite config '%s' with '%v'\n", envkey, envcvl.Interface()) + + } else { + return errors.New(fmt.Sprintf("Unknown kind/type in config: [ %s | %s ]", rvfield.Kind().String(), rvfield.Type().String())) + } + } + + return nil +} diff --git a/confext/confParser_test.go b/confext/confParser_test.go new file mode 100644 index 0000000..f2ff7fc --- /dev/null +++ b/confext/confParser_test.go @@ -0,0 +1,116 @@ +package confext + +import ( + "testing" + "time" +) + +func TestApplyEnvOverridesNoop(t *testing.T) { + + type aliasint int + type aliasstring string + + type testdata struct { + V1 int `env:"TEST_V1"` + VX string `` + V2 string `env:"TEST_V2"` + V3 int8 `env:"TEST_V3"` + V4 int32 `env:"TEST_V4"` + V5 int64 `env:"TEST_V5"` + V6 aliasint `env:"TEST_V6"` + VY aliasint `` + V7 aliasstring `env:"TEST_V7"` + V8 time.Duration `env:"TEST_V8"` + V9 time.Time `env:"TEST_V9"` + } + + input := testdata{ + V1: 1, + VX: "X", + V2: "2", + V3: 3, + V4: 4, + V5: 5, + V6: 6, + VY: 99, + V7: "7", + V8: 9, + V9: time.Unix(1671102873, 0), + } + + output := input + + err := ApplyEnvOverrides(&output) + if err != nil { + t.Errorf("%v", err) + t.FailNow() + } + + assertEqual(t, input, output) +} + +func TestApplyEnvOverridesSimple(t *testing.T) { + + type aliasint int + type aliasstring string + + type testdata struct { + V1 int `env:"TEST_V1"` + VX string `` + V2 string `env:"TEST_V2"` + V3 int8 `env:"TEST_V3"` + V4 int32 `env:"TEST_V4"` + V5 int64 `env:"TEST_V5"` + V6 aliasint `env:"TEST_V6"` + VY aliasint `` + V7 aliasstring `env:"TEST_V7"` + V8 time.Duration `env:"TEST_V8"` + V9 time.Time `env:"TEST_V9"` + } + + data := testdata{ + V1: 1, + VX: "X", + V2: "2", + V3: 3, + V4: 4, + V5: 5, + V6: 6, + VY: 99, + V7: "7", + V8: 9, + V9: time.Unix(1671102873, 0), + } + + t.Setenv("TEST_V1", "846") + t.Setenv("TEST_V2", "hello_world") + t.Setenv("TEST_V3", "6") + t.Setenv("TEST_V4", "333") + t.Setenv("TEST_V5", "-937") + t.Setenv("TEST_V6", "070") + t.Setenv("TEST_V7", "AAAAAA") + t.Setenv("TEST_V8", "1min4s") + t.Setenv("TEST_V9", "2009-11-10T23:00:00Z") + + err := ApplyEnvOverrides(&data) + if err != nil { + t.Errorf("%v", err) + t.FailNow() + } + + assertEqual(t, data.V1, 846) + assertEqual(t, data.V2, "hello_world") + assertEqual(t, data.V3, 6) + assertEqual(t, data.V4, 333) + assertEqual(t, data.V5, -937) + assertEqual(t, data.V6, 70) + assertEqual(t, data.V7, "AAAAAA") + assertEqual(t, data.V8, time.Second*64) + assertEqual(t, data.V9, time.Unix(1257894000, 0).UTC()) +} + +func assertEqual[T comparable](t *testing.T, actual T, expected T) { + if actual != expected { + t.Errorf("values differ: Actual: '%v', Expected: '%v'", actual, expected) + } +}