236 lines
6.5 KiB
Go
236 lines
6.5 KiB
Go
// 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)
|
|
}
|