From 1fe3a706e72dce6de11f07895d67122964da39bc Mon Sep 17 00:00:00 2001 From: Zack Patrick Date: Tue, 27 Feb 2024 13:01:15 -0800 Subject: [PATCH] remove extra directory, cleanup readme --- .github/README.md | 44 ++++---- {providers/envvar => envvar}/envvar.go | 0 {providers/envvar => envvar}/envvar_test.go | 2 +- {providers/flags => flags}/flags.go | 0 {providers/flags => flags}/flags_test.go | 2 +- {providers/ini => ini}/example_test.go | 4 +- {providers/ini => ini}/ini.go | 9 +- {providers/ini => ini}/ini_test.go | 4 +- internal/testing.go | 4 +- provider_test.go | 2 +- providers/generic/generic.go | 112 -------------------- {providers/toml => toml}/example_test.go | 2 +- {providers/toml => toml}/toml.go | 4 +- {providers/toml => toml}/toml_test.go | 2 +- 14 files changed, 42 insertions(+), 149 deletions(-) rename {providers/envvar => envvar}/envvar.go (100%) rename {providers/envvar => envvar}/envvar_test.go (97%) rename {providers/flags => flags}/flags.go (100%) rename {providers/flags => flags}/flags_test.go (96%) rename {providers/ini => ini}/example_test.go (93%) rename {providers/ini => ini}/ini.go (93%) rename {providers/ini => ini}/ini_test.go (93%) delete mode 100644 providers/generic/generic.go rename {providers/toml => toml}/example_test.go (96%) rename {providers/toml => toml}/toml.go (93%) rename {providers/toml => toml}/toml_test.go (95%) diff --git a/.github/README.md b/.github/README.md index afa2cae..b097726 100644 --- a/.github/README.md +++ b/.github/README.md @@ -3,11 +3,11 @@ [![Go Doc](https://godoc.org/github.com/zpatrick/cfg?status.svg)](https://godoc.org/github.com/zpatrick/cfg) [![Build Status](https://github.com/zpatrick/cfg/actions/workflows/go.yaml/badge.svg?branch=main)](https://github.com/zpatrick/cfg/actions/workflows/go.yaml?query=branch%3Amain) -This package is designed to house a common set of configuration-related features & patterns for teams working with microservice applications. These designs include: +This package is designed to house a common set of configuration-related features & patterns for Golang services. This include: - Support for multiple sources of configuration. - Providing default values and validation logic for specific settings. -- Support access patterns which encourage decoupling configuration logic from the rest of the application. +- Package API which encourages high cohesion and low coupling with the rest of the application. ## Usage @@ -20,8 +20,8 @@ import ( "time" "github.com/zpatrick/cfg" - "github.com/zpatrick/cfg/providers/envvar" - "github.com/zpatrick/cfg/providers/yaml" + "github.com/zpatrick/cfg/envvar" + "github.com/zpatrick/cfg/ini" ) type Config struct { @@ -32,7 +32,7 @@ type Config struct { } func LoadConfig(ctx context.Context, path string) (*Config, error) { - yamlFile, err := yaml.New(path) + iniFile, err := ini.Load(path) if err != nil { return nil, err } @@ -55,7 +55,7 @@ func LoadConfig(ctx context.Context, path string) (*Config, error) { Default: cfg.Addr(8080), Provider: cfg.MultiProvider { envvar.Newf("APP_SERVER_PORT", strconv.Atoi), - yamlFile.Int("server", "port"), + iniFile.Int("server", "port"), }, }, "server timeout": cfg.Schema[time.Duration]{ @@ -64,7 +64,7 @@ func LoadConfig(ctx context.Context, path string) (*Config, error) { Validator: cfg.Between(time.Second, time.Minute*5), Provider: cfg.MultiProvider { envvar.Newf("APP_SERVER_TIMEOUT", time.ParseDuration), - yamlFile.Duration("server", "timeout"), + iniFile.Duration("server", "timeout"), }, }, }); err != nil { @@ -82,8 +82,8 @@ func LoadConfig(ctx context.Context, path string) (*Config, error) { - [Flags](https://pkg.go.dev/github.com/zpatrick/cfg#Flag) - [INI Files](https://pkg.go.dev/github.com/zpatrick/cfg#INIFile) - [TOML Files](https://pkg.go.dev/github.com/zpatrick/cfg#TOMLFile) -- [YAML Files](https://pkg.go.dev/github.com/zpatrick/cfg#YAMLFile) +Please see the [Godoc](https://pkg.go.dev/github.com/zpatrick/cfg#example-YAML) example for YAML files. # Validation A setting may specify a [Validator](https://pkg.go.dev/github.com/zpatrick/cfg#Validator) which will check whether or not a provided value is valid. @@ -97,6 +97,20 @@ The built in validators are: # Advanced +## Custom Validation +A custom Validator must satisfy the [Validator](https://pkg.go.dev/github.com/zpatrick/cfg#Validator) interface. +The simplest way to achieve this is by using the [ValidatorFunc](https://pkg.go.dev/github.com/zpatrick/cfg#ValidatorFunc) type. + +```go +cfg.Setting[string]{ + Default: cfg.Addr("name@email.com"), + Validator: cfg.ValidatorFunc(func(addr string) error { + _, err := mail.ParseAddr(addr) + return err + }), +} +``` + ## Custom Providers ```go @@ -134,17 +148,3 @@ func LoadConfig() (*Config, error) { ... } ``` - -## Custom Validation -A custom Validator must satisfy the [Validator](https://pkg.go.dev/github.com/zpatrick/cfg#Validator) interface. -The simplest way to achieve this is by using the [ValidatorFunc](https://pkg.go.dev/github.com/zpatrick/cfg#ValidatorFunc) type. - -```go -cfg.Setting[string]{ - Default: cfg.Addr("name@email.com"), - Validator: cfg.ValidatorFunc(func(addr string) error { - _, err := mail.ParseAddr(addr) - return err - }), -} -``` diff --git a/providers/envvar/envvar.go b/envvar/envvar.go similarity index 100% rename from providers/envvar/envvar.go rename to envvar/envvar.go diff --git a/providers/envvar/envvar_test.go b/envvar/envvar_test.go similarity index 97% rename from providers/envvar/envvar_test.go rename to envvar/envvar_test.go index 1e5c7d7..ee0a07b 100644 --- a/providers/envvar/envvar_test.go +++ b/envvar/envvar_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/zpatrick/cfg" - "github.com/zpatrick/cfg/providers/envvar" + "github.com/zpatrick/cfg/envvar" "github.com/zpatrick/testx/assert" ) diff --git a/providers/flags/flags.go b/flags/flags.go similarity index 100% rename from providers/flags/flags.go rename to flags/flags.go diff --git a/providers/flags/flags_test.go b/flags/flags_test.go similarity index 96% rename from providers/flags/flags_test.go rename to flags/flags_test.go index 4898383..4ad6826 100644 --- a/providers/flags/flags_test.go +++ b/flags/flags_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/zpatrick/cfg" - "github.com/zpatrick/cfg/providers/flags" + "github.com/zpatrick/cfg/flags" "github.com/zpatrick/testx/assert" ) diff --git a/providers/ini/example_test.go b/ini/example_test.go similarity index 93% rename from providers/ini/example_test.go rename to ini/example_test.go index 4a06735..5d14d29 100644 --- a/providers/ini/example_test.go +++ b/ini/example_test.go @@ -7,8 +7,8 @@ import ( "time" "github.com/zpatrick/cfg" + "github.com/zpatrick/cfg/ini" "github.com/zpatrick/cfg/internal" - "github.com/zpatrick/cfg/providers/ini" ) type Config struct { @@ -32,7 +32,7 @@ addr = "localhost" } defer os.Remove(path) - iniFile, err := ini.New(path) + iniFile, err := ini.Load(path) if err != nil { panic(err) } diff --git a/providers/ini/ini.go b/ini/ini.go similarity index 93% rename from providers/ini/ini.go rename to ini/ini.go index e2d99ff..2d4971e 100644 --- a/providers/ini/ini.go +++ b/ini/ini.go @@ -16,8 +16,8 @@ type Provider struct { f *ini.File } -// New returns a Provider which loads values from the parsed ini file at the given path. -func New(path string) (*Provider, error) { +// Load reads and parses the ini file at the given path. +func Load(path string) (*Provider, error) { data, err := os.ReadFile(path) if err != nil { return nil, err @@ -31,6 +31,11 @@ func New(path string) (*Provider, error) { return &Provider{f: f}, nil } +// New returns a Provider which loads values from f. +func New(f *ini.File) (*Provider, error) { + return &Provider{f: f}, nil +} + // String returns a string configuration value at the given section and key. func (p *Provider) String(section, key string) cfg.Provider[string] { return Provide(p, section, key, func(k *ini.Key) (string, error) { return k.String(), nil }) diff --git a/providers/ini/ini_test.go b/ini/ini_test.go similarity index 93% rename from providers/ini/ini_test.go rename to ini/ini_test.go index e053240..b9e9d37 100644 --- a/providers/ini/ini_test.go +++ b/ini/ini_test.go @@ -7,8 +7,8 @@ import ( "time" "github.com/zpatrick/cfg" + "github.com/zpatrick/cfg/ini" "github.com/zpatrick/cfg/internal" - "github.com/zpatrick/cfg/providers/ini" "github.com/zpatrick/testx/assert" ) @@ -27,7 +27,7 @@ timeout = "5s" assert.NilError(t, err) t.Cleanup(func() { os.Remove(path) }) - f, err := ini.New(path) + f, err := ini.Load(path) assert.NilError(t, err) internal.AssertProvides(t, f.String("", "root"), "hello") diff --git a/internal/testing.go b/internal/testing.go index 2cdccfa..646a439 100644 --- a/internal/testing.go +++ b/internal/testing.go @@ -3,7 +3,7 @@ package internal import ( "bytes" "context" - "io/ioutil" + "os" "testing" "github.com/zpatrick/cfg" @@ -13,7 +13,7 @@ import ( // WriteTempFile creates a new temporary file under dir with the contents of data. // The file name is returned. func WriteTempFile(dir, data string) (string, error) { - file, err := ioutil.TempFile(dir, "config.yaml") + file, err := os.CreateTemp(dir, "config.yaml") if err != nil { return "", nil } diff --git a/provider_test.go b/provider_test.go index 87c5ff2..b3d1fc1 100644 --- a/provider_test.go +++ b/provider_test.go @@ -9,7 +9,7 @@ import ( "github.com/pkg/errors" "github.com/zpatrick/cfg" - "github.com/zpatrick/cfg/providers/envvar" + "github.com/zpatrick/cfg/envvar" "github.com/zpatrick/testx/assert" ) diff --git a/providers/generic/generic.go b/providers/generic/generic.go deleted file mode 100644 index 70141e0..0000000 --- a/providers/generic/generic.go +++ /dev/null @@ -1,112 +0,0 @@ -package generic - -import ( - "context" - "fmt" - "reflect" - "time" - - "github.com/pkg/errors" - "github.com/zpatrick/cfg" -) - -type UnexpectedTypeError struct { - Expected, Provided reflect.Type -} - -func NewUnexpectedTypeError(expectedVal, providedVal interface{}) *UnexpectedTypeError { - return &UnexpectedTypeError{ - Expected: reflect.TypeOf(expectedVal), - Provided: reflect.TypeOf(providedVal), - } -} - -func (e *UnexpectedTypeError) Error() string { - return fmt.Sprintf("unexpected type error: provided type was %s (expected %s)", e.Provided, e.Expected) -} - -type Provider map[string]any - -func (p Provider) Get(key string, keys ...string) (any, error) { - val, ok := p[key] - if !ok { - return nil, cfg.NoValueProvidedError - } - - if len(keys) == 0 { - return val, nil - } - - switch val := val.(type) { - case map[string]any: - return Provider(val).Get(keys[0], keys[1:]...) - case map[any]any: - return asProvider(val).Get(keys[0], keys[1:]...) - default: - uerr := NewUnexpectedTypeError(map[string]any{}, val) - return nil, errors.Wrapf(uerr, "unable to traverse past key %s", key) - } -} - -// asProvider extracts all key/val pairs from m where key is of type string. -func asProvider(m map[any]any) Provider { - out := make(map[string]any, len(m)) - for key, val := range m { - if key, ok := key.(string); ok { - out[key] = val - } - } - - return out -} - -func (p Provider) String(section string, keys ...string) cfg.Provider[string] { - return provide[string](p, section, keys...) -} - -func (p Provider) Float64(section string, keys ...string) cfg.Provider[float64] { - return provide[float64](p, section, keys...) -} - -func (p Provider) Int(section string, keys ...string) cfg.Provider[int] { - return provide[int](p, section, keys...) -} - -func (p Provider) Int64(section string, keys ...string) cfg.Provider[int64] { - return provide[int64](p, section, keys...) -} - -func (p Provider) Uint64(section string, keys ...string) cfg.Provider[uint64] { - return provide[uint64](p, section, keys...) -} - -func (p Provider) Bool(section string, keys ...string) cfg.Provider[bool] { - return provide[bool](p, section, keys...) -} - -func (p Provider) Duration(section string, keys ...string) cfg.Provider[time.Duration] { - return cfg.ProviderFunc[time.Duration](func(ctx context.Context) (out time.Duration, err error) { - val, err := provide[string](p, section, keys...).Provide(ctx) - if err != nil { - return out, err - } - - return time.ParseDuration(val) - }) -} - -func provide[T any](p Provider, section string, keys ...string) cfg.Provider[T] { - return cfg.ProviderFunc[T](func(ctx context.Context) (out T, err error) { - val, err := p.Get(section, keys...) - if err != nil { - return out, cfg.NoValueProvidedError - } - - v, ok := val.(T) - if !ok { - return out, NewUnexpectedTypeError(out, val) - } - - return v, nil - }) -} diff --git a/providers/toml/example_test.go b/toml/example_test.go similarity index 96% rename from providers/toml/example_test.go rename to toml/example_test.go index caa8c7a..f032a62 100644 --- a/providers/toml/example_test.go +++ b/toml/example_test.go @@ -8,7 +8,7 @@ import ( "github.com/zpatrick/cfg" "github.com/zpatrick/cfg/internal" - "github.com/zpatrick/cfg/providers/toml" + "github.com/zpatrick/cfg/toml" ) type Config struct { diff --git a/providers/toml/toml.go b/toml/toml.go similarity index 93% rename from providers/toml/toml.go rename to toml/toml.go index 37c3b3d..54278e7 100644 --- a/providers/toml/toml.go +++ b/toml/toml.go @@ -7,7 +7,7 @@ import ( "github.com/pelletier/go-toml" "github.com/zpatrick/cfg" - "github.com/zpatrick/cfg/providers/generic" + "github.com/zpatrick/cfg/internal" ) type Provider struct { @@ -68,7 +68,7 @@ func Provide[T any](p *Provider, keys ...string) cfg.Provider[T] { val := p.tree.GetPath(keys) v, ok := val.(T) if !ok { - return out, generic.NewUnexpectedTypeError(out, val) + return out, internal.NewUnexpectedTypeError(out, val) } return v, nil diff --git a/providers/toml/toml_test.go b/toml/toml_test.go similarity index 95% rename from providers/toml/toml_test.go rename to toml/toml_test.go index e307b8a..db90bc9 100644 --- a/providers/toml/toml_test.go +++ b/toml/toml_test.go @@ -8,7 +8,7 @@ import ( "github.com/zpatrick/cfg" "github.com/zpatrick/cfg/internal" - "github.com/zpatrick/cfg/providers/toml" + "github.com/zpatrick/cfg/toml" "github.com/zpatrick/testx/assert" )