57c29dc58f
- Removed YAML configuration files for the scraper and web commands, transitioning to an environment-based configuration system. - Updated configuration structs to utilize `env` tags for loading values from `.env` files, enhancing security and flexibility. - Implemented reflection-based parsing for environment variables, ensuring type-safe configuration loading. - Revised documentation to reflect the new configuration approach, including usage examples and best practices for environment variable naming conventions.
216 lines
5.4 KiB
Go
216 lines
5.4 KiB
Go
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
|
|
}
|