Add configuration and CLI foundation
This commit is contained in:
124
internal/config/validate.go
Normal file
124
internal/config/validate.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Validate(cfg Config) error {
|
||||
if cfg.WeatherAPI.BaseURL != "" {
|
||||
parsed, err := url.Parse(cfg.WeatherAPI.BaseURL)
|
||||
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
|
||||
return fmt.Errorf("weather_api.base_url must be an absolute URL")
|
||||
}
|
||||
}
|
||||
if cfg.WeatherAPI.Timeout <= 0 {
|
||||
return fmt.Errorf("weather_api.timeout must be greater than zero")
|
||||
}
|
||||
if cfg.WeatherAPI.Precision < 0 {
|
||||
return fmt.Errorf("weather_api.precision must be zero or greater")
|
||||
}
|
||||
if cfg.WeatherAPI.Units == "" {
|
||||
return fmt.Errorf("weather_api.units is required")
|
||||
}
|
||||
if cfg.WeatherAPI.Timezone == "" {
|
||||
return fmt.Errorf("weather_api.timezone is required")
|
||||
}
|
||||
if _, err := loadLocation(cfg.WeatherAPI.Timezone); err != nil {
|
||||
return fmt.Errorf("weather_api.timezone %q is invalid: %w", cfg.WeatherAPI.Timezone, err)
|
||||
}
|
||||
if cfg.WeatherAPI.Format == "" {
|
||||
return fmt.Errorf("weather_api.format is required")
|
||||
}
|
||||
if cfg.WeatherAPI.Format != "json" {
|
||||
return fmt.Errorf("weather_api.format must be json")
|
||||
}
|
||||
|
||||
if err := validatePolicy("missing_source.default", cfg.MissingSource.Default); err != nil {
|
||||
return err
|
||||
}
|
||||
for source, policy := range cfg.MissingSource.Sources {
|
||||
if strings.TrimSpace(source) == "" {
|
||||
return fmt.Errorf("missing_source.sources contains an empty source name")
|
||||
}
|
||||
if err := validatePolicy("missing_source.sources."+source, policy); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.Scriptorium.Binary == "" {
|
||||
return fmt.Errorf("scriptorium.binary is required")
|
||||
}
|
||||
if cfg.Scriptorium.Timeout <= 0 {
|
||||
return fmt.Errorf("scriptorium.timeout must be greater than zero")
|
||||
}
|
||||
if cfg.Workspace.Root == "" {
|
||||
return fmt.Errorf("workspace.root is required")
|
||||
}
|
||||
if cfg.Reports.OutputDir == "" {
|
||||
return fmt.Errorf("reports.output_dir is required")
|
||||
}
|
||||
if len(cfg.Dayparts) == 0 {
|
||||
return fmt.Errorf("dayparts must contain at least one entry")
|
||||
}
|
||||
for i, daypart := range cfg.Dayparts {
|
||||
if strings.TrimSpace(daypart.Name) == "" {
|
||||
return fmt.Errorf("dayparts[%d].name is required", i)
|
||||
}
|
||||
if err := validateClockTime(daypart.Start); err != nil {
|
||||
return fmt.Errorf("dayparts[%d].start is invalid: %w", i, err)
|
||||
}
|
||||
if err := validateClockTime(daypart.End); err != nil {
|
||||
return fmt.Errorf("dayparts[%d].end is invalid: %w", i, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadLocation(name string) (*time.Location, error) {
|
||||
location, err := time.LoadLocation(name)
|
||||
if err == nil {
|
||||
return location, nil
|
||||
}
|
||||
if strings.Contains(name, "/") {
|
||||
return nil, err
|
||||
}
|
||||
return time.LoadLocation("America/" + name)
|
||||
}
|
||||
|
||||
func validatePolicy(name string, policy MissingSourcePolicy) error {
|
||||
switch policy {
|
||||
case MissingSourceError, MissingSourceWarn, MissingSourceNone:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("%s must be one of error, warn, or none", name)
|
||||
}
|
||||
}
|
||||
|
||||
func validateClockTime(value string) error {
|
||||
parts := strings.Split(value, ":")
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("expected HH:MM")
|
||||
}
|
||||
hour, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid hour")
|
||||
}
|
||||
minute, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid minute")
|
||||
}
|
||||
if hour < 0 || hour > 24 {
|
||||
return fmt.Errorf("hour must be between 00 and 24")
|
||||
}
|
||||
if minute < 0 || minute > 59 {
|
||||
return fmt.Errorf("minute must be between 00 and 59")
|
||||
}
|
||||
if hour == 24 && minute != 0 {
|
||||
return fmt.Errorf("24 is only valid as 24:00")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user