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 | |
| 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.
| -rw-r--r-- | config.go | 93 | ||||
| -rw-r--r-- | config_test.go | 5 |
2 files changed, 97 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 { diff --git a/config_test.go b/config_test.go index c568a34..0972d5d 100644 --- a/config_test.go +++ b/config_test.go @@ -11,4 +11,9 @@ func TestDefaultConfig(t *testing.T) { if err != nil { t.Fatalf("Failed parsing default config from JSON: %v", err) } + + err = config.Validate() + if err != nil { + t.Fatalf("Failed to validate default config: %v", err) + } } |
