package config import ( "fmt" "os" "reflect" "strconv" "time" "github.com/subosito/gotenv" ) type ServerConfig struct { Host string `env:"SERVER_HOST"` Port int `env:"SERVER_PORT"` Timeout time.Duration `env:"SERVER_TIMEOUT"` ReadTimeout time.Duration `env:"SERVER_READ_TIMEOUT"` WriteTimeout time.Duration `env:"SERVER_WRITE_TIMEOUT"` } type DatabaseConfig struct { MongoDB MongoConfig } type MongoConfig struct { URI string `env:"MONGODB_URI"` Name string `env:"MONGODB_NAME"` Timeout time.Duration `env:"MONGODB_TIMEOUT"` MaxPoolSize int `env:"MONGODB_MAX_POOL_SIZE"` } type CacheConfig struct { Redis RedisConfig } type RedisConfig struct { Host string `env:"REDIS_HOST"` Port int `env:"REDIS_PORT"` Password string `env:"REDIS_PASSWORD"` DB int `env:"REDIS_DB"` PoolSize int `env:"REDIS_POOL_SIZE"` } type LoggingConfig struct { Level string `env:"LOG_LEVEL"` Format string `env:"LOG_FORMAT"` Output string `env:"LOG_OUTPUT"` File LogFileConfig } type LogFileConfig struct { Path string `env:"LOG_FILE_PATH"` MaxSize int `env:"LOG_FILE_MAX_SIZE"` MaxBackups int `env:"LOG_FILE_MAX_BACKUPS"` MaxAge int `env:"LOG_FILE_MAX_AGE"` Compress bool `env:"LOG_FILE_COMPRESS"` } type AssetsConfig struct { FlagsPath string `env:"ASSETS_FLAGS_PATH"` } type RateLimitConfig struct { RequestsPerMinute int `env:"RATE_LIMIT_REQUESTS_PER_MINUTE"` Burst int `env:"RATE_LIMIT_BURST"` } // LoadConfig loads configuration from .env file func LoadConfig[T any](path string, config T) (T, error) { // Load .env file envPath := fmt.Sprintf("%s/.env", path) if err := gotenv.Load(envPath); err != nil { return config, fmt.Errorf("failed to load .env file from %s: %w", envPath, err) } // Parse configuration using reflection if err := parseConfig(config); err != nil { return config, fmt.Errorf("failed to parse configuration: %w", err) } return config, nil } // parseConfig uses reflection to parse environment variables into the config struct func parseConfig(config interface{}) error { v := reflect.ValueOf(config) if v.Kind() != reflect.Ptr { return fmt.Errorf("config must be a pointer") } v = v.Elem() if v.Kind() != reflect.Struct { return fmt.Errorf("config must be a struct") } return parseStruct(v) } // parseStruct recursively parses struct fields func parseStruct(v reflect.Value) error { t := v.Type() for i := 0; i < v.NumField(); i++ { field := v.Field(i) fieldType := t.Field(i) // Skip unexported fields if !field.CanSet() { continue } // Get env tag envTag := fieldType.Tag.Get("env") if envTag == "" { // If no env tag, try to parse nested struct if field.Kind() == reflect.Struct { if err := parseStruct(field); err != nil { return err } } continue } // Get environment variable value envValue := os.Getenv(envTag) if envValue == "" { continue } // Parse and set the field value if err := setFieldValue(field, envValue); err != nil { return fmt.Errorf("failed to set field %s: %w", fieldType.Name, err) } } return nil } // setFieldValue sets the field value from environment variable string func setFieldValue(field reflect.Value, value string) error { switch field.Kind() { case reflect.String: field.SetString(value) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if field.Type() == reflect.TypeOf(time.Duration(0)) { // Handle time.Duration duration, err := time.ParseDuration(value) if err != nil { return fmt.Errorf("invalid duration format: %s", value) } field.Set(reflect.ValueOf(duration)) } else { // Handle regular integers intValue, err := strconv.ParseInt(value, 10, 64) if err != nil { return fmt.Errorf("invalid integer format: %s", value) } field.SetInt(intValue) } case reflect.Bool: boolValue, err := strconv.ParseBool(value) if err != nil { return fmt.Errorf("invalid boolean format: %s", value) } field.SetBool(boolValue) case reflect.Float32, reflect.Float64: floatValue, err := strconv.ParseFloat(value, 64) if err != nil { return fmt.Errorf("invalid float format: %s", value) } field.SetFloat(floatValue) default: return fmt.Errorf("unsupported field type: %s", field.Kind()) } return nil } // Helper function to get environment variable with default value func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } // Helper function to get environment variable as int with default value func getEnvAsInt(key string, defaultValue int) int { if value := os.Getenv(key); value != "" { if intValue, err := strconv.Atoi(value); err == nil { return intValue } } return defaultValue } // Helper function to get environment variable as duration with default value func getEnvAsDuration(key string, defaultValue time.Duration) time.Duration { if value := os.Getenv(key); value != "" { if duration, err := time.ParseDuration(value); err == nil { return duration } } return defaultValue } // Helper function to get environment variable as bool with default value func getEnvAsBool(key string, defaultValue bool) bool { if value := os.Getenv(key); value != "" { if boolValue, err := strconv.ParseBool(value); err == nil { return boolValue } } return defaultValue }