package config import ( "bytes" "fmt" "os" "time" "unicode/utf8" "github.com/pelletier/go-toml/v2" ) // ClientOptions controls platform-specific static validation. Production uses // CheckFilesystem; documentation tests may perform syntax-only validation for // a non-native target. type ClientOptions struct { Platform Platform CheckFilesystem bool } func LoadServer(path string) (*Server, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read server config: %w", err) } return DecodeServer(data) } func DecodeServer(data []byte) (*Server, error) { return DecodeServerWithOverrides(data, nil) } // DecodeServerWithOverrides applies only explicitly present flag values after // TOML decoding. Keys come from the single registry in overrides.go. func DecodeServerWithOverrides(data []byte, overrides map[string]string) (*Server, error) { var raw = defaultServerFile() if err := strictDecode(data, &raw); err != nil { return nil, fmt.Errorf("decode server config: %w", err) } if err := applyServerOverrides(&raw, overrides); err != nil { return nil, err } return validateServer(raw) } func LoadClient(path string, platform Platform) (*Client, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read client config: %w", err) } return DecodeClient(data, ClientOptions{Platform: platform, CheckFilesystem: true}) } func DecodeClient(data []byte, options ClientOptions) (*Client, error) { return DecodeClientWithOverrides(data, options, nil) } // DecodeClientWithOverrides applies explicitly present flags after the file. func DecodeClientWithOverrides(data []byte, options ClientOptions, overrides map[string]string) (*Client, error) { var raw = defaultClientFile() if err := strictDecode(data, &raw); err != nil { return nil, fmt.Errorf("decode client config: %w", err) } if err := applyClientOverrides(&raw, overrides); err != nil { return nil, err } return validateClient(raw, options) } func strictDecode(data []byte, target any) error { if !utf8.Valid(data) { return fmt.Errorf("configuration is not valid UTF-8") } decoder := toml.NewDecoder(bytes.NewReader(data)) decoder.DisallowUnknownFields() if err := decoder.Decode(target); err != nil { return err } return nil } func parseDuration(name, value string, allowZero bool) (time.Duration, error) { duration, err := time.ParseDuration(value) if err != nil { return 0, fmt.Errorf("%s: %w", name, err) } if duration < 0 || (!allowZero && duration == 0) { return 0, fmt.Errorf("%s must be %s", name, map[bool]string{true: "non-negative", false: "positive"}[allowZero]) } return duration, nil }