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 bool } 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 := strconv.ParseBool((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 }