aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbodqhrohro <bodqhrohro@gmail.com>2019-10-25 21:12:38 +0300
committerbodqhrohro <bodqhrohro@gmail.com>2019-10-25 21:12:38 +0300
commit695c9fc35325d3bec3ec81bdce59f780acd74e8d (patch)
tree91754643ae5f9cdde3d6e04af5239a3a7ccababa
parent72c9dac62cb6282841d22d877852bcee26bff9dd (diff)
Add config validation
-rw-r--r--config/config.go84
-rw-r--r--config/config_test.go12
-rw-r--r--config_schema.json94
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--telegabber.go3
6 files changed, 190 insertions, 6 deletions
diff --git a/config/config.go b/config/config.go
index 2ec6706..cd2883b 100644
--- a/config/config.go
+++ b/config/config.go
@@ -1,9 +1,11 @@
package config
import (
+ "fmt"
"github.com/pkg/errors"
"io/ioutil"
+ "github.com/santhosh-tekuri/jsonschema"
"gopkg.in/yaml.v2"
)
@@ -47,7 +49,7 @@ type TelegramTdlibClientConfig struct {
UseChatInfoDatabase bool `yaml:":use_chat_info_database"`
}
-func ReadConfig(path string) (Config, error) {
+func ReadConfig(path string, schema_path string) (Config, error) {
var config Config
file, err := ioutil.ReadFile(path)
@@ -60,5 +62,85 @@ func ReadConfig(path string) (Config, error) {
return config, errors.Wrap(err, "Error parsing config")
}
+ err = validateConfig(file, schema_path)
+ if err != nil {
+ return config, errors.Wrap(err, "Validation error")
+ }
+
return config, nil
}
+
+func validateConfig(file []byte, schema_path string) error {
+ schema, err := jsonschema.Compile(schema_path)
+ if err != nil {
+ return errors.Wrap(err, "Corrupted JSON schema")
+ }
+
+ var config_generic interface{}
+
+ err = yaml.Unmarshal(file, &config_generic)
+ if err != nil {
+ return errors.Wrap(err, "Error re-parsing config")
+ }
+
+ config_generic, err = convertToStringKeysRecursive(config_generic, "")
+ if err != nil {
+ return errors.Wrap(err, "Config conversion error")
+ }
+
+ err = schema.ValidateInterface(config_generic)
+ if err != nil {
+ return errors.Wrap(err, "Config validation error")
+ }
+
+ return nil
+}
+
+// copied and adapted from https://github.com/docker/docker-ce/blob/de14285fad39e215ea9763b8b404a37686811b3f/components/cli/cli/compose/loader/loader.go#L330
+func convertToStringKeysRecursive(value interface{}, keyPrefix string) (interface{}, error) {
+ if mapping, ok := value.(map[interface{}]interface{}); ok {
+ dict := make(map[string]interface{})
+ for key, entry := range mapping {
+ str, ok := key.(string)
+ if !ok {
+ return nil, formatInvalidKeyError(keyPrefix, key)
+ }
+ var newKeyPrefix string
+ if keyPrefix == "" {
+ newKeyPrefix = str
+ } else {
+ newKeyPrefix = fmt.Sprintf("%s.%s", keyPrefix, str)
+ }
+ convertedEntry, err := convertToStringKeysRecursive(entry, newKeyPrefix)
+ if err != nil {
+ return nil, err
+ }
+ dict[str] = convertedEntry
+ }
+ return dict, nil
+ }
+ if list, ok := value.([]interface{}); ok {
+ var convertedList []interface{}
+ for index, entry := range list {
+ newKeyPrefix := fmt.Sprintf("%s[%d]", keyPrefix, index)
+ convertedEntry, err := convertToStringKeysRecursive(entry, newKeyPrefix)
+ if err != nil {
+ return nil, err
+ }
+ convertedList = append(convertedList, convertedEntry)
+ }
+ return convertedList, nil
+ }
+
+ return value, nil
+}
+
+func formatInvalidKeyError(keyPrefix string, key interface{}) error {
+ var location string
+ if keyPrefix == "" {
+ location = "at top level"
+ } else {
+ location = fmt.Sprintf("in %s", keyPrefix)
+ }
+ return errors.Errorf("Non-string key %s: %#v", location, key)
+}
diff --git a/config/config_test.go b/config/config_test.go
index 34009d7..6fce23b 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -4,23 +4,27 @@ import (
"testing"
)
+const SCHEMA_PATH string = "../config_schema.json"
+
func TestNoConfig(t *testing.T) {
- _, err := ReadConfig("../test/sfklase.yml")
+ _, err := ReadConfig("../test/sfklase.yml", SCHEMA_PATH)
if err == nil {
t.Errorf("Non-existent config was successfully read")
}
}
func TestGoodConfig(t *testing.T) {
- _, err := ReadConfig("../test/good_config.yml")
+ _, err := ReadConfig("../test/good_config.yml", SCHEMA_PATH)
if err != nil {
t.Errorf("Good config is not accepted: %v", err)
}
}
func TestBadConfig(t *testing.T) {
- _, err := ReadConfig("../test/bad_config.yml")
+ _, err := ReadConfig("../test/bad_config.yml", SCHEMA_PATH)
if err == nil {
- t.Errorf("Bad config is accepted but it shoudn't!")
+ t.Errorf("Bad config is accepted but it shouldn't!")
+ } else {
+ t.Log(err)
}
}
diff --git a/config_schema.json b/config_schema.json
new file mode 100644
index 0000000..434bde5
--- /dev/null
+++ b/config_schema.json
@@ -0,0 +1,94 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type": "object",
+ "required": [":telegram", ":xmpp"],
+ "properties": {
+ ":telegram": {
+ "type": "object",
+ "required": [":loglevel", ":content", ":tdlib"],
+ "properties": {
+ ":loglevel": {
+ "$ref": "#/definitions/non-empty-string"
+ },
+ ":content": {
+ "type": "object",
+ "properties": {
+ ":path": {
+ "type": "string"
+ },
+ ":link": {
+ "type": "string"
+ },
+ ":upload": {
+ "type": "string"
+ }
+ }
+ },
+ ":tdlib_verbosity": {
+ "type": "integer"
+ },
+ ":tdlib": {
+ "required": [":lib_path", ":client"],
+ "type": "object",
+ "properties": {
+ ":lib_path": {
+ "$ref": "#/definitions/non-empty-string"
+ },
+ ":client": {
+ "type": "object",
+ "required": [":api_id", ":api_hash"],
+ "properties": {
+ ":api_id": {
+ "$ref": "#/definitions/non-empty-string"
+ },
+ ":api_hash": {
+ "$ref": "#/definitions/non-empty-string"
+ },
+ ":device_model": {
+ "type": "string"
+ },
+ ":application_version": {
+ "type": "string"
+ },
+ ":use_chat_info_database": {
+ "type": "boolean"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ ":xmpp": {
+ "type": "object",
+ "required": [":loglevel", ":jid", ":host", ":port", ":password", ":db"],
+ "properties": {
+ ":loglevel": {
+ "$ref": "#/definitions/non-empty-string"
+ },
+ ":jid": {
+ "$ref": "#/definitions/non-empty-string"
+ },
+ ":host": {
+ "$ref": "#/definitions/non-empty-string"
+ },
+ ":port": {
+ "type": "integer",
+ "minimum": 1
+ },
+ ":password": {
+ "$ref": "#/definitions/non-empty-string"
+ },
+ ":db": {
+ "$ref": "#/definitions/non-empty-string"
+ }
+ }
+ }
+ },
+ "definitions": {
+ "non-empty-string": {
+ "type": "string",
+ "minLength": 1
+ }
+ }
+}
diff --git a/go.mod b/go.mod
index 0193f42..c6f2878 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.13
require (
github.com/pkg/errors v0.8.1
+ github.com/santhosh-tekuri/jsonschema v1.2.4
gopkg.in/yaml.v2 v2.2.4
gosrc.io/xmpp v0.1.3
)
diff --git a/go.sum b/go.sum
index e273364..4734020 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,8 @@
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis=
+github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/telegabber.go b/telegabber.go
index 6c17c91..d924822 100644
--- a/telegabber.go
+++ b/telegabber.go
@@ -8,9 +8,10 @@ import (
)
const CONFIG_PATH string = "config.yml"
+const SCHEMA_PATH string = "./config_schema.json"
func main() {
- config, err := config.ReadConfig(CONFIG_PATH)
+ config, err := config.ReadConfig(CONFIG_PATH, SCHEMA_PATH)
if err != nil {
log.Fatal(err)
}