// Package config owns GL configuration loading and validation. package config import ( "fmt" "time" "github.com/knadh/koanf/parsers/toml" "github.com/knadh/koanf/providers/file" "github.com/knadh/koanf/v2" ) type Config struct { Environment string `koanf:"environment"` GRPC GRPCConfig `koanf:"grpc"` Database DatabaseConfig `koanf:"database"` } type DashboardConfig struct { Environment string `koanf:"environment"` HTTP HTTPConfig `koanf:"http"` Database DatabaseConfig `koanf:"database"` } type GRPCConfig struct { Host string `koanf:"host"` Port int `koanf:"port"` ShutdownTimeout time.Duration `koanf:"shutdown-timeout"` } type HTTPConfig struct { Host string `koanf:"host"` Port int `koanf:"port"` ReadHeaderTimeout time.Duration `koanf:"read-header-timeout"` ShutdownTimeout time.Duration `koanf:"shutdown-timeout"` } type DatabaseConfig struct { Host string `koanf:"host"` Port int `koanf:"port"` Name string `koanf:"name"` User string `koanf:"user"` Password string `koanf:"password"` SSLMode string `koanf:"ssl-mode"` } func Load(path string) (*Config, error) { cfg := &Config{ Environment: "local", GRPC: GRPCConfig{ Host: "0.0.0.0", Port: 8600, ShutdownTimeout: 10 * time.Second, }, Database: defaultDatabaseConfig(), } if err := load(path, cfg); err != nil { return nil, err } if err := cfg.Validate(); err != nil { return nil, err } return cfg, nil } func LoadDashboard(path string) (*DashboardConfig, error) { cfg := &DashboardConfig{ Environment: "local", HTTP: HTTPConfig{ Host: "0.0.0.0", Port: 8080, ReadHeaderTimeout: 5 * time.Second, ShutdownTimeout: 10 * time.Second, }, Database: defaultDatabaseConfig(), } if err := load(path, cfg); err != nil { return nil, err } if err := cfg.Validate(); err != nil { return nil, err } return cfg, nil } func load(path string, target any) error { k := koanf.New(".") if err := k.Load(file.Provider(path), toml.Parser()); err != nil { return fmt.Errorf("load config: %w", err) } if err := k.Unmarshal("", target); err != nil { return fmt.Errorf("decode config: %w", err) } return nil } func defaultDatabaseConfig() DatabaseConfig { return DatabaseConfig{ Host: "127.0.0.1", Port: 5432, Name: "gl_db", User: "postgres", SSLMode: "disable", } } func (c *Config) Validate() error { if c.GRPC.Host == "" { return fmt.Errorf("grpc host is required") } if c.GRPC.Port < 0 || c.GRPC.Port > 65535 { return fmt.Errorf("grpc port must be between 0 and 65535") } if c.GRPC.ShutdownTimeout <= 0 { return fmt.Errorf("grpc shutdown timeout must be positive") } return validateDatabase(c.Database) } func (c *DashboardConfig) Validate() error { if c.HTTP.Host == "" { return fmt.Errorf("http host is required") } if c.HTTP.Port < 0 || c.HTTP.Port > 65535 { return fmt.Errorf("http port must be between 0 and 65535") } if c.HTTP.ReadHeaderTimeout <= 0 || c.HTTP.ShutdownTimeout <= 0 { return fmt.Errorf("http timeouts must be positive") } return validateDatabase(c.Database) } func validateDatabase(database DatabaseConfig) error { if database.Host == "" || database.Name == "" || database.User == "" { return fmt.Errorf("database host, name, and user are required") } if database.Port < 1 || database.Port > 65535 { return fmt.Errorf("database port must be between 1 and 65535") } return nil }