diff --git a/internal/cnf/ini.go b/internal/cnf/ini.go index 4955025..2a2ea6a 100644 --- a/internal/cnf/ini.go +++ b/internal/cnf/ini.go @@ -16,8 +16,15 @@ type ini struct { // ReadFrom implements io.ReaderFrom 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 - file, err := lib.Load(ioutil.NopCloser(_reader)) if err != nil { return 0, err } diff --git a/internal/cnf/loader.go b/internal/cnf/loader.go index e13de07..10a4aae 100644 --- a/internal/cnf/loader.go +++ b/internal/cnf/loader.go @@ -2,7 +2,6 @@ package cnf import ( "errors" - "fmt" "os" "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 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 func Load(path string) (ConfigurationFormat, error) { - var confFormat ConfigurationFormat - // 1. check file 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 extension := filepath.Ext(path) if len(extension) > 0 { - confFormat = loadFromExtension(extension) - if confFormat == nil { - return nil, ErrUnknownExtension - } + if confFormat := loadFromExtension(extension); confFormat != nil { - // open file - file, err := os.Open(path) - if err != nil { - return nil, err - } - defer file.Close() + // open file + file, err := os.Open(path) + if err != nil { + return nil, err + } - // parse - _, err = confFormat.ReadFrom(file) - if err == nil { - return confFormat, nil - } + // parse + _, err = confFormat.ReadFrom(file) + file.Close() + if err == 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() // 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 for _, ext := range extensions { diff --git a/internal/cnf/loader_test.go b/internal/cnf/loader_test.go new file mode 100644 index 0000000..c1c4c74 --- /dev/null +++ b/internal/cnf/loader_test.go @@ -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 + } + + } + +}