From dd86f0537b72f3c3528cf1a5a2d11fc9dbb94304 Mon Sep 17 00:00:00 2001 From: ktong Date: Sat, 11 Nov 2023 19:34:05 -0800 Subject: [PATCH] split file and fs provider --- .github/codecov.yml | 4 ++ .github/dependabot.yml | 5 ++ .github/workflows/benchmark.yml | 3 + .github/workflows/coverage.yml | 3 + .github/workflows/lint.yml | 4 ++ .github/workflows/test.yml | 6 ++ example_test.go | 26 +------- go.mod | 6 +- go.sum | 4 -- provider/file/benchmark_test.go | 17 +----- provider/file/file.go | 30 ++++----- provider/file/file_test.go | 76 ++--------------------- provider/file/go.mod | 14 +++++ provider/file/go.sum | 14 +++++ provider/file/option.go | 36 ++--------- provider/file/watch.go | 2 - provider/file/watch_unsupported.go | 18 ------ provider/fs/benchmark_test.go | 54 ++++++++++++++++ provider/fs/fs.go | 69 +++++++++++++++++++++ provider/fs/fs_test.go | 98 ++++++++++++++++++++++++++++++ provider/fs/option.go | 25 ++++++++ provider/fs/testdata/config.json | 6 ++ 22 files changed, 331 insertions(+), 189 deletions(-) create mode 100644 provider/file/go.mod create mode 100644 provider/file/go.sum delete mode 100644 provider/file/watch_unsupported.go create mode 100644 provider/fs/benchmark_test.go create mode 100644 provider/fs/fs.go create mode 100644 provider/fs/fs_test.go create mode 100644 provider/fs/option.go create mode 100644 provider/fs/testdata/config.json diff --git a/.github/codecov.yml b/.github/codecov.yml index e7f9b85c..4e300f3c 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -20,6 +20,10 @@ component_management: - component_id: konf paths: - "!provider/pflag/" + - "!provider/file/" + - component_id: file + paths: + - "provider/file/" - component_id: pflag paths: - "provider/pflag/" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index db878d51..efa7f49d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,6 +6,11 @@ updates: schedule: interval: daily + - package-ecosystem: gomod + directory: /provider/file + schedule: + interval: daily + - package-ecosystem: gomod directory: /provider/pflag schedule: diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 13ffb561..c0727f86 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -18,6 +18,9 @@ jobs: go-version: 'stable' - name: Benchmark run: go test -v -shuffle=on -bench=. ./... + - name: Benchmark (file) + run: go test -v -shuffle=on -bench=. ./... + working-directory: provider/file - name: Benchmark (pflag) run: go test -v -shuffle=on -bench=. ./... working-directory: provider/pflag diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index eb5db6e9..513d4f56 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -18,6 +18,9 @@ jobs: go-version: 'stable' - name: Coverage run: go test -v -covermode=count -coverprofile=coverage.txt ./... + - name: Coverage (file) + run: go test -v -covermode=count -coverprofile=coverage.txt ./... + working-directory: provider/file - name: Coverage (pflag) run: go test -v -covermode=count -coverprofile=coverage.txt ./... working-directory: provider/pflag diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 123a8791..49ab3c00 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,6 +18,10 @@ jobs: go-version: 'stable' - name: Lint uses: golangci/golangci-lint-action@v3 + - name: Lint (file) + uses: golangci/golangci-lint-action@v3 + with: + working-directory: provider/file - name: Lint (pflag) uses: golangci/golangci-lint-action@v3 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6efdb8f4..57a27f3b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,6 +24,12 @@ jobs: run: go test -v -shuffle=on -count=10 -race ./... - name: Test run: go test -v -shuffle=on ./... + - name: Race Test (file) + run: go test -v -shuffle=on -count=10 -race ./... + working-directory: provider/file + - name: Test (file) + run: go test -v -shuffle=on ./... + working-directory: provider/file - name: Race Test (pflag) run: go test -v -shuffle=on -count=10 -race ./... working-directory: provider/pflag diff --git a/example_test.go b/example_test.go index 50826be0..85c918c0 100644 --- a/example_test.go +++ b/example_test.go @@ -4,15 +4,12 @@ package konf_test import ( - "context" "embed" "fmt" - "sync" - "time" "github.com/ktong/konf" "github.com/ktong/konf/provider/env" - "github.com/ktong/konf/provider/file" + pfs "github.com/ktong/konf/provider/fs" ) func ExampleGet() { @@ -41,32 +38,13 @@ func ExampleUnmarshal() { // Output: example.com:8080 } -func ExampleWatch() { - ExampleSetGlobal() - - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - - var waitGroup sync.WaitGroup - waitGroup.Add(1) - go func() { - if err := konf.Watch(ctx, func() { - fmt.Print(konf.Get[string]("server.host")) - }); err != nil { - panic(err) - } - }() - waitGroup.Wait() - // Output: -} - //go:embed testdata var testdata embed.FS func ExampleSetGlobal() { cfg, err := konf.New( konf.WithLoader( - file.New("testdata/config.json", file.WithFS(testdata)), + pfs.New(testdata, "testdata/config.json"), env.New(env.WithPrefix("server")), ), ) diff --git a/go.mod b/go.mod index 549c7ce7..648fe635 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,7 @@ module github.com/ktong/konf go 1.21 -require ( - github.com/fsnotify/fsnotify v1.7.0 - github.com/mitchellh/mapstructure v1.5.0 -) +require github.com/mitchellh/mapstructure v1.5.0 require ( // for test github.com/stretchr/testify v1.8.4 @@ -16,6 +13,5 @@ require ( // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.14.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 7f51b824..ae162242 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -15,8 +13,6 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/provider/file/benchmark_test.go b/provider/file/benchmark_test.go index 19c0d8a6..af7ca917 100644 --- a/provider/file/benchmark_test.go +++ b/provider/file/benchmark_test.go @@ -5,7 +5,6 @@ package file_test import ( "testing" - "testing/fstest" "github.com/stretchr/testify/require" @@ -13,16 +12,9 @@ import ( ) func BenchmarkNew(b *testing.B) { - mapFS := fstest.MapFS{ - "config.json": { - Data: []byte(`{"k":"v"}`), - }, - } - b.ResetTimer() - var loader file.File for i := 0; i < b.N; i++ { - loader = file.New("config.json", file.WithFS(mapFS)) + loader = file.New("testdata/config.json") } b.StopTimer() @@ -32,12 +24,7 @@ func BenchmarkNew(b *testing.B) { } func BenchmarkLoad(b *testing.B) { - fs := fstest.MapFS{ - "config.json": { - Data: []byte(`{"k":"v"}`), - }, - } - loader := file.New("config.json", file.WithFS(fs)) + loader := file.New("testdata/config.json") b.ResetTimer() var ( diff --git a/provider/file/file.go b/provider/file/file.go index f3616983..9ac171e3 100644 --- a/provider/file/file.go +++ b/provider/file/file.go @@ -15,16 +15,14 @@ package file import ( + "encoding/json" "fmt" - "io/fs" "log/slog" "os" ) // File is a Provider that loads configuration from file. type File struct { - _ [0]func() // Ensure it's incomparable. - fs fs.FS path string unmarshal func([]byte, any) error ignoreNotExist bool @@ -32,19 +30,19 @@ type File struct { // New returns a File with the given path and Option(s). func New(path string, opts ...Option) File { - return File(apply(path, opts)) + option := &options{ + path: path, + unmarshal: json.Unmarshal, + } + for _, opt := range opts { + opt(option) + } + + return File(*option) } func (f File) Load() (map[string]any, error) { - var ( - bytes []byte - err error - ) - if f.fs == nil { - bytes, err = os.ReadFile(f.path) - } else { - bytes, err = fs.ReadFile(f.fs, f.path) - } + bytes, err := os.ReadFile(f.path) if err != nil { if f.ignoreNotExist && os.IsNotExist(err) { slog.Warn("Config file does not exist.", "file", f.path) @@ -64,9 +62,5 @@ func (f File) Load() (map[string]any, error) { } func (f File) String() string { - if f.fs == nil { - return "os file:" + f.path - } - - return "fs file:" + f.path + return "file:" + f.path } diff --git a/provider/file/file_test.go b/provider/file/file_test.go index 5731e198..fc2abb58 100644 --- a/provider/file/file_test.go +++ b/provider/file/file_test.go @@ -8,26 +8,18 @@ package file_test import ( "context" "errors" - "io/fs" "os" "path/filepath" "strings" "sync" "testing" - "testing/fstest" "time" "github.com/stretchr/testify/require" - "github.com/ktong/konf" "github.com/ktong/konf/provider/file" ) -var ( - _ konf.Loader = (*file.File)(nil) - _ konf.Watcher = (*file.File)(nil) -) - func TestFile_Load(t *testing.T) { t.Parallel() @@ -39,7 +31,7 @@ func TestFile_Load(t *testing.T) { err string }{ { - description: "os file", + description: "file", path: "testdata/config.json", expected: map[string]any{ "p": map[string]any{ @@ -48,49 +40,16 @@ func TestFile_Load(t *testing.T) { }, }, { - description: "os file (not exist)", + description: "file (not exist)", path: "not_found.json", err: "read file: open not_found.json: ", }, { - description: "os file (ignore not exist)", + description: "file (ignore not exist)", path: "not_found.json", opts: []file.Option{file.IgnoreFileNotExit()}, expected: map[string]any{}, }, - { - description: "fs file", - path: "config.json", - opts: []file.Option{ - file.WithFS(fstest.MapFS{ - "config.json": { - Data: []byte(`{"p":{"k":"v"}}`), - }, - }), - }, - expected: map[string]any{ - "p": map[string]any{ - "k": "v", - }, - }, - }, - { - description: "fs file (not exist)", - path: "not_found.json", - opts: []file.Option{ - file.WithFS(fstest.MapFS{}), - }, - err: "read file: open not_found.json: file does not exist", - }, - { - description: "fs file (ignore not exist)", - path: "not_found.json", - opts: []file.Option{ - file.WithFS(fstest.MapFS{}), - file.IgnoreFileNotExit(), - }, - expected: map[string]any{}, - }, { description: "unmarshal error", path: "testdata/config.json", @@ -179,32 +138,5 @@ func TestFile_Watch(t *testing.T) { func TestFile_String(t *testing.T) { t.Parallel() - testcases := []struct { - description string - path string - fs fs.FS - expected string - }{ - { - description: "fs file", - path: "config.json", - fs: fstest.MapFS{}, - expected: "fs file:config.json", - }, - { - description: "os file", - path: "config.json", - expected: "os file:config.json", - }, - } - - for i := range testcases { - testcase := testcases[i] - - t.Run(testcase.description, func(t *testing.T) { - t.Parallel() - - require.Equal(t, testcase.expected, file.New(testcase.path, file.WithFS(testcase.fs)).String()) - }) - } + require.Equal(t, "file:config.json", file.New("config.json").String()) } diff --git a/provider/file/go.mod b/provider/file/go.mod new file mode 100644 index 00000000..9d36ad4b --- /dev/null +++ b/provider/file/go.mod @@ -0,0 +1,14 @@ +module github.com/ktong/konf/provider/file + +go 1.21 + +require github.com/fsnotify/fsnotify v1.7.0 + +require github.com/stretchr/testify v1.8.4 + +require ( // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.14.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/provider/file/go.sum b/provider/file/go.sum new file mode 100644 index 00000000..68a314b5 --- /dev/null +++ b/provider/file/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/provider/file/option.go b/provider/file/option.go index a87da02f..cf9c2504 100644 --- a/provider/file/option.go +++ b/provider/file/option.go @@ -3,49 +3,23 @@ package file -import ( - "encoding/json" - "io/fs" -) - -// WithFS provides the fs.FS that config file is loaded from. -// -// The default file system is OS file system. -func WithFS(fs fs.FS) Option { - return func(file *options) { - file.fs = fs - } -} - // WithUnmarshal provides the function that parses config file. // // The default function is json.Unmarshal. func WithUnmarshal(unmarshal func([]byte, any) error) Option { - return func(file *options) { - file.unmarshal = unmarshal + return func(options *options) { + options.unmarshal = unmarshal } } // IgnoreFileNotExit ignores the error if config file does not exist. func IgnoreFileNotExit() Option { - return func(file *options) { - file.ignoreNotExist = true + return func(options *options) { + options.ignoreNotExist = true } } // Option configures the given File. -type Option func(file *options) +type Option func(options *options) type options File - -func apply(path string, opts []Option) options { - option := &options{ - path: path, - unmarshal: json.Unmarshal, - } - for _, opt := range opts { - opt(option) - } - - return *option -} diff --git a/provider/file/watch.go b/provider/file/watch.go index 541c7420..a01aa518 100644 --- a/provider/file/watch.go +++ b/provider/file/watch.go @@ -1,8 +1,6 @@ // Copyright (c) 2023 The konf authors // Use of this source code is governed by a MIT license found in the LICENSE file. -//go:build darwin || dragonfly || freebsd || openbsd || linux || netbsd || solaris || windows - package file import ( diff --git a/provider/file/watch_unsupported.go b/provider/file/watch_unsupported.go deleted file mode 100644 index c72ef6b0..00000000 --- a/provider/file/watch_unsupported.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2023 The konf authors -// Use of this source code is governed by a MIT license found in the LICENSE file. - -//go:build appengine || !(darwin || dragonfly || freebsd || openbsd || linux || netbsd || solaris || windows) - -package file - -import ( - "context" - "log/slog" - "runtime" -) - -func (f File) Watch(context.Context, func(map[string]any)) error { - slog.Warn("File.Watch does not supported on runtime.", "runtime", runtime.GOOS) - - return nil -} diff --git a/provider/fs/benchmark_test.go b/provider/fs/benchmark_test.go new file mode 100644 index 00000000..877c8994 --- /dev/null +++ b/provider/fs/benchmark_test.go @@ -0,0 +1,54 @@ +// Copyright (c) 2023 The konf authors +// Use of this source code is governed by a MIT license found in the LICENSE file. + +package fs_test + +import ( + "testing" + "testing/fstest" + + "github.com/stretchr/testify/require" + + pfs "github.com/ktong/konf/provider/fs" +) + +func BenchmarkNew(b *testing.B) { + mapFS := fstest.MapFS{ + "config.json": { + Data: []byte(`{"k":"v"}`), + }, + } + b.ResetTimer() + + var loader pfs.FS + for i := 0; i < b.N; i++ { + loader = pfs.New(mapFS, "config.json") + } + b.StopTimer() + + values, err := loader.Load() + require.NoError(b, err) + require.Equal(b, "v", values["k"]) +} + +func BenchmarkLoad(b *testing.B) { + fs := fstest.MapFS{ + "config.json": { + Data: []byte(`{"k":"v"}`), + }, + } + loader := pfs.New(fs, "config.json") + b.ResetTimer() + + var ( + values map[string]any + err error + ) + for i := 0; i < b.N; i++ { + values, err = loader.Load() + } + b.StopTimer() + + require.NoError(b, err) + require.Equal(b, "v", values["k"]) +} diff --git a/provider/fs/fs.go b/provider/fs/fs.go new file mode 100644 index 00000000..a4f338eb --- /dev/null +++ b/provider/fs/fs.go @@ -0,0 +1,69 @@ +// Copyright (c) 2023 The konf authors +// Use of this source code is governed by a MIT license found in the LICENSE file. + +// Package fs loads configuration from files. +// +// FS loads file with given path from OS file system and returns nested map[string]any +// that is parsed as json. +// +// The default behavior can be changed with following options: +// - WithFS provides the fs.FS that config file is loaded from. +// E.g. `WithFS(cfg)` will load configuration from embed file while cfg is embed.FS. +// - WithUnmarshal provides the function that parses config file. +// E.g. `WithUnmarshal(yaml.Unmarshal)` will parse the file as yaml. +// - IgnoreFileNotExit ignores the error if config file does not exist. +package fs + +import ( + "encoding/json" + "fmt" + "io/fs" + "log/slog" + "os" +) + +// FS is a Provider that loads configuration from file. +type FS struct { + fs fs.FS + path string + unmarshal func([]byte, any) error + ignoreNotExist bool +} + +// New returns a FS with the given path and Option(s). +func New(fs fs.FS, path string, opts ...Option) FS { + option := &options{ + fs: fs, + path: path, + unmarshal: json.Unmarshal, + } + for _, opt := range opts { + opt(option) + } + + return FS(*option) +} + +func (f FS) Load() (map[string]any, error) { + bytes, err := fs.ReadFile(f.fs, f.path) + if err != nil { + if f.ignoreNotExist && os.IsNotExist(err) { + slog.Warn("Config file does not exist.", "file", f.path) + + return make(map[string]any), nil + } + + return nil, fmt.Errorf("read file: %w", err) + } + + var out map[string]any + if err := f.unmarshal(bytes, &out); err != nil { + return nil, fmt.Errorf("unmarshal: %w", err) + } + + return out, nil +} + +func (f FS) String() string { + return "fs:" + f.path +} diff --git a/provider/fs/fs_test.go b/provider/fs/fs_test.go new file mode 100644 index 00000000..119dbc40 --- /dev/null +++ b/provider/fs/fs_test.go @@ -0,0 +1,98 @@ +// Copyright (c) 2023 The konf authors +// Use of this source code is governed by a MIT license found in the LICENSE file. + +//go:build !race + +package fs_test + +import ( + "errors" + "io/fs" + "strings" + "testing" + "testing/fstest" + + "github.com/stretchr/testify/require" + + pfs "github.com/ktong/konf/provider/fs" +) + +func TestFile_Load(t *testing.T) { + t.Parallel() + + testcases := []struct { + description string + fs fs.FS + path string + opts []pfs.Option + expected map[string]any + err string + }{ + { + description: "fs file", + fs: fstest.MapFS{ + "config.json": { + Data: []byte(`{"p":{"k":"v"}}`), + }, + }, + path: "config.json", + expected: map[string]any{ + "p": map[string]any{ + "k": "v", + }, + }, + }, + { + description: "fs file (not exist)", + fs: fstest.MapFS{}, + path: "not_found.json", + err: "read file: open not_found.json: file does not exist", + }, + { + description: "fs file (ignore not exist)", + fs: fstest.MapFS{}, + path: "not_found.json", + opts: []pfs.Option{ + pfs.IgnoreFileNotExit(), + }, + expected: map[string]any{}, + }, + { + description: "unmarshal error", + fs: fstest.MapFS{ + "config.json": { + Data: []byte(`{"p":{"k":"v"}}`), + }, + }, + path: "config.json", + opts: []pfs.Option{ + pfs.WithUnmarshal(func([]byte, any) error { + return errors.New("unmarshal error") + }), + }, + err: "unmarshal: unmarshal error", + }, + } + + for i := range testcases { + testcase := testcases[i] + + t.Run(testcase.description, func(t *testing.T) { + t.Parallel() + + values, err := pfs.New(testcase.fs, testcase.path, testcase.opts...).Load() + if err != nil { + require.True(t, strings.HasPrefix(err.Error(), testcase.err)) + } else { + require.NoError(t, err) + require.Equal(t, testcase.expected, values) + } + }) + } +} + +func TestFile_String(t *testing.T) { + t.Parallel() + + require.Equal(t, "fs:config.json", pfs.New(fstest.MapFS{}, "config.json").String()) +} diff --git a/provider/fs/option.go b/provider/fs/option.go new file mode 100644 index 00000000..a50c7bed --- /dev/null +++ b/provider/fs/option.go @@ -0,0 +1,25 @@ +// Copyright (c) 2023 The konf authors +// Use of this source code is governed by a MIT license found in the LICENSE file. + +package fs + +// WithUnmarshal provides the function that parses config file. +// +// The default function is json.Unmarshal. +func WithUnmarshal(unmarshal func([]byte, any) error) Option { + return func(options *options) { + options.unmarshal = unmarshal + } +} + +// IgnoreFileNotExit ignores the error if config file does not exist. +func IgnoreFileNotExit() Option { + return func(options *options) { + options.ignoreNotExist = true + } +} + +// Option configures the given FS. +type Option func(file *options) + +type options FS diff --git a/provider/fs/testdata/config.json b/provider/fs/testdata/config.json new file mode 100644 index 00000000..f9a2909e --- /dev/null +++ b/provider/fs/testdata/config.json @@ -0,0 +1,6 @@ +{ + "p": { + "k": "v" + } +} +