Add config validation
This commit is contained in:
parent
72c9dac62c
commit
695c9fc353
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
94
config_schema.json
Normal file
94
config_schema.json
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
1
go.mod
1
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
|
||||
)
|
||||
|
|
2
go.sum
2
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=
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue