goext/confext/confParser.go

199 lines
5.5 KiB
Go
Raw Normal View History

2022-12-15 12:36:24 +01:00
package confext
import (
"errors"
"fmt"
"gogs.mikescher.com/BlackForestBytes/goext/timeext"
"math/bits"
"os"
"reflect"
"strconv"
2023-05-28 22:55:06 +02:00
"strings"
2022-12-15 12:36:24 +01:00
"time"
)
// ApplyEnvOverrides overrides field values from environment variables
//
// fields must be tagged with `env:"env_key"`
2023-01-06 02:02:22 +01:00
//
// only works on exported fields
//
// fields without an env tag are ignored
// fields with an `env:"-"` tag are ignore
//
// sub-structs are recursively parsed (if they have an env tag) and the env-variable keys are delimited by the delim parameter
// sub-structs with `env:""` are also parsed, but the delimited is skipped (they are handled as if they were one level higher)
2023-03-11 14:38:19 +01:00
func ApplyEnvOverrides[T any](prefix string, c *T, delim string) error {
2022-12-15 12:36:24 +01:00
rval := reflect.ValueOf(c).Elem()
2023-01-06 02:02:22 +01:00
2023-03-11 14:38:19 +01:00
return processEnvOverrides(rval, delim, prefix)
2023-01-06 02:02:22 +01:00
}
func processEnvOverrides(rval reflect.Value, delim string, prefix string) error {
2022-12-15 12:36:24 +01:00
rtyp := rval.Type()
for i := 0; i < rtyp.NumField(); i++ {
rsfield := rtyp.Field(i)
rvfield := rval.Field(i)
2023-01-06 02:02:22 +01:00
if !rsfield.IsExported() {
continue
}
if rvfield.Kind() == reflect.Struct {
envkey, found := rsfield.Tag.Lookup("env")
if !found || envkey == "-" {
continue
}
subPrefix := prefix
if envkey != "" {
subPrefix = subPrefix + envkey + delim
}
err := processEnvOverrides(rvfield, delim, subPrefix)
if err != nil {
return err
}
}
2022-12-15 12:36:24 +01:00
envkey := rsfield.Tag.Get("env")
2023-01-06 02:02:22 +01:00
if envkey == "" || envkey == "-" {
2022-12-15 12:36:24 +01:00
continue
}
2023-01-06 02:02:22 +01:00
fullEnvKey := prefix + envkey
envval, efound := os.LookupEnv(fullEnvKey)
2022-12-15 12:36:24 +01:00
if !efound {
continue
}
2023-03-07 10:43:30 +01:00
if rvfield.Type().Kind() == reflect.Pointer {
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
newval, err := parseEnvToValue(envval, fullEnvKey, rvfield.Type().Elem())
if err != nil {
return err
}
// converts reflect.Value to pointer
ptrval := reflect.New(rvfield.Type().Elem())
ptrval.Elem().Set(newval)
rvfield.Set(ptrval)
2022-12-15 12:36:24 +01:00
2023-01-28 14:44:12 +01:00
fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", fullEnvKey, envval)
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
} else {
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
newval, err := parseEnvToValue(envval, fullEnvKey, rvfield.Type())
2022-12-15 12:36:24 +01:00
if err != nil {
2023-03-07 10:43:30 +01:00
return err
2022-12-15 12:36:24 +01:00
}
2023-03-07 10:43:30 +01:00
rvfield.Set(newval)
2023-01-06 02:02:22 +01:00
fmt.Printf("[CONF] Overwrite config '%s' with '%s'\n", fullEnvKey, envval)
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
}
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
}
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
return nil
}
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
func parseEnvToValue(envval string, fullEnvKey string, rvtype reflect.Type) (reflect.Value, error) {
if rvtype == reflect.TypeOf("") {
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
return reflect.ValueOf(envval), nil
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
} else if rvtype == reflect.TypeOf(int(0)) {
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
envint, err := strconv.ParseInt(envval, 10, bits.UintSize)
if err != nil {
return reflect.Value{}, errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to int (value := '%s')", fullEnvKey, envval))
}
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
return reflect.ValueOf(int(envint)), nil
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
} else if rvtype == reflect.TypeOf(int64(0)) {
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
envint, err := strconv.ParseInt(envval, 10, 64)
if err != nil {
return reflect.Value{}, errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to int64 (value := '%s')", fullEnvKey, envval))
}
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
return reflect.ValueOf(int64(envint)), nil
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
} else if rvtype == reflect.TypeOf(int32(0)) {
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
envint, err := strconv.ParseInt(envval, 10, 32)
if err != nil {
return reflect.Value{}, errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to int32 (value := '%s')", fullEnvKey, envval))
}
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
return reflect.ValueOf(int32(envint)), nil
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
} else if rvtype == reflect.TypeOf(int8(0)) {
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
envint, err := strconv.ParseInt(envval, 10, 8)
if err != nil {
return reflect.Value{}, errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to int32 (value := '%s')", fullEnvKey, envval))
}
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
return reflect.ValueOf(int8(envint)), nil
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
} else if rvtype == reflect.TypeOf(time.Duration(0)) {
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
dur, err := timeext.ParseDurationShortString(envval)
if err != nil {
return reflect.Value{}, errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to duration (value := '%s')", fullEnvKey, envval))
}
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
return reflect.ValueOf(dur), nil
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
} else if rvtype == reflect.TypeOf(time.UnixMilli(0)) {
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
tim, err := time.Parse(time.RFC3339Nano, envval)
if err != nil {
return reflect.Value{}, errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to time.time (value := '%s')", fullEnvKey, envval))
}
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
return reflect.ValueOf(tim), nil
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
} else if rvtype.ConvertibleTo(reflect.TypeOf(int(0))) {
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
envint, err := strconv.ParseInt(envval, 10, 8)
if err != nil {
return reflect.Value{}, errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to <%s, ,int> (value := '%s')", rvtype.Name(), fullEnvKey, envval))
}
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
envcvl := reflect.ValueOf(envint).Convert(rvtype)
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
return envcvl, nil
2022-12-15 12:36:24 +01:00
2023-05-28 22:55:06 +02:00
} else if rvtype.ConvertibleTo(reflect.TypeOf(false)) {
if strings.TrimSpace(strings.ToLower(envval)) == "true" {
return reflect.ValueOf(true).Convert(rvtype), nil
} else if strings.TrimSpace(strings.ToLower(envval)) == "false" {
return reflect.ValueOf(true).Convert(rvtype), nil
} else if strings.TrimSpace(strings.ToLower(envval)) == "1" {
return reflect.ValueOf(false).Convert(rvtype), nil
} else if strings.TrimSpace(strings.ToLower(envval)) == "0" {
return reflect.ValueOf(false).Convert(rvtype), nil
} else {
return reflect.Value{}, errors.New(fmt.Sprintf("Failed to parse env-config variable '%s' to <%s, ,bool> (value := '%s')", rvtype.Name(), fullEnvKey, envval))
}
2023-03-07 10:43:30 +01:00
} else if rvtype.ConvertibleTo(reflect.TypeOf("")) {
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
envcvl := reflect.ValueOf(envval).Convert(rvtype)
return envcvl, nil
2022-12-15 12:36:24 +01:00
2023-03-07 10:43:30 +01:00
} else {
return reflect.Value{}, errors.New(fmt.Sprintf("Unknown kind/type in config: [ %s | %s ]", rvtype.Kind().String(), rvtype.String()))
2022-12-15 12:36:24 +01:00
}
}