fix: cnf make cnf/* configuration formats more strict and not to include themselves (to really find out the good format) | add tests | TODO: json is a subset of yaml, create my own YAML parser that is strict to the 'well-known' YAML syntax

This commit is contained in:
Adrien Marquès 2018-11-15 17:26:11 +01:00
parent b26dec8576
commit bf391fd5a0
3 changed files with 276 additions and 22 deletions

View File

@ -16,8 +16,15 @@ type ini struct {
// ReadFrom implements io.ReaderFrom // ReadFrom implements io.ReaderFrom
func (d *ini) ReadFrom(_reader io.Reader) (int64, error) { func (d *ini) ReadFrom(_reader io.Reader) (int64, error) {
// disallow "key: value"
// when trying to find out the format from content
// and avoids conflicts with YAML
opts := lib.LoadOptions{
KeyValueDelimiters: "=",
}
file, err := lib.LoadSources(opts, ioutil.NopCloser(_reader))
// 1. get json decoder // 1. get json decoder
file, err := lib.Load(ioutil.NopCloser(_reader))
if err != nil { if err != nil {
return 0, err return 0, err
} }

View File

@ -2,7 +2,6 @@ package cnf
import ( import (
"errors" "errors"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
) )
@ -13,39 +12,37 @@ var ErrUnknownExtension = errors.New("unknown extension format")
// ErrUnknownFormat is raised when the format cannot be guessed from the content of the file // ErrUnknownFormat is raised when the format cannot be guessed from the content of the file
var ErrUnknownFormat = errors.New("cannot infer format from content") var ErrUnknownFormat = errors.New("cannot infer format from content")
// ErrUnknownFormat is raised when required file does not exist
var ErrFileNotExist = errors.New("cannot find file")
// Load the current file and create the configuration format accordingly // Load the current file and create the configuration format accordingly
func Load(path string) (ConfigurationFormat, error) { func Load(path string) (ConfigurationFormat, error) {
var confFormat ConfigurationFormat
// 1. check file // 1. check file
if _, err := os.Stat(path); os.IsNotExist(err) { if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, fmt.Errorf("cannot find file '%s'", path) return nil, ErrFileNotExist
} }
// 2. Try to load from extension // 2. Try to load from extension
extension := filepath.Ext(path) extension := filepath.Ext(path)
if len(extension) > 0 { if len(extension) > 0 {
confFormat = loadFromExtension(extension) if confFormat := loadFromExtension(extension); confFormat != nil {
if confFormat == nil {
return nil, ErrUnknownExtension
}
// open file // open file
file, err := os.Open(path) file, err := os.Open(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer file.Close()
// parse // parse
_, err = confFormat.ReadFrom(file) _, err = confFormat.ReadFrom(file)
file.Close()
if err == nil { if err == nil {
return confFormat, nil return confFormat, nil
} }
// return nil, fmt.Errorf("cannot parse file as '%s' | %s", extension, err) }
} }
@ -89,7 +86,7 @@ func loadFromContent(path string) (ConfigurationFormat, error) {
defer file.Close() defer file.Close()
// extensions ordered by strictness of the language's syntax // extensions ordered by strictness of the language's syntax
extensions := []string{".json", ".nginx", ".yaml", ".ini"} extensions := []string{".json", ".nginx", ".ini", ".yaml"}
// try to load each available extension // try to load each available extension
for _, ext := range extensions { for _, ext := range extensions {

250
internal/cnf/loader_test.go Normal file
View File

@ -0,0 +1,250 @@
package cnf
import (
"fmt"
"os"
"testing"
)
func TestLoadFileNotExist(t *testing.T) {
_, err := Load("/tmp/not-existing/file")
if err == nil {
t.Fatalf("expected error")
}
if err != ErrFileNotExist {
t.Fatalf("expected error <%s>, got <%s>", ErrFileNotExist, err)
}
}
func TestLoadFromExtensionWithContentMatch(t *testing.T) {
tests := []struct {
File string
Format ConfigurationFormat
Content string
Field string
}{
// key = value
{"/tmp/load-test.json", new(json), `{ "key": "value" }`, "key"},
{"/tmp/load-test.nginx", new(nginx), "key value;", "key"},
{"/tmp/load-test.ini", new(ini), `key = value`, "key"},
{"/tmp/load-test.yaml", new(yaml), "key: value", "key"},
// parent.key = value
{"/tmp/load-test.json", new(json), `{ "parent": { "key": "value" } }`, "parent.key"},
{"/tmp/load-test.nginx", new(nginx), "parent {\nkey value;\n}", "parent.key"},
{"/tmp/load-test.ini", new(ini), "[parent]\nkey = value", "parent.key"},
{"/tmp/load-test.yaml", new(yaml), "parent:\n key: value", "parent.key"},
// comments
// {"/tmp/load-test.json", new(json), "{ \"parent\": { \"key\": \"value\" } }", "parent.key"},
{"/tmp/load-test.nginx", new(nginx), ";comment\nparent {\nkey value;\n}", "parent.key"},
{"/tmp/load-test.nginx", new(nginx), "#comment\nparent {\nkey value;\n}", "parent.key"},
{"/tmp/load-test.ini", new(ini), ";comment\n[parent]\nkey = value", "parent.key"},
{"/tmp/load-test.ini", new(ini), "#comment\n[parent]\nkey = value", "parent.key"},
{"/tmp/load-test.yaml", new(yaml), "#comment\nparent:\n key: value", "parent.key"},
}
for i, test := range tests {
f, err := os.Create(test.File)
if err != nil {
t.Errorf("[%d] cannot create file '%s'", i, test.File)
continue
}
f.Write([]byte(test.Content))
f.Close()
format, err := Load(test.File)
if err != nil {
t.Errorf("[%d] unexpected error <%s>", i, err)
continue
}
if format == nil {
t.Errorf("[%d] expected format not to be null", i)
continue
}
// check type
have, want := fmt.Sprintf("%T", format), fmt.Sprintf("%T", test.Format)
if have != want {
t.Errorf("[%d] expected format <%s>, got <%s>", i, want, have)
continue
}
// try to get value
val, found := format.Get(test.Field)
if !found {
t.Errorf("[%d] expected to find '%s'", i, test.Field)
continue
}
if val != "value" {
t.Errorf("[%d] expected value '%s', got '%s'", i, "value", val)
continue
}
}
for _, test := range tests {
os.RemoveAll(test.File)
}
}
func TestLoadFromExtensionWithContentNotMatch(t *testing.T) {
tests := []struct {
File string
Format ConfigurationFormat
Content string
Field string
}{
// [JSON] key = value
{"/tmp/load-test.json", new(json), `{ "key": "value" }`, "key"},
{"/tmp/load-test.nginx", new(json), `{ "key": "value" }`, "key"},
{"/tmp/load-test.ini", new(json), `{ "key": "value" }`, "key"},
// {"/tmp/load-test.yaml", new(json), `{ "key": "value" }`, "key"},
// [NGINX] key = value
{"/tmp/load-test.json", new(nginx), "key value;", "key"},
{"/tmp/load-test.nginx", new(nginx), "key value;", "key"},
{"/tmp/load-test.ini", new(nginx), "key value;", "key"},
// {"/tmp/load-test.yaml", new(nginx), "key value;", "key"},
// [INI] key = value
{"/tmp/load-test.json", new(ini), `key = value`, "key"},
{"/tmp/load-test.nginx", new(ini), `key = value`, "key"},
{"/tmp/load-test.ini", new(ini), `key = value`, "key"},
// {"/tmp/load-test.yaml", new(ini), `key = value`, "key"},
// [YAML] key = value
{"/tmp/load-test.json", new(yaml), "key: value", "key"},
{"/tmp/load-test.nginx", new(yaml), "key: value", "key"},
{"/tmp/load-test.yaml", new(yaml), "key: value", "key"},
{"/tmp/load-test.yaml", new(yaml), "key: value", "key"},
}
for i, test := range tests {
f, err := os.Create(test.File)
if err != nil {
t.Errorf("[%d] cannot create file '%s'", i, test.File)
continue
}
f.Write([]byte(test.Content))
f.Close()
format, err := Load(test.File)
if err != nil {
t.Errorf("[%d] unexpected error <%s>", i, err)
continue
}
if format == nil {
t.Errorf("[%d] expected format not to be null", i)
continue
}
// check type
have, want := fmt.Sprintf("%T", format), fmt.Sprintf("%T", test.Format)
if have != want {
t.Errorf("[%d] expected format <%s>, got <%s>", i, want, have)
continue
}
// try to get value
val, found := format.Get(test.Field)
if !found {
t.Errorf("[%d] expected to find '%s'", i, test.Field)
continue
}
if val != "value" {
t.Errorf("[%d] expected value '%s', got '%s'", i, "value", val)
continue
}
}
for _, test := range tests {
os.RemoveAll(test.File)
}
}
func TestLoadContent(t *testing.T) {
file := "/tmp/no-extension-file"
defer os.RemoveAll(file)
tests := []struct {
Format ConfigurationFormat
Content string
Field string
}{
// key = value
{new(json), `{ "key": "value" }`, "key"},
{new(nginx), "key value;", "key"},
{new(ini), `key = value`, "key"},
{new(yaml), "key: value", "key"},
// parent.key = value
{new(json), `{ "parent": { "key": "value" } }`, "parent.key"},
{new(nginx), "parent {\nkey value;\n}", "parent.key"},
{new(ini), "[parent]\nkey = value", "parent.key"},
{new(yaml), "parent:\n key: value", "parent.key"},
// comments
{new(json), "{ \"parent\": { \"key\": \"value\" } }", "parent.key"},
{new(nginx), ";comment\nparent {\nkey value;\n}", "parent.key"},
{new(nginx), "#comment\nparent {\nkey value;\n}", "parent.key"},
{new(ini), ";comment\n[parent]\nkey = value", "parent.key"},
{new(ini), "#comment\n[parent]\nkey = value", "parent.key"},
{new(yaml), "#comment\nparent:\n key: value", "parent.key"},
}
for i, test := range tests {
os.RemoveAll(file)
f, err := os.Create(file)
if err != nil {
t.Errorf("[%d] cannot create file '%s'", i, file)
continue
}
f.Write([]byte(test.Content))
f.Close()
format, err := Load(file)
if err != nil {
t.Errorf("[%d] unexpected error <%s>", i, err)
continue
}
if format == nil {
t.Errorf("[%d] expected format not to be null", i)
continue
}
// check type
have, want := fmt.Sprintf("%T", format), fmt.Sprintf("%T", test.Format)
if have != want {
t.Errorf("[%d] expected format <%s>, got <%s>", i, want, have)
continue
}
// try to get value
val, found := format.Get(test.Field)
if !found {
t.Errorf("[%d] expected to find '%s'", i, test.Field)
continue
}
if val != "value" {
t.Errorf("[%d] expected value '%s', got '%s'", i, "value", val)
continue
}
}
}