diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..cdc8434 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +## Unreleased + +- Added License +- Changed the go version to 1.17 +- Added a Loadfiles function to load at once several config files + +## v0.1.0 (2020-03-17) +- Initial varsion + diff --git a/config.go b/config.go index 79ca2c6..f9e5700 100644 --- a/config.go +++ b/config.go @@ -4,6 +4,7 @@ package conf import ( "encoding/json" + "errors" "fmt" "io/ioutil" "os" @@ -16,6 +17,23 @@ func LoadFile(path string, data interface{}) error { return read(path, data) } +// LoadFiles tries to load all the given paths in the given order. +// +// If a path does not exist, it is ignored. +// +// It returns an error only if the content of a file is invalid, i.e. it +// cannot be unmarshaled by json. +func LoadFiles(data interface{}, paths ...string) error { + for _, p := range paths { + err := read(p, data) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("cannot load %q: %w", p, err) + } + } + + return nil +} + // SaveFile writes the given data serialized in JSON in the given path func SaveFile(path string, data interface{}) error { return write(path, data) diff --git a/config_test.go b/config_test.go index 8812b03..c83e34a 100644 --- a/config_test.go +++ b/config_test.go @@ -3,6 +3,7 @@ package conf import ( "io/ioutil" "os" + "path/filepath" "testing" . "github.com/smartystreets/goconvey/convey" @@ -94,6 +95,115 @@ func TestLoadFile(t *testing.T) { }) } +func TestLoadFiles(t *testing.T) { + Convey("Given a config struct", t, func() { + c := &testconf{} + tmpDir := t.TempDir() + + Convey("Given two existing files setting different options", func() { + content1 := []byte(`{"String": "foo"}`) + content2 := []byte(`{"Int": 42}`) + paths := []string{ + filepath.Join(tmpDir, "file1.json"), + filepath.Join(tmpDir, "file2.json"), + } + + err := ioutil.WriteFile(paths[0], content1, 0o600) + So(err, ShouldBeNil) + err = ioutil.WriteFile(paths[1], content2, 0o600) + So(err, ShouldBeNil) + + Convey("When LoadFiles is called", func() { + err := LoadFiles(&c, paths...) + + Convey("Then there is no error", func() { + So(err, ShouldBeNil) + + Convey("And the options from both files have been set", func() { + So(c.String, ShouldEqual, "foo") + So(c.Int, ShouldEqual, 42) + }) + }) + }) + }) + + Convey("Given two existing files setting the same option", func() { + content1 := []byte(`{"String": "foo"}`) + content2 := []byte(`{"String": "bar"}`) + paths := []string{ + filepath.Join(tmpDir, "file1.json"), + filepath.Join(tmpDir, "file2.json"), + } + + err := ioutil.WriteFile(paths[0], content1, 0o600) + So(err, ShouldBeNil) + err = ioutil.WriteFile(paths[1], content2, 0o600) + So(err, ShouldBeNil) + + Convey("When LoadFiles is called", func() { + err := LoadFiles(&c, paths...) + + Convey("Then there is no error", func() { + So(err, ShouldBeNil) + + Convey("And the last file overwrote the first", func() { + So(c.String, ShouldEqual, "bar") + }) + }) + }) + }) + + Convey("Given one non-existing and one existing files", func() { + content2 := []byte(`{"String": "bar"}`) + paths := []string{ + "does-not-exist.json", + filepath.Join(tmpDir, "file2.json"), + } + + err := ioutil.WriteFile(paths[1], content2, 0o600) + So(err, ShouldBeNil) + + Convey("When LoadFiles is called", func() { + err := LoadFiles(&c, paths...) + + Convey("Then there is no error", func() { + So(err, ShouldBeNil) + + Convey("And the options from the last file have been set", func() { + So(c.String, ShouldEqual, "bar") + }) + }) + }) + }) + + Convey("Given one invalid and one valid files", func() { + content1 := []byte(`{"`) + content2 := []byte(`{"String": "bar"}`) + paths := []string{ + filepath.Join(tmpDir, "file1.json"), + filepath.Join(tmpDir, "file2.json"), + } + + err := ioutil.WriteFile(paths[0], content1, 0o600) + So(err, ShouldBeNil) + err = ioutil.WriteFile(paths[1], content2, 0o600) + So(err, ShouldBeNil) + + Convey("When LoadFiles is called", func() { + err := LoadFiles(&c, paths...) + + Convey("Then an error is returned", func() { + So(err, ShouldBeError) + + Convey("And the last file has not been read", func() { + So(c.String, ShouldNotEqual, "bar") + }) + }) + }) + }) + }) +} + func TestSaveFile(t *testing.T) { Convey("Given a config struct", t, func() { c := testconf{ diff --git a/go.mod b/go.mod index e1da6b2..4589416 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,11 @@ module code.bcarlin.xyz/go/conf -go 1.14 +go 1.17 + +require github.com/smartystreets/goconvey v1.7.2 + +require ( + github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect + github.com/jtolds/gls v4.20.0+incompatible // indirect + github.com/smartystreets/assertions v1.2.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..234fe39 --- /dev/null +++ b/go.sum @@ -0,0 +1,13 @@ +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= +github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=