package config import ( "fmt" "log" "os" "strings" "github.com/spf13/viper" ) // Config holds all configuration for the application. type Config struct { Database DatabaseConfig `mapstructure:"database"` Server ServerConfig `mapstructure:"server"` } // DatabaseConfig holds database connection configuration. type DatabaseConfig struct { User string `mapstructure:"user"` Password string `mapstructure:"password"` Dbname string `mapstructure:"dbname"` Host string `mapstructure:"host"` Port int `mapstructure:"port"` Sslmode string `mapstructure:"sslmode"` } // ServerConfig holds server-specific configuration. type ServerConfig struct { Address string `mapstructure:"address"` } // Load reads configuration from file and environment variables. func Load() (*Config, error) { // 0. Configuration for environment variables viper.SetEnvPrefix("APP") // Variables must start with "APP_" (e.g., APP_SERVER_ADDRESS) // 1. Base configuration file viper.SetConfigName("config") // config.yaml viper.SetConfigType("yaml") viper.AddConfigPath(".") // Read the base file. Don't stop if it's not found. if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); !ok { return nil, fmt.Errorf("error reading base config file config.yaml: %w", err) } } // 2. Override with environment-specific file (via APP_ENV) if env := os.Getenv("APP_ENV"); env != "" { viper.SetConfigName("config." + env) // e.g., config.prod.yaml if err := viper.MergeInConfig(); err != nil { log.Printf("Warning: could not load config file for environment '%s': %v", env, err) } } viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) viper.AutomaticEnv() var cfg Config if err := viper.Unmarshal(&cfg); err != nil { return nil, fmt.Errorf("unable to decode config into struct: %w", err) } return &cfg, nil }