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:
parent
b26dec8576
commit
bf391fd5a0
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue