diff options
| author | xengineering <me@xengineering.eu> | 2025-12-20 14:15:58 +0100 |
|---|---|---|
| committer | xengineering <me@xengineering.eu> | 2025-12-20 14:15:58 +0100 |
| commit | d429f3a7dbe8fc8cc43ebe565b6130b1cfce4ea1 (patch) | |
| tree | f00d0911850110b983a1648342cc0646c90e9b6c /config.go | |
| parent | 224d52d1033d8ccce5087c9bee5a63457830a13a (diff) | |
| download | sia-server-d429f3a7dbe8fc8cc43ebe565b6130b1cfce4ea1.tar sia-server-d429f3a7dbe8fc8cc43ebe565b6130b1cfce4ea1.tar.zst sia-server-d429f3a7dbe8fc8cc43ebe565b6130b1cfce4ea1.zip | |
Add StartupConfiguration.Validate()
This method makes it easy to validate a configuration.
A call of it is now embedded into the StartupConfiguration.FromJSON()
method which should always be the lowest level function to parse
configurations.
Thus configurations can usually be trusted.
Diffstat (limited to 'config.go')
| -rw-r--r-- | config.go | 93 |
1 files changed, 92 insertions, 1 deletions
@@ -3,9 +3,33 @@ package main import ( _ "embed" "encoding/json" + "fmt" "log" + "net" + "net/url" + "regexp" + "strconv" + "time" ) +const ( + MQTT_BROKER_REGEX = `^tcp://127\.0\.0\.1:\d+$` + MQTT_CLIENT_ID_REGEX = `^[0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]{1,23}$` + MQTT_TOPIC_PREFIX_REGEX = `^[a-zA-Z0-9]{1,20}$` +) + +var ( + mqttBrokerRegexp *regexp.Regexp + mqttClientIDRegexp *regexp.Regexp + mqttTopicPrefixRegexp *regexp.Regexp +) + +func init() { + mqttBrokerRegexp = regexp.MustCompile(MQTT_BROKER_REGEX) + mqttClientIDRegexp = regexp.MustCompile(MQTT_CLIENT_ID_REGEX) + mqttTopicPrefixRegexp = regexp.MustCompile(MQTT_TOPIC_PREFIX_REGEX) +} + //go:embed configs/default.json var defaultConfig []byte @@ -25,8 +49,75 @@ type StartupConfig struct { Homematic HomematicConfig `json:"homematic"` } +func (sc StartupConfig) Validate() error { + if !mqttBrokerRegexp.MatchString(sc.MQTT.Broker) { + return fmt.Errorf( + "mqtt/broker configuration '%s' does not match regular expression '%s'.", + sc.MQTT.Broker, + MQTT_BROKER_REGEX, + ) + } + + if !mqttClientIDRegexp.MatchString(sc.MQTT.ClientID) { + return fmt.Errorf( + "mqtt/client-id configuration '%s' does not match regular expression '%s'.", + sc.MQTT.ClientID, + MQTT_CLIENT_ID_REGEX, + ) + } + + if !mqttTopicPrefixRegexp.MatchString(sc.MQTT.TopicPrefix) { + return fmt.Errorf( + "mqtt/topic-prefix configuration '%s' does not match regular expression '%s'.", + sc.MQTT.TopicPrefix, + MQTT_TOPIC_PREFIX_REGEX, + ) + } + + cu, err := url.Parse(sc.Homematic.CCU) + if err != nil { + return fmt.Errorf("homematic/ccu configuration '%s' cannot be parsed as URL: %v", sc.Homematic.CCU, err) + } + + if cu.Scheme != `http` { + return fmt.Errorf("homematic/ccu configuration '%s' must use scheme 'http'.", sc.Homematic.CCU) + } + + host, portstring, err := net.SplitHostPort(cu.Host) + if err != nil { + return fmt.Errorf("homematic/ccu configuration '%s' can't be split into hostname and port: %v", sc.Homematic.CCU, err) + } + + ip := net.ParseIP(host) + if ip == nil { + return fmt.Errorf("homematic/ccu configuration '%s' must use a plain IP address as host.", sc.Homematic.CCU) + } + + _, err = strconv.Atoi(portstring) + if err != nil { + return fmt.Errorf("homematic/ccu configuration '%s' must use a numeric port: %v", sc.Homematic.CCU, err) + } + + _, err = time.ParseDuration(sc.Homematic.PollingPeriod) + if err != nil { + return fmt.Errorf("homematic/polling-period configuration '%s' could not be parsed to duration: %v", sc.Homematic.PollingPeriod, err) + } + + return nil +} + func (sc *StartupConfig) FromJSON(data []byte) error { - return json.Unmarshal(data, sc) + err := json.Unmarshal(data, sc) + if err != nil { + return fmt.Errorf("Failed to unmarshal configuration: %w", err) + } + + err = sc.Validate() + if err != nil { + return fmt.Errorf("Could not validate configuration: %w", err) + } + + return nil } func GetStartupConfig() StartupConfig { |
