summaryrefslogtreecommitdiff
path: root/config.go
diff options
context:
space:
mode:
authorxengineering <me@xengineering.eu>2025-12-20 14:15:58 +0100
committerxengineering <me@xengineering.eu>2025-12-20 14:15:58 +0100
commitd429f3a7dbe8fc8cc43ebe565b6130b1cfce4ea1 (patch)
treef00d0911850110b983a1648342cc0646c90e9b6c /config.go
parent224d52d1033d8ccce5087c9bee5a63457830a13a (diff)
downloadsia-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.go93
1 files changed, 92 insertions, 1 deletions
diff --git a/config.go b/config.go
index edd6f4b..7d3cab8 100644
--- a/config.go
+++ b/config.go
@@ -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 {