// Package config provides configuration loading using Viper. // // This package standardizes configuration loading across all services with: // - Environment variable support // - .env file support for development // - Sensible defaults // - Type-safe configuration structs // // Usage: // // // Initialize configuration (call once at startup) // config.MustInit(config.Options{ // AppName: "my-service", // DefaultPort: 8080, // }) // // // Read configuration // appCfg := config.ReadAppConfig() // serverCfg := config.ReadServerConfig() // // // Or read specific values // dbURL := viper.GetString("DATABASE_URL") package config import ( "fmt" "time" "github.com/spf13/viper" ) // ServerConfig holds HTTP server configuration. type ServerConfig struct { Host string `json:"host"` Port int `json:"port"` ReadTimeout time.Duration `json:"read_timeout"` WriteTimeout time.Duration `json:"write_timeout"` IdleTimeout time.Duration `json:"idle_timeout"` } // Addr returns the server address in host:port format. func (c ServerConfig) Addr() string { return fmt.Sprintf("%s:%d", c.Host, c.Port) } // DatabaseConfig holds database connection configuration. type DatabaseConfig struct { URL string `json:"url"` MaxOpenConns int `json:"max_open_conns"` MaxIdleConns int `json:"max_idle_conns"` ConnMaxLifetime time.Duration `json:"conn_max_lifetime"` } // AppConfig holds application-level configuration common to all services. type AppConfig struct { Name string `json:"name"` Environment string `json:"environment"` Debug bool `json:"debug"` } // IsDevelopment returns true if the environment is development. func (c AppConfig) IsDevelopment() bool { return c.Environment == "development" } // IsProduction returns true if the environment is production. func (c AppConfig) IsProduction() bool { return c.Environment == "production" } // LoggingConfig holds logging configuration. type LoggingConfig struct { Level string `json:"level"` Format string `json:"format"` } // Options configures the behavior of Init. type Options struct { // AppName is the default name for the application. AppName string // DefaultPort is the default server port if not specified. DefaultPort int // EnvFile is the path to the .env file for development. // Defaults to ".env" if not specified. EnvFile string // SetDefaults is an optional function to set additional viper defaults // before loading configuration. SetDefaults func() // SkipEnvFile skips loading from .env file. // Useful for production where all config comes from environment. SkipEnvFile bool } // Init initializes viper with common defaults and loads configuration. // This should be called once at service startup before using viper.Get* functions. // // Load order (later sources override earlier): // 1. Default values // 2. .env file (development only) // 3. Environment variables func Init(opts Options) error { viper.SetConfigType("env") viper.SetEnvPrefix("") viper.AutomaticEnv() // Set common defaults setCommonDefaults(opts) // Set service-specific defaults if opts.SetDefaults != nil { opts.SetDefaults() } // In development, optionally load from .env file if !opts.SkipEnvFile { env := viper.GetString("APP_ENVIRONMENT") if env == "development" || env == "" { envFile := opts.EnvFile if envFile == "" { envFile = ".env" } viper.SetConfigFile(envFile) _ = viper.ReadInConfig() // Ignore error - fallback to env vars } } return nil } // MustInit is like Init but panics if initialization fails. // This is useful in main() where you want to fail fast on configuration errors. func MustInit(opts Options) { if err := Init(opts); err != nil { panic(fmt.Sprintf("failed to initialize config: %v", err)) } } // setCommonDefaults sets default values for common configuration fields. func setCommonDefaults(opts Options) { // App defaults appName := opts.AppName if appName == "" { appName = "service" } viper.SetDefault("APP_NAME", appName) viper.SetDefault("APP_ENVIRONMENT", "development") viper.SetDefault("APP_DEBUG", false) // Server defaults viper.SetDefault("SERVER_HOST", "0.0.0.0") port := opts.DefaultPort if port == 0 { port = 8080 } viper.SetDefault("SERVER_PORT", port) viper.SetDefault("SERVER_READ_TIMEOUT", "30s") viper.SetDefault("SERVER_WRITE_TIMEOUT", "0s") // Disabled for SSE support viper.SetDefault("SERVER_IDLE_TIMEOUT", "120s") // Database defaults viper.SetDefault("DATABASE_MAX_OPEN_CONNS", 25) viper.SetDefault("DATABASE_MAX_IDLE_CONNS", 5) viper.SetDefault("DATABASE_CONN_MAX_LIFETIME", "5m") // Logging defaults viper.SetDefault("LOG_LEVEL", "info") viper.SetDefault("LOG_FORMAT", "auto") // auto = JSON in prod, text in dev } // ReadAppConfig reads AppConfig from viper. func ReadAppConfig() AppConfig { return AppConfig{ Name: viper.GetString("APP_NAME"), Environment: viper.GetString("APP_ENVIRONMENT"), Debug: viper.GetBool("APP_DEBUG"), } } // ReadServerConfig reads ServerConfig from viper. func ReadServerConfig() ServerConfig { return ServerConfig{ Host: viper.GetString("SERVER_HOST"), Port: viper.GetInt("SERVER_PORT"), ReadTimeout: viper.GetDuration("SERVER_READ_TIMEOUT"), WriteTimeout: viper.GetDuration("SERVER_WRITE_TIMEOUT"), IdleTimeout: viper.GetDuration("SERVER_IDLE_TIMEOUT"), } } // ReadDatabaseConfig reads DatabaseConfig from viper. func ReadDatabaseConfig() DatabaseConfig { return DatabaseConfig{ URL: viper.GetString("DATABASE_URL"), MaxOpenConns: viper.GetInt("DATABASE_MAX_OPEN_CONNS"), MaxIdleConns: viper.GetInt("DATABASE_MAX_IDLE_CONNS"), ConnMaxLifetime: viper.GetDuration("DATABASE_CONN_MAX_LIFETIME"), } } // ReadLoggingConfig reads LoggingConfig from viper. func ReadLoggingConfig() LoggingConfig { return LoggingConfig{ Level: viper.GetString("LOG_LEVEL"), Format: viper.GetString("LOG_FORMAT"), } } // GetString returns a string configuration value. func GetString(key string) string { return viper.GetString(key) } // GetInt returns an integer configuration value. func GetInt(key string) int { return viper.GetInt(key) } // GetBool returns a boolean configuration value. func GetBool(key string) bool { return viper.GetBool(key) } // GetDuration returns a duration configuration value. func GetDuration(key string) time.Duration { return viper.GetDuration(key) } // IsSet returns true if the key is set in configuration. func IsSet(key string) bool { return viper.IsSet(key) }