From da0b2b9454c8be7e000a7d9f46fa61064b11f0ce Mon Sep 17 00:00:00 2001 From: Milad Abbasi Date: Fri, 29 Jan 2021 16:45:03 +0330 Subject: [PATCH] Update readme file --- README.md | 305 ++++++++++++++++++++++++++++++++++++++++++++++++++++ env.go | 8 +- env_test.go | 4 +- file.go | 2 +- go.mod | 4 +- 5 files changed, 313 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 2b2ea77..445ab4d 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,308 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/milad-abbasi/gonfig)](https://goreportcard.com/report/github.com/milad-abbasi/gonfig) [![GitHub release](https://img.shields.io/github/release/milad-abbasi/gonfig.svg)](https://gitHub.com/milad-abbasi/gonfig/releases) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) + +Tag-based configuration parser which loads values from different providers into typesafe struct. + +## Installation +This package needs go version 1.15+ +```bash +go get -u github.com/milad-abbasi/gonfig +``` + +## Usage +```go +package main + +import ( + "fmt" + "net/url" + "time" + + "github.com/milad-abbasi/gonfig" +) + +// Unexported struct fields are ignored +type Config struct { + Host string + Log bool + Expiry int + Port uint + Pi float64 + Com complex128 + Byte byte + Rune rune + Duration time.Duration + Time time.Time + url url.URL + Redis struct { + Hosts []string + Ports []int + } +} + +func main() { + var c Config + + // Input argument must be a pointer to struct + err := gonfig.Load().FromEnv().Into(&c) + if err != nil { + fmt.Println(err) + } +} +``` + +## Tags +All the tags are optional. + +### Config +`config` tag is used to change default key for fetching the value of field. +```go +type Config struct { + HostName string `config:"HOST"` +} + +func main() { + var c Config + + os.Setenv("HOST", "golang.org") + gonfig.Load().FromEnv().Into(&c) +} +``` + +### File related tags +`json`, `yaml` and `toml` tags are used to change default key for fetching the value from file. +```go +type Config struct { + HostName string `json:"host" yaml:"host" toml:"host"` +} + +func main() { + var c Config + + gonfig.Load().FromFile("config.json").Into(&c) +} +``` + +### Default +`default` tag is used to declare a default value in case of missing value. +```go +type Config struct { + Host url.URL `default:"golang.org"` +} +``` + +### Required +`required` tag is used to make sure a value is present for corresponding field. +Fields are optional by default. +```go +type Config struct { + Host url.URL `required:"true"` +} +``` + +### Ignore +`ignore` tag is used to skip populating a field. +Ignore is `false` by default. +```go +type Config struct { + Ignored int `ignore:"true"` + AlsoIgnored int `config:"-"` +} +``` + +### Expand +`expand` tag is used to expand value from OS environment variables. +Expand is `false` by default. +```go +type Config struct { + Expanded int `expand:"true" default:"${ENV_VALUE}"` +} + +func main() { + var c Config + + os.Setenv("ENV_VALUE", "123") + gonfig.Load().FromEnv().Into(&c) + fmt.Println(c.Expanded) // 123 +} +``` + +### Separator +`separator` tag is used to separate slice/array items. +Default separator is a single space. +```go +type Config struct { + List []int `separator:"," default:"1, 2, 3"` +} +``` + +### Format +`format` tag is used for parsing time strings. +Default format is `time.RFC3339`. +```go +type Config struct { + Time time.Time `format:"2006-01-02T15:04:05.999999999Z07:00"` +} +``` + +## Providers +Providers can be chained together and they are applied in the specified order. +If multiple values are provided for a field, last one will get applied. + +### Supported providers +- Environment variables +- files + - .json + - .yaml (.yml) + - .toml + - .env + +```go +func main() { + var c Config + + gonfig. + Load(). + FromEnv(). + FromFile("config.json"). + FromFile("config.yaml"). + FromFile("config.toml"). + FromFile(".env"). + AddProvider(CustomProvider). + Into(&c) +} +``` + +### Env Provider +Env provider will populate struct fields based on the hierarchy of struct. + +```go +type Config struct { + PrettyLog bool + Redis struct { + Host string + Port int + } +} + +func main() { + var c Config + + gonfig. + Load(). + FromEnv(). + Into(&c) +} +``` +It will check for following keys: +- `PRETTY_LOG` +- `REDIS_HOST` +- `REDIS_PORT` + +To change default settings, make an `EnvProvider` and add it to the providers list manually: +```go +type Config struct { + PrettyLog bool + Redis struct { + Host string + Port int + } +} + +func main() { + var c Config + + ep := gonfig.EnvProvider{ + Prefix: "APP_", // Default to "" + SnakeCase: false, // Defaults to true + UpperCase: false, // Defaults to true + FieldSeparator: "__", // Defaults to "_" + Source: ".env", // Defaults to OS env vars + Required: true, // Defaults to false + } + + gonfig. + Load(). + AddProvider(&ep). + Into(&c) +} +``` +It will check for following keys in `.env` file: +- `APP_PrettyLog` +- `APP_Redis__Host` +- `APP_Redis__Port` + +### File Provider +File provider uses third party parsers for parsing files, read their documentation for more info. +- [json](https://golang.org/pkg/encoding/json) +- [yaml](https://github.com/go-yaml/yaml/tree/v3) +- [toml](https://github.com/BurntSushi/toml) +- [env](https://github.com/joho/godotenv) + +### Custom Provider +You can use your own provider by implementing `Provider` interface and one or both `Unmarshaler` and `Filler` interfaces. + +```go +type CustomProvider struct{} + +func (cp *CustomProvider) Name() string { + return "custom provider" +} + +func (cp *CustomProvider) UnmarshalStruct(i interface{}) error { + // UnmarshalStruct receives a struct pointer and unmarshalls values into it. + return nil +} + +func (cp *CustomProvider) Fill(in *gonfig.Input) error { + // Fill receives struct fields and set their values. + return nil +} + +func main() { + var c Config + + gonfig. + Load(). + AddProvider(new(CustomProvider)). + Into(&c) +} +``` + +## Supported types +Any other type except the followings, results an error +- `string` +- `bool` +- `int`, `int8`, `int16`, `int32`, `int64` +- `uint`, `uint8`, `uint16`, `uint32`, `uint64` +- `float32`, `float64` +- `complex64`, `complex128` +- `byte` +- `rune` +- [time.Duration](https://golang.org/pkg/time/#Duration) +- [time.Time](https://golang.org/pkg/time/#Time) +- [url.URL](https://golang.org/pkg/net/url/#URL) +- `pointer`, `slice` and `array` of above types +- `nested` and `embedded` structs + +## TODO +Any contribution is appreciated :) + +- [ ] Add support for map data type + - [ ] Add support for map slice +- [ ] Add support for slice of structs +- [ ] Add support for [encoding.TextUnmarshaler](https://golang.org/pkg/encoding/#TextUnmarshaler) and [encoding.BinaryUnmarshaler](https://golang.org/pkg/encoding/#BinaryUnmarshaler) +- [ ] Add support for other providers + - [ ] command line flags + - [ ] [etcd](https://etcd.io) + - [ ] [Consul](https://www.consul.io) + - [ ] [Vault](https://www.vaultproject.io) + - [ ] [Amazon SSM](https://docs.aws.amazon.com/systems-manager/latest/userguide/what-is-systems-manager.html) + +## Documentation +Take a look at [docs](https://pkg.go.dev/github.com/milad-abbasi/gonfig) for more information. + +## License +The library is released under the [MIT license](https://opensource.org/licenses/MIT). +Checkout [LICENSE](https://github.com/milad-abbasi/gonfig/blob/master/LICENSE) file. diff --git a/env.go b/env.go index 686a167..84f8576 100644 --- a/env.go +++ b/env.go @@ -11,7 +11,7 @@ import ( // EnvProvider loads values from environment variables to provided struct type EnvProvider struct { // Prefix is used when finding values from environment variables, defaults to "" - EnvPrefix string + Prefix string // SnakeCase specifies whether to convert field names to snake_case or not, defaults to true SnakeCase bool @@ -38,7 +38,7 @@ var ( // NewEnvProvider creates a new EnvProvider func NewEnvProvider() *EnvProvider { return &EnvProvider{ - EnvPrefix: "", + Prefix: "", SnakeCase: true, UpperCase: true, FieldSeparator: "_", @@ -162,7 +162,7 @@ func (ep *EnvProvider) provide(content map[string]string, key string, path []str // buildKey prefix key with EnvPrefix, if not provided, path slice will be used func (ep *EnvProvider) buildKey(key string, path []string) string { if key != "" { - return ep.EnvPrefix + key + return ep.Prefix + key } k := strings.Join(path, ep.FieldSeparator) @@ -173,7 +173,7 @@ func (ep *EnvProvider) buildKey(key string, path []string) string { k = strings.ToUpper(k) } - k = ep.EnvPrefix + k + k = ep.Prefix + k return k } diff --git a/env_test.go b/env_test.go index 148f7fa..8bd82cd 100644 --- a/env_test.go +++ b/env_test.go @@ -12,7 +12,7 @@ import ( func TestNewEnvProvider(t *testing.T) { ep := NewEnvProvider() require.NotNil(t, ep) - assert.Equal(t, "", ep.EnvPrefix) + assert.Equal(t, "", ep.Prefix) assert.Equal(t, true, ep.SnakeCase) assert.Equal(t, true, ep.UpperCase) assert.Equal(t, "_", ep.FieldSeparator) @@ -131,7 +131,7 @@ func TestEnvProvider_Fill(t *testing.T) { require.NoError(t, err) require.NotNil(t, in) ep := EnvProvider{ - EnvPrefix: "APP_", + Prefix: "APP_", } err = ep.Fill(in) diff --git a/file.go b/file.go index f0baa86..f29167d 100644 --- a/file.go +++ b/file.go @@ -9,7 +9,7 @@ import ( "path/filepath" "github.com/BurntSushi/toml" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" ) // Supported file extensions diff --git a/go.mod b/go.mod index 0e33b78..6a68d80 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,7 @@ go 1.15 require ( github.com/BurntSushi/toml v0.3.1 - github.com/davecgh/go-spew v1.1.1 // indirect github.com/joho/godotenv v1.3.0 github.com/stretchr/testify v1.6.1 - gopkg.in/yaml.v2 v2.4.0 - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c )