config: add Validate() with startup checks
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Iab753b2bae4fb760159d6459734293d46a6a6964
This commit is contained in:
parent
f504f3114f
commit
d055799f3d
2 changed files with 87 additions and 3 deletions
|
|
@ -2,6 +2,7 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -46,10 +47,10 @@ type ServerConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CacheConfig struct {
|
type CacheConfig struct {
|
||||||
DBPath string `yaml:"db_path"`
|
DBPath string `yaml:"db_path"`
|
||||||
MaxEntries int `yaml:"max_entries"`
|
MaxEntries int `yaml:"max_entries"`
|
||||||
TTL Duration `yaml:"ttl"`
|
TTL Duration `yaml:"ttl"`
|
||||||
LatencyAlpha float64 `yaml:"latency_alpha"`
|
LatencyAlpha float64 `yaml:"latency_alpha"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MeshConfig struct {
|
type MeshConfig struct {
|
||||||
|
|
@ -100,6 +101,37 @@ func defaults() Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validates config fields. Call after Load.
|
||||||
|
func (c *Config) Validate() error {
|
||||||
|
if len(c.Upstreams) == 0 {
|
||||||
|
return fmt.Errorf("at least one upstream is required")
|
||||||
|
}
|
||||||
|
for i, u := range c.Upstreams {
|
||||||
|
if u.URL == "" {
|
||||||
|
return fmt.Errorf("upstream[%d]: URL is empty", i)
|
||||||
|
}
|
||||||
|
if _, err := url.ParseRequestURI(u.URL); err != nil {
|
||||||
|
return fmt.Errorf("upstream[%d]: invalid URL %q: %w", i, u.URL, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.Server.Listen == "" {
|
||||||
|
return fmt.Errorf("server.listen is empty")
|
||||||
|
}
|
||||||
|
if c.Cache.LatencyAlpha <= 0 || c.Cache.LatencyAlpha >= 1 {
|
||||||
|
return fmt.Errorf("cache.latency_alpha must be between 0 and 1 exclusive, got %f", c.Cache.LatencyAlpha)
|
||||||
|
}
|
||||||
|
if c.Cache.TTL.Duration <= 0 {
|
||||||
|
return fmt.Errorf("cache.ttl must be positive")
|
||||||
|
}
|
||||||
|
if c.Cache.MaxEntries <= 0 {
|
||||||
|
return fmt.Errorf("cache.max_entries must be positive")
|
||||||
|
}
|
||||||
|
if c.Mesh.Enabled && len(c.Mesh.Peers) == 0 {
|
||||||
|
return fmt.Errorf("mesh.enabled is true but no peers configured")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Loads config from file (if non-empty) and applies env overrides.
|
// Loads config from file (if non-empty) and applies env overrides.
|
||||||
func Load(path string) (*Config, error) {
|
func Load(path string) (*Config, error) {
|
||||||
cfg := defaults()
|
cfg := defaults()
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,58 @@ mesh:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateValid(t *testing.T) {
|
||||||
|
cfg, _ := config.Load("")
|
||||||
|
if err := cfg.Validate(); err != nil {
|
||||||
|
t.Errorf("default config should be valid: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNoUpstreams(t *testing.T) {
|
||||||
|
cfg, _ := config.Load("")
|
||||||
|
cfg.Upstreams = nil
|
||||||
|
if err := cfg.Validate(); err == nil {
|
||||||
|
t.Error("expected error for no upstreams")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateBadURL(t *testing.T) {
|
||||||
|
cfg, _ := config.Load("")
|
||||||
|
cfg.Upstreams = []config.UpstreamConfig{{URL: "not-a-url"}}
|
||||||
|
if err := cfg.Validate(); err == nil {
|
||||||
|
t.Error("expected error for invalid URL")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateBadAlpha(t *testing.T) {
|
||||||
|
cfg, _ := config.Load("")
|
||||||
|
cfg.Cache.LatencyAlpha = 0
|
||||||
|
if err := cfg.Validate(); err == nil {
|
||||||
|
t.Error("expected error for alpha=0")
|
||||||
|
}
|
||||||
|
cfg.Cache.LatencyAlpha = 1
|
||||||
|
if err := cfg.Validate(); err == nil {
|
||||||
|
t.Error("expected error for alpha=1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNegativeTTL(t *testing.T) {
|
||||||
|
cfg, _ := config.Load("")
|
||||||
|
cfg.Cache.TTL = config.Duration{}
|
||||||
|
if err := cfg.Validate(); err == nil {
|
||||||
|
t.Error("expected error for zero TTL")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateMeshEnabledNoPeers(t *testing.T) {
|
||||||
|
cfg, _ := config.Load("")
|
||||||
|
cfg.Mesh.Enabled = true
|
||||||
|
cfg.Mesh.Peers = nil
|
||||||
|
if err := cfg.Validate(); err == nil {
|
||||||
|
t.Error("expected error for mesh enabled without peers")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestInvalidDuration(t *testing.T) {
|
func TestInvalidDuration(t *testing.T) {
|
||||||
yamlContent := `
|
yamlContent := `
|
||||||
server:
|
server:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue