package config import ( "fmt" "time" "github.com/spf13/viper" "git.warky.dev/wdevs/pgsql-broker/pkg/broker/adapter" ) // Config holds all broker configuration type Config struct { Databases []DatabaseConfig `mapstructure:"databases"` Broker BrokerConfig `mapstructure:"broker"` Logging LoggingConfig `mapstructure:"logging"` } // DatabaseConfig holds database connection settings type DatabaseConfig struct { Name string `mapstructure:"name"` Host string `mapstructure:"host"` Port int `mapstructure:"port"` Database string `mapstructure:"database"` User string `mapstructure:"user"` Password string `mapstructure:"password"` SSLMode string `mapstructure:"sslmode"` MaxOpenConns int `mapstructure:"max_open_conns"` MaxIdleConns int `mapstructure:"max_idle_conns"` ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"` ConnMaxIdleTime time.Duration `mapstructure:"conn_max_idle_time"` QueueCount int `mapstructure:"queue_count"` } // BrokerConfig holds broker-specific settings type BrokerConfig struct { Name string `mapstructure:"name"` FetchQueryQueSize int `mapstructure:"fetch_query_que_size"` QueueTimerSec int `mapstructure:"queue_timer_sec"` QueueBufferSize int `mapstructure:"queue_buffer_size"` WorkerIdleTimeoutSec int `mapstructure:"worker_idle_timeout_sec"` NotifyRetrySeconds time.Duration `mapstructure:"notify_retry_seconds"` EnableDebug bool `mapstructure:"enable_debug"` } // LoggingConfig holds logging settings type LoggingConfig struct { Level string `mapstructure:"level"` Format string `mapstructure:"format"` // json or text } // LoadConfig loads configuration from file and environment variables func LoadConfig(configPath string) (*Config, error) { v := viper.New() // Set defaults setDefaults(v) // Config file settings if configPath != "" { v.SetConfigFile(configPath) } else { v.SetConfigName("broker") v.SetConfigType("yaml") v.AddConfigPath(".") v.AddConfigPath("/etc/pgsql-broker/") v.AddConfigPath("$HOME/.pgsql-broker/") } // Read from environment variables v.SetEnvPrefix("BROKER") v.AutomaticEnv() // Read config file if err := v.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); !ok { return nil, fmt.Errorf("failed to read config file: %w", err) } } var config Config if err := v.Unmarshal(&config); err != nil { return nil, fmt.Errorf("failed to unmarshal config: %w", err) } // Validate configuration if err := validateConfig(&config); err != nil { return nil, err } // Apply defaults to databases applyDatabaseDefaults(&config) return &config, nil } // setDefaults sets default configuration values func setDefaults(v *viper.Viper) { // Broker defaults v.SetDefault("broker.name", "pgsql-broker") v.SetDefault("broker.fetch_query_que_size", 100) v.SetDefault("broker.queue_timer_sec", 10) v.SetDefault("broker.queue_buffer_size", 50) v.SetDefault("broker.worker_idle_timeout_sec", 10) v.SetDefault("broker.notify_retry_seconds", 30*time.Second) v.SetDefault("broker.enable_debug", false) // Logging defaults v.SetDefault("logging.level", "info") v.SetDefault("logging.format", "json") } // validateConfig validates the configuration func validateConfig(config *Config) error { if len(config.Databases) == 0 { return fmt.Errorf("at least one database must be configured") } // Validate each database configuration for i, db := range config.Databases { if db.Name == "" { return fmt.Errorf("database[%d]: name is required", i) } if db.Host == "" { return fmt.Errorf("database[%d] (%s): host is required", i, db.Name) } if db.Database == "" { return fmt.Errorf("database[%d] (%s): database name is required", i, db.Name) } if db.User == "" { return fmt.Errorf("database[%d] (%s): user is required", i, db.Name) } } return nil } // applyDatabaseDefaults applies default values to database configurations func applyDatabaseDefaults(config *Config) { for i := range config.Databases { db := &config.Databases[i] if db.Port == 0 { db.Port = 5432 } if db.SSLMode == "" { db.SSLMode = "disable" } if db.MaxOpenConns == 0 { db.MaxOpenConns = 25 } if db.MaxIdleConns == 0 { db.MaxIdleConns = 5 } if db.ConnMaxLifetime == 0 { db.ConnMaxLifetime = 5 * time.Minute } if db.ConnMaxIdleTime == 0 { db.ConnMaxIdleTime = 10 * time.Minute } if db.QueueCount == 0 { db.QueueCount = 4 } } } // ToPostgresConfig converts DatabaseConfig to adapter.PostgresConfig func (d *DatabaseConfig) ToPostgresConfig() adapter.PostgresConfig { return adapter.PostgresConfig{ Host: d.Host, Port: d.Port, Database: d.Database, User: d.User, Password: d.Password, SSLMode: d.SSLMode, MaxOpenConns: d.MaxOpenConns, MaxIdleConns: d.MaxIdleConns, ConnMaxLifetime: d.ConnMaxLifetime, ConnMaxIdleTime: d.ConnMaxIdleTime, } }