128 lines
3.8 KiB
Go
128 lines
3.8 KiB
Go
package config
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/briefing"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/module"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
func (cfg Config) ReportModuleOverrides() map[report.ID][]module.ConfigItem {
|
|
overrides := map[report.ID][]module.ConfigItem{}
|
|
for key, reportCfg := range cfg.Reports {
|
|
if !reportCfg.deterministicModulesSet {
|
|
continue
|
|
}
|
|
id, err := reportIDForConfigKey(key)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
items := make([]module.ConfigItem, 0, len(reportCfg.DeterministicModules))
|
|
for _, item := range reportCfg.DeterministicModules {
|
|
items = append(items, module.ConfigItem{ID: item.ID, Options: item.Options})
|
|
}
|
|
overrides[id] = items
|
|
}
|
|
return overrides
|
|
}
|
|
|
|
func normalizeReportModules(cfg *Config) error {
|
|
if cfg.Reports == nil {
|
|
cfg.Reports = map[string]ReportConfig{}
|
|
}
|
|
moduleRegistry, err := briefing.DefaultModuleRegistry()
|
|
if err != nil {
|
|
return fmt.Errorf("initialize module registry: %w", err)
|
|
}
|
|
reportRegistry := report.DefaultRegistry()
|
|
seenReports := map[report.ID]string{}
|
|
for key, reportCfg := range cfg.Reports {
|
|
reportID, err := reportIDForConfigKey(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if previous, ok := seenReports[reportID]; ok {
|
|
return fmt.Errorf("reports.%s duplicates report override %q", key, previous)
|
|
}
|
|
seenReports[reportID] = key
|
|
if _, err := reportRegistry.Lookup(reportID); err != nil {
|
|
return fmt.Errorf("reports.%s: %w", key, err)
|
|
}
|
|
if !reportCfg.deterministicModulesSet {
|
|
continue
|
|
}
|
|
items := make([]module.ConfigItem, 0, len(reportCfg.DeterministicModules))
|
|
for i, rawItem := range reportCfg.DeterministicModules {
|
|
options, err := normalizeModuleOptions(moduleRegistry, rawItem.ID, rawItem.Options)
|
|
if err != nil {
|
|
return fmt.Errorf("reports.%s.deterministic_modules[%d]: %w", key, i, err)
|
|
}
|
|
reportCfg.DeterministicModules[i].Options = options
|
|
items = append(items, module.ConfigItem{ID: rawItem.ID, Options: options})
|
|
}
|
|
if err := moduleRegistry.ValidateComposition(reportID, items); err != nil {
|
|
return fmt.Errorf("reports.%s.deterministic_modules: %w", key, err)
|
|
}
|
|
cfg.Reports[key] = reportCfg
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func normalizeModuleOptions(registry briefing.ModuleRegistry, id module.ID, raw any) (any, error) {
|
|
if raw == nil {
|
|
return nil, nil
|
|
}
|
|
definition, err := registry.Lookup(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if definition.DefaultOptions == nil {
|
|
return nil, fmt.Errorf("module %q does not accept options", id)
|
|
}
|
|
optionType := reflect.TypeOf(definition.DefaultOptions)
|
|
normalized, err := decodeKnownOptions(raw, optionType)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("module %q options are invalid: %w", id, err)
|
|
}
|
|
return normalized, nil
|
|
}
|
|
|
|
func decodeKnownOptions(raw any, optionType reflect.Type) (any, error) {
|
|
data, err := yaml.Marshal(raw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
target := reflect.New(optionType)
|
|
decoder := yaml.NewDecoder(bytes.NewReader(data))
|
|
decoder.KnownFields(true)
|
|
if err := decoder.Decode(target.Interface()); err != nil {
|
|
return nil, err
|
|
}
|
|
return target.Elem().Interface(), nil
|
|
}
|
|
|
|
func reportIDForConfigKey(key string) (report.ID, error) {
|
|
normalized := strings.ReplaceAll(strings.TrimSpace(strings.ToLower(key)), "-", "_")
|
|
switch normalized {
|
|
case "daily", "daily_today":
|
|
return report.DailyToday, nil
|
|
case "tomorrow", "daily_tomorrow":
|
|
return report.DailyTomorrow, nil
|
|
case "hourly":
|
|
return report.Hourly, nil
|
|
case "three_day", "three_day_outlook":
|
|
return report.ThreeDay, nil
|
|
case "weekend", "weekend_outlook":
|
|
return report.Weekend, nil
|
|
case "storm", "storm_report":
|
|
return report.Storm, nil
|
|
default:
|
|
return "", fmt.Errorf("reports.%s is not a known report", key)
|
|
}
|
|
}
|