2024-07-11 14:12:22 -05:00

242 lines
5.6 KiB
Go

package config
import (
"flag"
"fmt"
"os"
"reflect"
"strconv"
"strings"
)
type structInfo struct {
Name string
Alt string
Info string
Key string
Field reflect.Value
Tags reflect.StructTag
Type reflect.Type
DefaultValue interface{}
Secret interface{}
}
func getEnv[t string | bool | int | int64 | float64](env string, def t) (t, error) {
val := os.Getenv(env)
if len(val) == 0 {
return def, nil
}
output := *new(t)
switch (interface{})(def).(type) {
case string:
v, err := typeConversion("string", val)
if err != nil {
return (interface{})(false).(t), err
}
output = v.(t)
case bool:
v, err := typeConversion("bool", val)
if err != nil {
return (interface{})(false).(t), err
}
output = v.(t)
case int:
v, err := typeConversion("int", val)
if err != nil {
return (interface{})(int(0)).(t), err
}
output = (interface{})(int(v.(int64))).(t)
case int64:
v, err := typeConversion("int64", val)
if err != nil {
return (interface{})(int64(0)).(t), err
}
output = v.(t)
case float64:
v, err := typeConversion("float64", val)
if err != nil {
return (interface{})(float64(0)).(t), err
}
output = v.(t)
}
return output, nil
}
func getStructInfo(spec interface{}) ([]structInfo, error) {
s := reflect.ValueOf(spec)
if s.Kind() != reflect.Pointer {
return []structInfo{}, fmt.Errorf("getStructInfo() was sent a %s instead of a pointer to a struct.\n", s.Kind())
}
s = s.Elem()
if s.Kind() != reflect.Struct {
return []structInfo{}, fmt.Errorf("getStructInfo() was sent a %s instead of a struct.\n", s.Kind())
}
typeOfSpec := s.Type()
infos := make([]structInfo, 0, s.NumField())
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
ftype := typeOfSpec.Field(i)
ignored, _ := strconv.ParseBool(ftype.Tag.Get("ignored"))
if !f.CanSet() || ignored {
continue
}
for f.Kind() == reflect.Pointer {
if f.IsNil() {
if f.Type().Elem().Kind() != reflect.Struct {
break
}
f.Set(reflect.New(f.Type().Elem()))
}
f = f.Elem()
}
secret, err := typeConversion(ftype.Type.String(), ftype.Tag.Get("secret"))
if err != nil {
secret = false
}
var desc string
if len(ftype.Tag.Get("info")) != 0 {
desc = fmt.Sprintf("(%s) %s", strings.ToUpper(ftype.Tag.Get("env")), ftype.Tag.Get("info"))
} else {
desc = fmt.Sprintf("(%s)", strings.ToUpper(ftype.Tag.Get("env")))
}
info := structInfo{
Name: ftype.Name,
Alt: strings.ToUpper(ftype.Tag.Get("env")),
Info: desc,
Key: ftype.Name,
Field: f,
Tags: ftype.Tag,
Type: ftype.Type,
Secret: secret,
}
if info.Alt != "" {
info.Key = info.Alt
}
info.Key = strings.ToUpper(info.Key)
if ftype.Tag.Get("default") != "" {
v, err := typeConversion(ftype.Type.String(), ftype.Tag.Get("default"))
if err != nil {
return []structInfo{}, err
}
info.DefaultValue = v
}
infos = append(infos, info)
}
return infos, nil
}
func typeConversion(t, v string) (interface{}, error) {
switch t {
case "string": //nolint:goconst
return v, nil
case "int": //nolint:goconst
return strconv.ParseInt(v, 10, 0)
case "int8":
return strconv.ParseInt(v, 10, 8)
case "int16":
return strconv.ParseInt(v, 10, 16)
case "int32":
return strconv.ParseInt(v, 10, 32)
case "int64":
return strconv.ParseInt(v, 10, 64)
case "uint":
return strconv.ParseUint(v, 10, 0)
case "uint16":
return strconv.ParseUint(v, 10, 16)
case "uint32":
return strconv.ParseUint(v, 10, 32)
case "uint64":
return strconv.ParseUint(v, 10, 64)
case "float32":
return strconv.ParseFloat(v, 32)
case "float64":
return strconv.ParseFloat(v, 64)
case "complex64":
return strconv.ParseComplex(v, 64)
case "complex128":
return strconv.ParseComplex(v, 128)
case "bool": //nolint:goconst
return strconv.ParseBool(v)
}
return nil, fmt.Errorf("Unable to identify type.")
}
func (cfg *Config) parseFlags(cfgInfo []structInfo) error { //nolint:gocognit
for _, info := range cfgInfo {
switch info.Type.String() {
case "string":
var dv string
if info.DefaultValue != nil {
dv = info.DefaultValue.(string)
}
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*string)
retVal, err := getEnv(info.Alt, dv)
if err != nil {
return err
}
flag.StringVar(p, info.Name, retVal, info.Info)
case "bool":
var dv bool
if info.DefaultValue != nil {
dv = info.DefaultValue.(bool)
}
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*bool)
retVal, err := getEnv(info.Alt, dv)
if err != nil {
return err
}
flag.BoolVar(p, info.Name, retVal, info.Info)
case "int":
var dv int
if info.DefaultValue != nil {
dv = int(info.DefaultValue.(int64))
}
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*int)
retVal, err := getEnv(info.Alt, dv)
if err != nil {
return err
}
flag.IntVar(p, info.Name, retVal, info.Info)
case "int64":
var dv int64
if info.DefaultValue != nil {
dv = info.DefaultValue.(int64)
}
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*int64)
retVal, err := getEnv(info.Alt, dv)
if err != nil {
return err
}
flag.Int64Var(p, info.Name, retVal, info.Info)
case "float64":
var dv float64
if info.DefaultValue != nil {
dv = info.DefaultValue.(float64)
}
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*float64)
retVal, err := getEnv(info.Alt, dv)
if err != nil {
return err
}
flag.Float64Var(p, info.Name, retVal, info.Info)
}
}
flag.Parse()
return nil
}