// 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 GRPCConfig struct { Host string `koanf:"host"` Port int `koanf:"port"` 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: 8500, ShutdownTimeout: 10 * time.Second, }, Database: DatabaseConfig{ Host: "127.0.0.1", Port: 5432, Name: "gl_db", User: "postgres", SSLMode: "disable", }, } k := koanf.New(".") if err := k.Load(file.Provider(path), toml.Parser()); err != nil { return nil, fmt.Errorf("load config: %w", err) } if err := k.Unmarshal("", cfg); err != nil { return nil, fmt.Errorf("decode config: %w", err) } if err := cfg.Validate(); err != nil { return nil, err } return cfg, nil } 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") } if c.Database.Host == "" || c.Database.Name == "" || c.Database.User == "" { return fmt.Errorf("database host, name, and user are required") } if c.Database.Port < 1 || c.Database.Port > 65535 { return fmt.Errorf("database port must be between 1 and 65535") } return nil }