package config import ( "fmt" "os" "reflect" "strconv" "strings" ) func ReadEnv(c *Config, prefix string) (namesAvailable, namesUsed []string) { return structFromEnv(reflect.ValueOf(c), reflect.TypeOf(c), prefix) } func structFromEnv(v reflect.Value, t reflect.Type, prefix string) (namesAvailable, namesUsed []string) { if v.Kind() == reflect.Ptr { v = v.Elem() t = t.Elem() } for i := 0; i < v.NumField(); i++ { fv := v.Field(i) ft := t.Field(i) tag := ft.Tag.Get("env") if !ft.IsExported() || tag == "-" { continue } name := ft.Name if tag != "" { name = tag } if fv.Kind() == reflect.Struct { na, nu := structFromEnv(fv, ft.Type, prefix+"_"+name) namesAvailable = append(namesAvailable, na...) namesUsed = append(namesUsed, nu...) } else if !ft.Anonymous { envName := strings.ToUpper(prefix + "_" + name) namesAvailable = append(namesAvailable, envName) val, ok := os.LookupEnv(envName) if !ok { continue } if !fv.CanSet() { panic("cannot set field for environment variable " + envName) } switch fk := fv.Kind(); fk { case reflect.String: fv.SetString(val) case reflect.Int: x, err := strconv.ParseInt(val, 10, 0) if err != nil { panic(err) } fv.SetInt(x) case reflect.Bool: switch strings.ToLower(val) { case "1", "true", "t", "on": fv.SetBool(true) case "0", "false", "f", "off": fv.SetBool(false) default: panic(fmt.Sprintf("cannot set field for environment variable %s: cannot parse %s as boolean. Please use one of: 1, true, t, on / 0, false, f, off", envName, fk)) } default: panic(fmt.Sprintf("cannot set field for environment variable %s: type %s is not supported", envName, fk)) } namesUsed = append(namesUsed, envName) } } return namesAvailable, namesUsed }