package config import ( "fmt" "strings" "github.com/spf13/viper" ) // Manager handles configuration loading from multiple sources type Manager struct { v *viper.Viper } // NewManager creates a new configuration manager with defaults func NewManager() *Manager { v := viper.New() // Set configuration file settings v.SetConfigName("config") v.SetConfigType("yaml") v.AddConfigPath(".") v.AddConfigPath("./config") v.AddConfigPath("/etc/resolvespec") v.AddConfigPath("$HOME/.resolvespec") // Enable environment variable support v.SetEnvPrefix("RESOLVESPEC") v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) v.AutomaticEnv() // Set default values setDefaults(v) return &Manager{v: v} } // NewManagerWithOptions creates a new configuration manager with custom options func NewManagerWithOptions(opts ...Option) *Manager { m := NewManager() for _, opt := range opts { opt(m) } return m } // Option is a functional option for configuring the Manager type Option func(*Manager) // WithConfigFile sets a specific config file path func WithConfigFile(path string) Option { return func(m *Manager) { m.v.SetConfigFile(path) } } // WithConfigName sets the config file name (without extension) func WithConfigName(name string) Option { return func(m *Manager) { m.v.SetConfigName(name) } } // WithConfigPath adds a path to search for config files func WithConfigPath(path string) Option { return func(m *Manager) { m.v.AddConfigPath(path) } } // WithEnvPrefix sets the environment variable prefix func WithEnvPrefix(prefix string) Option { return func(m *Manager) { m.v.SetEnvPrefix(prefix) } } // Load attempts to load configuration from file and environment func (m *Manager) Load() error { // Try to read config file (not an error if it doesn't exist) if err := m.v.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); !ok { return fmt.Errorf("error reading config file: %w", err) } // Config file not found; will rely on defaults and env vars } return nil } // GetConfig returns the complete configuration func (m *Manager) GetConfig() (*Config, error) { var cfg Config if err := m.v.Unmarshal(&cfg); err != nil { return nil, fmt.Errorf("failed to unmarshal config: %w", err) } return &cfg, nil } // SetConfig sets the complete configuration func (m *Manager) SetConfig(cfg *Config) error { configMap := make(map[string]interface{}) // Marshal the config to a map structure that viper can use if err := m.v.Unmarshal(&configMap); err != nil { return fmt.Errorf("failed to prepare config map: %w", err) } // Use viper's merge to apply the config m.v.Set("servers", cfg.Servers) m.v.Set("tracing", cfg.Tracing) m.v.Set("cache", cfg.Cache) m.v.Set("logger", cfg.Logger) m.v.Set("error_tracking", cfg.ErrorTracking) m.v.Set("middleware", cfg.Middleware) m.v.Set("cors", cfg.CORS) m.v.Set("event_broker", cfg.EventBroker) m.v.Set("dbmanager", cfg.DBManager) m.v.Set("paths", cfg.Paths) m.v.Set("extensions", cfg.Extensions) return nil } // Get returns a configuration value by key func (m *Manager) Get(key string) interface{} { return m.v.Get(key) } // GetString returns a string configuration value func (m *Manager) GetString(key string) string { return m.v.GetString(key) } // GetInt returns an int configuration value func (m *Manager) GetInt(key string) int { return m.v.GetInt(key) } // GetBool returns a bool configuration value func (m *Manager) GetBool(key string) bool { return m.v.GetBool(key) } // Set sets a configuration value func (m *Manager) Set(key string, value interface{}) { m.v.Set(key, value) } // SaveConfig writes the current configuration to the specified path func (m *Manager) SaveConfig(path string) error { if err := m.v.WriteConfigAs(path); err != nil { return fmt.Errorf("failed to save config to %s: %w", path, err) } return nil } // setDefaults sets default configuration values func setDefaults(v *viper.Viper) { // Server defaults - new structure v.SetDefault("servers.default_server", "default") // Global server timeout defaults v.SetDefault("servers.shutdown_timeout", "30s") v.SetDefault("servers.drain_timeout", "25s") v.SetDefault("servers.read_timeout", "10s") v.SetDefault("servers.write_timeout", "10s") v.SetDefault("servers.idle_timeout", "120s") // Default server instance v.SetDefault("servers.instances.default.name", "default") v.SetDefault("servers.instances.default.host", "") v.SetDefault("servers.instances.default.port", 8080) v.SetDefault("servers.instances.default.description", "Default HTTP server") v.SetDefault("servers.instances.default.gzip", false) // Tracing defaults v.SetDefault("tracing.enabled", false) v.SetDefault("tracing.service_name", "resolvespec") v.SetDefault("tracing.service_version", "1.0.0") v.SetDefault("tracing.endpoint", "") // Cache defaults v.SetDefault("cache.provider", "memory") v.SetDefault("cache.redis.host", "localhost") v.SetDefault("cache.redis.port", 6379) v.SetDefault("cache.redis.password", "") v.SetDefault("cache.redis.db", 0) v.SetDefault("cache.memcache.servers", []string{"localhost:11211"}) v.SetDefault("cache.memcache.max_idle_conns", 10) v.SetDefault("cache.memcache.timeout", "100ms") // Logger defaults v.SetDefault("logger.dev", false) v.SetDefault("logger.path", "") // Middleware defaults v.SetDefault("middleware.rate_limit_rps", 100.0) v.SetDefault("middleware.rate_limit_burst", 200) v.SetDefault("middleware.max_request_size", 10485760) // 10MB // CORS defaults v.SetDefault("cors.allowed_origins", []string{"*"}) v.SetDefault("cors.allowed_methods", []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}) v.SetDefault("cors.allowed_headers", []string{"*"}) v.SetDefault("cors.max_age", 3600) // Database defaults v.SetDefault("database.url", "") // Database Manager defaults v.SetDefault("dbmanager.default_connection", "default") v.SetDefault("dbmanager.max_open_conns", 25) v.SetDefault("dbmanager.max_idle_conns", 5) v.SetDefault("dbmanager.conn_max_lifetime", "30m") v.SetDefault("dbmanager.conn_max_idle_time", "5m") v.SetDefault("dbmanager.retry_attempts", 3) v.SetDefault("dbmanager.retry_delay", "1s") v.SetDefault("dbmanager.retry_max_delay", "10s") v.SetDefault("dbmanager.health_check_interval", "30s") v.SetDefault("dbmanager.enable_auto_reconnect", true) // Default PostgreSQL connection v.SetDefault("dbmanager.connections.default.name", "default") v.SetDefault("dbmanager.connections.default.type", "postgres") v.SetDefault("dbmanager.connections.default.host", "localhost") v.SetDefault("dbmanager.connections.default.port", 5432) v.SetDefault("dbmanager.connections.default.user", "postgres") v.SetDefault("dbmanager.connections.default.password", "") v.SetDefault("dbmanager.connections.default.database", "resolvespec") v.SetDefault("dbmanager.connections.default.sslmode", "disable") v.SetDefault("dbmanager.connections.default.connect_timeout", "10s") v.SetDefault("dbmanager.connections.default.query_timeout", "30s") v.SetDefault("dbmanager.connections.default.enable_tracing", false) v.SetDefault("dbmanager.connections.default.enable_metrics", false) v.SetDefault("dbmanager.connections.default.enable_logging", false) v.SetDefault("dbmanager.connections.default.default_orm", "bun") // Event Broker defaults v.SetDefault("event_broker.enabled", false) v.SetDefault("event_broker.provider", "memory") v.SetDefault("event_broker.mode", "async") v.SetDefault("event_broker.worker_count", 10) v.SetDefault("event_broker.buffer_size", 1000) v.SetDefault("event_broker.instance_id", "") // Event Broker - Redis defaults v.SetDefault("event_broker.redis.stream_name", "resolvespec:events") v.SetDefault("event_broker.redis.consumer_group", "resolvespec-workers") v.SetDefault("event_broker.redis.max_len", 10000) v.SetDefault("event_broker.redis.host", "localhost") v.SetDefault("event_broker.redis.port", 6379) v.SetDefault("event_broker.redis.password", "") v.SetDefault("event_broker.redis.db", 0) // Event Broker - NATS defaults v.SetDefault("event_broker.nats.url", "nats://localhost:4222") v.SetDefault("event_broker.nats.stream_name", "RESOLVESPEC_EVENTS") v.SetDefault("event_broker.nats.subjects", []string{"events.>"}) v.SetDefault("event_broker.nats.storage", "file") v.SetDefault("event_broker.nats.max_age", "24h") // Event Broker - Database defaults v.SetDefault("event_broker.database.table_name", "events") v.SetDefault("event_broker.database.channel", "resolvespec_events") v.SetDefault("event_broker.database.poll_interval", "1s") // Event Broker - Retry Policy defaults v.SetDefault("event_broker.retry_policy.max_retries", 3) v.SetDefault("event_broker.retry_policy.initial_delay", "1s") v.SetDefault("event_broker.retry_policy.max_delay", "30s") v.SetDefault("event_broker.retry_policy.backoff_factor", 2.0) // Paths defaults (common directory paths) v.SetDefault("paths.data_dir", "./data") v.SetDefault("paths.config_dir", "./config") v.SetDefault("paths.logs_dir", "./logs") v.SetDefault("paths.temp_dir", "./tmp") // Extensions defaults (empty map) v.SetDefault("extensions", map[string]interface{}{}) }