package config import ( "errors" "fmt" "os" "reflect" "strconv" "strings" ) type AppConfig struct { Port string `env:"GUARD_PORT" default:"3001"` Host string `env:"GUARD_HOST" default:"127.0.0.1"` Uri string `env:"GUARD_URI" default:"http://127.0.0.1:3001"` DatabaseURL string `env:"GUARD_DB_URL" required:"true"` Admin AdminConfig Jwt JwtConfig Minio MinioConfig } func LoadEnv(target any) error { v := reflect.ValueOf(target) if v.Kind() != reflect.Pointer || v.Elem().Kind() != reflect.Struct { return &InvalidTargetError{} } return loadStruct(v.Elem(), "") } type InvalidTargetError struct{} func (e *InvalidTargetError) Error() string { return "target must be a pointer to a struct" } var ErrMissingRequiredEnv = errors.New("missing required environment variable") func loadStruct(v reflect.Value, prefix string) error { t := v.Type() for i := 0; i < t.NumField(); i++ { field := t.Field(i) valueField := v.Field(i) if !valueField.CanSet() { continue } envKey := field.Tag.Get("env") if envKey == "" { envKey = strings.ToUpper(prefix + "_" + field.Name) envKey = strings.TrimPrefix(envKey, "_") } required := field.Tag.Get("required") == "true" defaultVal := field.Tag.Get("default") if field.Type.Kind() == reflect.Struct { err := loadStruct(valueField, envKey) if err != nil { return err } continue } envVal := os.Getenv(envKey) if envVal == "" { if defaultVal != "" { envVal = defaultVal } else if required { return fmt.Errorf("%w: %s", ErrMissingRequiredEnv, envKey) } else { continue } } switch field.Type.Kind() { case reflect.String: valueField.SetString(envVal) case reflect.Int, reflect.Int64: i, err := strconv.ParseInt(envVal, 10, 64) if err != nil { return fmt.Errorf("invalid int for %s: %w", envKey, err) } valueField.SetInt(i) case reflect.Bool: b, err := strconv.ParseBool(envVal) if err != nil { return fmt.Errorf("invalid bool for %s: %w", envKey, err) } valueField.SetBool(b) default: return fmt.Errorf("unsupported type for field %s", field.Name) } } return nil }