Go: Bump github.com/sethvargo/go-envconfig from 0.9.0 to 1.0.0 (#7190)

* Go: Bump github.com/sethvargo/go-envconfig from 0.9.0 to 1.0.0

Bumps [github.com/sethvargo/go-envconfig](https://github.com/sethvargo/go-envconfig) from 0.9.0 to 1.0.0.
- [Release notes](https://github.com/sethvargo/go-envconfig/releases)
- [Commits](https://github.com/sethvargo/go-envconfig/compare/v0.9.0...v1.0.0)

---
updated-dependencies:
- dependency-name: github.com/sethvargo/go-envconfig
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix breaking change

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Armel Soro <asoro@redhat.com>
This commit is contained in:
dependabot[bot]
2024-01-30 08:33:43 +00:00
committed by GitHub
parent e19625ae89
commit 74f295d6ba
7 changed files with 439 additions and 361 deletions

View File

@@ -1,23 +1,19 @@
# Envconfig
[![GoDoc](https://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://pkg.go.dev/mod/github.com/sethvargo/go-envconfig)
[![GitHub Actions](https://img.shields.io/github/workflow/status/sethvargo/go-envconfig/unit/main?style=flat-square)](https://github.com/sethvargo/go-envconfig/actions?query=branch%3Amain+-event%3Aschedule)
[![GoDoc](https://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)][godoc]
Envconfig populates struct field values based on environment variables or
arbitrary lookup functions. It supports pre-setting mutations, which is useful
for things like converting values to uppercase, trimming whitespace, or looking
up secrets.
**Note:** Versions prior to v0.2 used a different import path. This README and
examples are for v0.2+.
## Usage
Define a struct with fields using the `env` tag:
```go
type MyConfig struct {
Port int `env:"PORT"`
Port string `env:"PORT"`
Username string `env:"USERNAME"`
}
```
@@ -63,113 +59,141 @@ type MyConfig struct {
}
type DatabaseConfig struct {
Port int `env:"PORT"`
Port string `env:"PORT"`
Username string `env:"USERNAME"`
}
```
## Configuration
Use the `env` struct tag to define configuration.
Use the `env` struct tag to define configuration. See the [godoc][] for usage
examples.
### Required
- `required` - marks a field as required. If a field is required, decoding
will error if the environment variable is unset.
If a field is required, processing will error if the environment variable is
unset.
```go
type MyStruct struct {
Port string `env:"PORT, required"`
}
```
```go
type MyStruct struct {
Port int `env:"PORT,required"`
}
```
- `default` - sets the default value for the environment variable is not set.
The environment variable must not be set (e.g. `unset PORT`). If the
environment variable is the empty string, envconfig considers that a "value"
and the default will **not** be used.
It is invalid to have a field as both `required` and `default`.
You can also set the default value to the value from another field or a
value from a different environment variable.
### Default
```go
type MyStruct struct {
Port string `env:"PORT, default=5555"`
User string `env:"USER, default=$CURRENT_USER"`
}
```
If an environment variable is not set, the field will be set to the default
value. Note that the environment variable must not be set (e.g. `unset PORT`).
If the environment variable is the empty string, that counts as a "value" and
the default will not be used.
- `prefix` - sets the prefix to use for looking up environment variable keys
on child structs and fields. This is useful for shared configurations:
```go
type MyStruct struct {
Port int `env:"PORT,default=5555"`
}
```
```go
type RedisConfig struct {
Host string `env:"REDIS_HOST"`
User string `env:"REDIS_USER"`
}
You can also set the default value to another field or value from the
environment, for example:
type ServerConfig struct {
// CacheConfig will process values from $CACHE_REDIS_HOST and
// $CACHE_REDIS respectively.
CacheConfig *RedisConfig `env:", prefix=CACHE_"`
```go
type MyStruct struct {
DefaultPort int `env:"DEFAULT_PORT,default=5555"`
Port int `env:"OVERRIDE_PORT,default=$DEFAULT_PORT"`
}
```
// RateLimitConfig will process values from $RATE_LIMIT_REDIS_HOST and
// $RATE_LIMIT_REDIS respectively.
RateLimitConfig *RedisConfig `env:", prefix=RATE_LIMIT_"`
}
```
The value for `Port` will default to the value of `DEFAULT_PORT`.
- `overwrite` - force overwriting existing non-zero struct values if the
environment variable was provided.
It is invalid to have a field as both `required` and `default`.
```go
type MyStruct struct {
Port string `env:"PORT, overwrite"`
}
```
### Prefix
The rules for overwrite + default are:
For shared, embedded structs, you can define a prefix to use when processing
struct values for that embed.
- If the struct field has the zero value and a default is set:
```go
type SharedConfig struct {
Port int `env:"PORT,default=5555"`
}
- If no environment variable is specified, the struct field will be
populated with the default value.
type Server1 struct {
// This processes Port from $FOO_PORT.
*SharedConfig `env:",prefix=FOO_"`
}
- If an environment variable is specified, the struct field will be
populate with the environment variable value.
type Server2 struct {
// This processes Port from $BAR_PORT.
*SharedConfig `env:",prefix=BAR_"`
}
```
- If the struct field has a non-zero value and a default is set:
It is invalid to specify a prefix on non-struct fields.
- If no environment variable is specified, the struct field's existing
value will be used (the default is ignored).
### Overwrite
- If an environment variable is specified, the struct field's existing
value will be overwritten with the environment variable value.
If overwrite is set, the value will be overwritten if there is an environment
variable match regardless if the value is non-zero.
- `delimiter` - choose a custom character to denote individual slice and map
entries. The default value is the comma (`,`).
```go
type MyStruct struct {
Port int `env:"PORT,overwrite"`
}
```
```go
type MyStruct struct {
MyVar []string `env:"MYVAR, delimiter=;"`
```
The rules for overwrite + default are:
```bash
export MYVAR="a;b;c;d" # []string{"a", "b", "c", "d"}
```
- If the struct field has the zero value and a default is set:
- `separator` - choose a custom character to denote the separation between
keys and values in map entries. The default value is the colon (`:`) Define
a separator with `separator`:
- If no environment variable is specified, the struct field will be
populated with the default value.
```go
type MyStruct struct {
MyVar map[string]string `env:"MYVAR, separator=|"`
}
```
- If an environment variable is specified, the struct field will be
populate with the environment variable value.
```bash
export MYVAR="a|b,c|d" # map[string]string{"a":"b", "c":"d"}
```
- If the struct field has a non-zero value and a default is set:
- `noinit` - do not initialize struct fields unless environment variables were
provided. The default behavior is to deeply initialize all fields to their
default (zero) value.
- If no environment variable is specified, the struct field's existing
value will be used (the default is ignored).
```go
type MyStruct struct {
MyVar *url.URL `env:"MYVAR, noinit"`
}
```
- If an environment variable is specified, the struct field's existing
value will be overwritten with the environment variable value.
- `decodeunset` - force envconfig to run decoders even on unset environment
variable values. The default behavior is to skip running decoders on unset
environment variable values.
```go
type MyStruct struct {
MyVar *url.URL `env:"MYVAR, decodeunset"`
}
```
## Complex Types
## Decoding
> [!NOTE]
>
> Complex types are only decoded or unmarshalled when the environment variable
> is defined or a default value is specified.
**Note:** Complex types are only decoded or unmarshalled when the environment
variable is defined or a default is specified. The decoding/unmarshalling
functions are _not_ invoked when a value is not defined.
### Durations
@@ -186,14 +210,18 @@ type MyStruct struct {
export MYVAR="10m" # 10 * time.Minute
```
### TextUnmarshaler / BinaryUnmarshaler
Types that implement `TextUnmarshaler` or `BinaryUnmarshaler` are processed as such.
Types that implement `TextUnmarshaler` or `BinaryUnmarshaler` are processed as
such.
### json.Unmarshaler
Types that implement `json.Unmarshaler` are processed as such.
### gob.Decoder
Types that implement `gob.Decoder` are processed as such.
@@ -201,7 +229,7 @@ Types that implement `gob.Decoder` are processed as such.
### Slices
Slices are specified as comma-separated values:
Slices are specified as comma-separated values.
```go
type MyStruct struct {
@@ -213,20 +241,10 @@ type MyStruct struct {
export MYVAR="a,b,c,d" # []string{"a", "b", "c", "d"}
```
Define a custom delimiter with `delimiter`:
```go
type MyStruct struct {
MyVar []string `env:"MYVAR,delimiter=;"`
```
```bash
export MYVAR="a;b;c;d" # []string{"a", "b", "c", "d"}
```
Note that byte slices are special cased and interpreted as strings from the
environment.
### Maps
Maps are specified as comma-separated key:value pairs:
@@ -241,29 +259,6 @@ type MyStruct struct {
export MYVAR="a:b,c:d" # map[string]string{"a":"b", "c":"d"}
```
Define a custom delimiter with `delimiter`:
```go
type MyStruct struct {
MyVar map[string]string `env:"MYVAR,delimiter=;"`
```
```bash
export MYVAR="a:b;c:d" # map[string]string{"a":"b", "c":"d"}
```
Define a separator with `separator`:
```go
type MyStruct struct {
MyVar map[string]string `env:"MYVAR,separator=|"`
}
```
```bash
export MYVAR="a|b,c|d" # map[string]string{"a":"b", "c":"d"}
```
### Structs
@@ -275,102 +270,12 @@ the non-nil value. To change this behavior, see
[Initialization](#Initialization).
### Custom
### Custom Decoders
You can also [define your own decoder](#Extension).
You can also define your own decoders. See the [godoc][godoc] for more
information.
## Prefixing
You can define a custom prefix using the `PrefixLookuper`. This will lookup
values in the environment by prefixing the keys with the provided value:
```go
type MyStruct struct {
MyVar string `env:"MYVAR"`
}
```
```go
// Process variables, but look for the "APP_" prefix.
l := envconfig.PrefixLookuper("APP_", envconfig.OsLookuper())
if err := envconfig.ProcessWith(ctx, &c, l); err != nil {
panic(err)
}
```
```bash
export APP_MYVAR="foo"
```
## Initialization
By default, all pointers, slices, and maps are initialized (allocated) so they
are not `nil`. To disable this behavior, use the tag the field as `noinit`:
```go
type MyStruct struct {
// Without `noinit`, DeleteUser would be initialized to the default boolean
// value. With `noinit`, if the environment variable is not given, the value
// is kept as uninitialized (nil).
DeleteUser *bool `env:"DELETE_USER, noinit"`
}
```
This also applies to nested fields in a struct:
```go
type ParentConfig struct {
// Without `noinit` tag, `Child` would be set to `&ChildConfig{}` whether
// or not `FIELD` is set in the env var.
// With `noinit`, `Child` would stay nil if `FIELD` is not set in the env var.
Child *ChildConfig `env:",noinit"`
}
type ChildConfig struct {
Field string `env:"FIELD"`
}
```
The `noinit` tag is only applicable for pointer, slice, and map fields. Putting
the tag on a different type will return an error.
## Extension
All built-in types are supported except `Func` and `Chan`. If you need to define
a custom decoder, implement the `Decoder` interface:
```go
type MyStruct struct {
field string
}
func (v *MyStruct) EnvDecode(val string) error {
v.field = fmt.Sprintf("PREFIX-%s", val)
return nil
}
```
If you need to modify environment variable values before processing, you can
specify a custom `Mutator`:
```go
type Config struct {
Password `env:"PASSWORD"`
}
func resolveSecretFunc(ctx context.Context, key, value string) (string, error) {
if strings.HasPrefix(value, "secret://") {
return secretmanager.Resolve(ctx, value) // example
}
return value, nil
}
var config Config
envconfig.ProcessWith(ctx, &config, envconfig.OsLookuper(), resolveSecretFunc)
```
## Testing
Relying on the environment in tests can be troublesome because environment
@@ -384,7 +289,9 @@ lookuper := envconfig.MapLookuper(map[string]string{
})
var config Config
envconfig.ProcessWith(ctx, &config, lookuper)
envconfig.ProcessWith(ctx, &config, &envconfig.Config{
Lookuper: lookuper,
})
```
Now you can parallelize all your tests by providing a map for the lookup
@@ -395,20 +302,4 @@ You can also combine multiple lookupers with `MultiLookuper`. See the GoDoc for
more information and examples.
## Inspiration
This library is conceptually similar to [kelseyhightower/envconfig](https://github.com/kelseyhightower/envconfig), with the following
major behavioral differences:
- Adds support for specifying a custom lookup function (such as a map), which
is useful for testing.
- Only populates fields if they contain zero or nil values if `overwrite` is
unset. This means you can pre-initialize a struct and any pre-populated
fields will not be overwritten during processing.
- Support for interpolation. The default value for a field can be the value of
another field.
- Support for arbitrary mutators that change/resolve data before type
conversion.
[godoc]: https://pkg.go.dev/mod/github.com/sethvargo/go-envconfig

View File

@@ -46,22 +46,7 @@
//
// export MYVAR="a:b,c:d" // map[string]string{"a":"b", "c":"d"}
//
// If you need to modify environment variable values before processing, you can
// specify a custom mutator:
//
// type Config struct {
// Password `env:"PASSWORD_SECRET"`
// }
//
// func resolveSecretFunc(ctx context.Context, key, value string) (string, error) {
// if strings.HasPrefix(value, "secret://") {
// return secretmanager.Resolve(ctx, value) // example
// }
// return value, nil
// }
//
// var config Config
// ProcessWith(&config, OsLookuper(), resolveSecretFunc)
// For more configuration options and examples, see the documentation.
package envconfig
import (
@@ -82,39 +67,37 @@ import (
const (
envTag = "env"
optDefault = "default="
optDelimiter = "delimiter="
optNoInit = "noinit"
optOverwrite = "overwrite"
optPrefix = "prefix="
optRequired = "required"
optSeparator = "separator="
defaultDelimiter = ","
defaultSeparator = ":"
optDecodeUnset = "decodeunset"
optDefault = "default="
optDelimiter = "delimiter="
optNoInit = "noinit"
optOverwrite = "overwrite"
optPrefix = "prefix="
optRequired = "required"
optSeparator = "separator="
)
// Error is a custom error type for errors returned by envconfig.
type Error string
// internalError is a custom error type for errors returned by envconfig.
type internalError string
// Error implements error.
func (e Error) Error() string {
func (e internalError) Error() string {
return string(e)
}
const (
ErrInvalidEnvvarName = Error("invalid environment variable name")
ErrInvalidMapItem = Error("invalid map item")
ErrLookuperNil = Error("lookuper cannot be nil")
ErrMissingKey = Error("missing key")
ErrMissingRequired = Error("missing required value")
ErrNoInitNotPtr = Error("field must be a pointer to have noinit")
ErrNotPtr = Error("input must be a pointer")
ErrNotStruct = Error("input must be a struct")
ErrPrefixNotStruct = Error("prefix is only valid on struct types")
ErrPrivateField = Error("cannot parse private fields")
ErrRequiredAndDefault = Error("field cannot be required and have a default value")
ErrUnknownOption = Error("unknown option")
ErrInvalidEnvvarName = internalError("invalid environment variable name")
ErrInvalidMapItem = internalError("invalid map item")
ErrLookuperNil = internalError("lookuper cannot be nil")
ErrMissingKey = internalError("missing key")
ErrMissingRequired = internalError("missing required value")
ErrNoInitNotPtr = internalError("field must be a pointer to have noinit")
ErrNotPtr = internalError("input must be a pointer")
ErrNotStruct = internalError("input must be a struct")
ErrPrefixNotStruct = internalError("prefix is only valid on struct types")
ErrPrivateField = internalError("cannot parse private fields")
ErrRequiredAndDefault = internalError("field cannot be required and have a default value")
ErrUnknownOption = internalError("unknown option")
)
// Lookuper is an interface that provides a lookup for a string-based key.
@@ -188,7 +171,24 @@ type prefixLookuper struct {
}
func (p *prefixLookuper) Lookup(key string) (string, bool) {
return p.l.Lookup(p.prefix + key)
return p.l.Lookup(p.Key(key))
}
func (p *prefixLookuper) Key(key string) string {
return p.prefix + key
}
func (p *prefixLookuper) Unwrap() Lookuper {
l := p.l
for v, ok := l.(unwrappableLookuper); ok; {
l = v.Unwrap()
}
return l
}
// unwrappableLookuper is a lookuper that can return the underlying lookuper.
type unwrappableLookuper interface {
Unwrap() Lookuper
}
// MultiLookuper wraps a collection of lookupers. It does not combine them, and
@@ -197,6 +197,12 @@ func MultiLookuper(lookupers ...Lookuper) Lookuper {
return &multiLookuper{ls: lookupers}
}
// keyedLookuper is an extension to the [Lookuper] interface that returns the
// underlying key (used by the [PrefixLookuper] or custom implementations).
type keyedLookuper interface {
Key(key string) string
}
// Decoder is an interface that custom types/fields can implement to control how
// decoding takes place. For example:
//
@@ -209,59 +215,95 @@ type Decoder interface {
EnvDecode(val string) error
}
// MutatorFunc is a function that mutates a given value before it is passed
// along for processing. This is useful if you want to mutate the environment
// variable value before it's converted to the proper type.
type MutatorFunc func(ctx context.Context, k, v string) (string, error)
// options are internal options for decoding.
type options struct {
Default string
Delimiter string
Prefix string
Separator string
NoInit bool
Overwrite bool
Required bool
Default string
Delimiter string
Prefix string
Separator string
NoInit bool
Overwrite bool
DecodeUnset bool
Required bool
}
// Process processes the struct using the environment. See [ProcessWith] for a
// more customizable version.
func Process(ctx context.Context, i interface{}) error {
return ProcessWith(ctx, i, OsLookuper())
// Config represent inputs to the envconfig decoding.
type Config struct {
// Target is the destination structure to decode. This value is required, and
// it must be a pointer to a struct.
Target any
// Lookuper is the lookuper implementation to use. If not provided, it
// defaults to the OS Lookuper.
Lookuper Lookuper
// DefaultDelimiter is the default value to use for the delimiter in maps and
// slices. This can be overridden on a per-field basis, which takes
// precedence. The default value is ",".
DefaultDelimiter string
// DefaultSeparator is the default value to use for the separator in maps.
// This can be overridden on a per-field basis, which takes precedence. The
// default value is ":".
DefaultSeparator string
// DefaultNoInit is the default value for skipping initialization of
// unprovided fields. The default value is false (deeply initialize all
// fields and nested structs).
DefaultNoInit bool
// DefaultOverwrite is the default value for overwriting an existing value set
// on the struct before processing. The default value is false.
DefaultOverwrite bool
// DefaultDecodeUnset is the default value for running decoders even when no
// value was given for the environment variable.
DefaultDecodeUnset bool
// DefaultRequired is the default value for marking a field as required. The
// default value is false.
DefaultRequired bool
// Mutators is an optiona list of mutators to apply to lookups.
Mutators []Mutator
}
// ProcessWith processes the given interface with the given lookuper. See the
// package-level documentation for specific examples and behaviors.
func ProcessWith(ctx context.Context, i interface{}, l Lookuper, fns ...MutatorFunc) error {
return processWith(ctx, i, l, false, fns...)
// Process decodes the struct using values from environment variables. See
// [ProcessWith] for a more customizable version.
func Process(ctx context.Context, i any, mus ...Mutator) error {
return ProcessWith(ctx, &Config{
Target: i,
Mutators: mus,
})
}
// ExtractDefaults is a helper that returns a fully-populated struct with the
// default values resolved. This is helpful when you want to return a constant
// "default" configuration that is not affected by the user's environment.
//
// type Config struct {
// Port string `env:"PORT,default=8080"`
// }
//
// func DefaultConfig() *Config {
// var cfg Config
// if err := envconfig.ExtractDefaults(ctx, &cfg); err != nil {
// panic("failed to extract default config: %s" + err.Error())
// }
// return &cfg
// }
//
// This is effectively the same as calling [ProcessWith] with an empty
// [MapLookuper].
func ExtractDefaults(ctx context.Context, i interface{}, fns ...MutatorFunc) error {
return processWith(ctx, i, MapLookuper(nil), false, fns...)
// ProcessWith executes the decoding process using the provided [Config].
func ProcessWith(ctx context.Context, c *Config) error {
if c == nil {
c = new(Config)
}
if c.Lookuper == nil {
c.Lookuper = OsLookuper()
}
// Deep copy the slice and remove any nil mutators.
var mus []Mutator
for _, m := range c.Mutators {
if m != nil {
mus = append(mus, m)
}
}
c.Mutators = mus
return processWith(ctx, c)
}
// processWith is a helper that captures whether the parent wanted
// initialization.
func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bool, fns ...MutatorFunc) error {
// processWith is a helper that retains configuration from the parent structs.
func processWith(ctx context.Context, c *Config) error {
i := c.Target
l := c.Lookuper
if l == nil {
return ErrLookuperNil
}
@@ -278,6 +320,24 @@ func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bo
t := e.Type()
structDelimiter := c.DefaultDelimiter
if structDelimiter == "" {
structDelimiter = ","
}
structNoInit := c.DefaultNoInit
structSeparator := c.DefaultSeparator
if structSeparator == "" {
structSeparator = ":"
}
structOverwrite := c.DefaultOverwrite
structDecodeUnset := c.DefaultDecodeUnset
structRequired := c.DefaultRequired
mutators := c.Mutators
for i := 0; i < t.NumField(); i++ {
ef := e.Field(i)
tf := t.Field(i)
@@ -308,7 +368,21 @@ func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bo
ef.Kind() != reflect.UnsafePointer {
return fmt.Errorf("%s: %w", tf.Name, ErrNoInitNotPtr)
}
shouldNotInit := opts.NoInit || parentNoInit
// Compute defaults from local tags.
delimiter := structDelimiter
if v := opts.Delimiter; v != "" {
delimiter = v
}
separator := structSeparator
if v := opts.Separator; v != "" {
separator = v
}
noInit := structNoInit || opts.NoInit
overwrite := structOverwrite || opts.Overwrite
decodeUnset := structDecodeUnset || opts.DecodeUnset
required := structRequired || opts.Required
isNilStructPtr := false
setNilStruct := func(v reflect.Value) {
@@ -319,7 +393,7 @@ func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bo
// If a struct (after traversal) equals to the empty value, it means
// nothing was changed in any sub-fields. With the noinit opt, we skip
// setting the empty value to the original struct pointer (keep it nil).
if !reflect.DeepEqual(v.Interface(), empty) || !shouldNotInit {
if !reflect.DeepEqual(v.Interface(), empty) || !noInit {
origin.Set(v)
}
}
@@ -354,18 +428,20 @@ func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bo
// Lookup the value, ignoring an error if the key isn't defined. This is
// required for nested structs that don't declare their own `env` keys,
// but have internal fields with an `env` defined.
val, _, _, err := lookup(key, opts, l)
val, found, usedDefault, err := lookup(key, required, opts.Default, l)
if err != nil && !errors.Is(err, ErrMissingKey) {
return fmt.Errorf("%s: %w", tf.Name, err)
}
if ok, err := processAsDecoder(val, ef); ok {
if err != nil {
return err
}
if found || usedDefault || decodeUnset {
if ok, err := processAsDecoder(val, ef); ok {
if err != nil {
return err
}
setNilStruct(ef)
continue
setNilStruct(ef)
continue
}
}
plu := l
@@ -373,7 +449,16 @@ func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bo
plu = PrefixLookuper(opts.Prefix, l)
}
if err := processWith(ctx, ef.Interface(), plu, shouldNotInit, fns...); err != nil {
if err := processWith(ctx, &Config{
Target: ef.Interface(),
Lookuper: plu,
DefaultDelimiter: delimiter,
DefaultSeparator: separator,
DefaultNoInit: noInit,
DefaultOverwrite: overwrite,
DefaultRequired: required,
Mutators: mutators,
}); err != nil {
return fmt.Errorf("%s: %w", tf.Name, err)
}
@@ -394,11 +479,11 @@ func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bo
// The field already has a non-zero value and overwrite is false, do not
// overwrite.
if (pointerWasSet || !ef.IsZero()) && !opts.Overwrite {
if (pointerWasSet || !ef.IsZero()) && !overwrite {
continue
}
val, found, usedDefault, err := lookup(key, opts, l)
val, found, usedDefault, err := lookup(key, required, opts.Default, l)
if err != nil {
return fmt.Errorf("%s: %w", tf.Name, err)
}
@@ -414,28 +499,27 @@ func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bo
// type conversions. They always resolve to a string (or error), so we don't
// call mutators when the environment variable was not set.
if found || usedDefault {
for _, fn := range fns {
if fn != nil {
val, err = fn(ctx, key, val)
if err != nil {
return fmt.Errorf("%s: %w", tf.Name, err)
}
originalKey := key
resolvedKey := originalKey
if keyer, ok := l.(keyedLookuper); ok {
resolvedKey = keyer.Key(resolvedKey)
}
originalValue := val
stop := false
for _, mu := range mutators {
val, stop, err = mu.EnvMutate(ctx, originalKey, resolvedKey, originalValue, val)
if err != nil {
return fmt.Errorf("%s: %w", tf.Name, err)
}
if stop {
break
}
}
}
// If Delimiter is not defined set it to ","
if opts.Delimiter == "" {
opts.Delimiter = defaultDelimiter
}
// If Separator is not defined set it to ":"
if opts.Separator == "" {
opts.Separator = defaultSeparator
}
// Set value.
if err := processField(val, ef, opts.Delimiter, opts.Separator, opts.NoInit); err != nil {
if err := processField(val, ef, delimiter, separator, noInit); err != nil {
return fmt.Errorf("%s(%q): %w", tf.Name, val, err)
}
}
@@ -443,10 +527,24 @@ func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bo
return nil
}
// SplitString splits the given string on the provided rune, unless the rune is
// escaped by the escape character.
func splitString(s string, on string, esc string) []string {
a := strings.Split(s, on)
for i := len(a) - 2; i >= 0; i-- {
if strings.HasSuffix(a[i], esc) {
a[i] = a[i][:len(a[i])-len(esc)] + on + a[i+1]
a = append(a[:i+1], a[i+2:]...)
}
}
return a
}
// keyAndOpts parses the given tag value (e.g. env:"foo,required") and
// returns the key name and options as a list.
func keyAndOpts(tag string) (string, *options, error) {
parts := strings.Split(tag, ",")
parts := splitString(tag, ",", "\\")
key, tagOpts := strings.TrimSpace(parts[0]), parts[1:]
if key != "" && !validateEnvName(key) {
@@ -458,20 +556,24 @@ func keyAndOpts(tag string) (string, *options, error) {
LOOP:
for i, o := range tagOpts {
o = strings.TrimLeftFunc(o, unicode.IsSpace)
search := strings.ToLower(o)
switch {
case o == optOverwrite:
case search == optDecodeUnset:
opts.DecodeUnset = true
case search == optOverwrite:
opts.Overwrite = true
case o == optRequired:
case search == optRequired:
opts.Required = true
case o == optNoInit:
case search == optNoInit:
opts.NoInit = true
case strings.HasPrefix(o, optPrefix):
case strings.HasPrefix(search, optPrefix):
opts.Prefix = strings.TrimPrefix(o, optPrefix)
case strings.HasPrefix(o, optDelimiter):
case strings.HasPrefix(search, optDelimiter):
opts.Delimiter = strings.TrimPrefix(o, optDelimiter)
case strings.HasPrefix(o, optSeparator):
case strings.HasPrefix(search, optSeparator):
opts.Separator = strings.TrimPrefix(o, optSeparator)
case strings.HasPrefix(o, optDefault):
case strings.HasPrefix(search, optDefault):
// If a default value was given, assume everything after is the provided
// value, including comma-seprated items.
o = strings.TrimLeft(strings.Join(tagOpts[i:], ","), " ")
@@ -489,7 +591,7 @@ LOOP:
// first boolean parameter indicates whether the value was found in the
// lookuper. The second boolean parameter indicates whether the default value
// was used.
func lookup(key string, opts *options, l Lookuper) (string, bool, bool, error) {
func lookup(key string, required bool, defaultValue string, l Lookuper) (string, bool, bool, error) {
if key == "" {
// The struct has something like `env:",required"`, which is likely a
// mistake. We could try to infer the envvar from the field name, but that
@@ -497,7 +599,7 @@ func lookup(key string, opts *options, l Lookuper) (string, bool, bool, error) {
return "", false, false, ErrMissingKey
}
if opts.Required && opts.Default != "" {
if required && defaultValue != "" {
// Having a default value on a required value doesn't make sense.
return "", false, false, ErrRequiredAndDefault
}
@@ -505,19 +607,24 @@ func lookup(key string, opts *options, l Lookuper) (string, bool, bool, error) {
// Lookup value.
val, found := l.Lookup(key)
if !found {
if opts.Required {
if pl, ok := l.(*prefixLookuper); ok {
key = pl.prefix + key
if required {
if keyer, ok := l.(keyedLookuper); ok {
key = keyer.Key(key)
}
return "", false, false, fmt.Errorf("%w: %s", ErrMissingRequired, key)
}
if opts.Default != "" {
if defaultValue != "" {
// Expand the default value. This allows for a default value that maps to
// a different variable.
val = os.Expand(opts.Default, func(i string) string {
s, ok := l.Lookup(i)
// a different environment variable.
val = os.Expand(defaultValue, func(i string) string {
lookuper := l
if v, ok := lookuper.(unwrappableLookuper); ok {
lookuper = v.Unwrap()
}
s, ok := lookuper.Lookup(i)
if ok {
return s
}

76
vendor/github.com/sethvargo/go-envconfig/mutator.go generated vendored Normal file
View File

@@ -0,0 +1,76 @@
// Copyright The envconfig Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package envconfig
import "context"
// Mutator is the interface for a mutator function. Mutators act like middleware
// and alter values for subsequent processing. This is useful if you want to
// mutate the environment variable value before it's converted to the proper
// type.
//
// Mutators are only called on defined values (or when decodeunset is true).
type Mutator interface {
// EnvMutate is called to alter the environment variable value.
//
// - `originalKey` is the unmodified environment variable name as it was defined
// on the struct.
//
// - `resolvedKey` is the fully-resolved environment variable name, which may
// include prefixes or modifications from processing. When there are
// no modifications, this will be equivalent to `originalKey`.
//
// - `originalValue` is the unmodified environment variable's value before any
// mutations were run.
//
// - `currentValue` is the currently-resolved value, which may have been
// modified by previous mutators and may be modified in the future by
// subsequent mutators in the stack.
//
// The function returns (in order):
//
// - The new value to use in both future mutations and final processing.
//
// - A boolean which indicates whether future mutations in the stack should be
// applied.
//
// - Any errors that occurred.
//
EnvMutate(ctx context.Context, originalKey, resolvedKey, originalValue, currentValue string) (newValue string, stop bool, err error)
}
var _ Mutator = (MutatorFunc)(nil)
// MutatorFunc implements the [Mutator] and provides a quick way to create an
// anonymous function.
type MutatorFunc func(ctx context.Context, originalKey, resolvedKey, originalValue, currentValue string) (newValue string, stop bool, err error)
// EnvMutate implements [Mutator].
func (m MutatorFunc) EnvMutate(ctx context.Context, originalKey, resolvedKey, originalValue, currentValue string) (newValue string, stop bool, err error) {
return m(ctx, originalKey, resolvedKey, originalValue, currentValue)
}
// LegacyMutatorFunc is a helper that eases the transition from the previous
// MutatorFunc signature. It wraps the previous-style mutator function and
// returns a new one. Since the former mutator function had less data, this is
// inherently lossy.
//
// Deprecated: Use [MutatorFunc] instead.
func LegacyMutatorFunc(fn func(ctx context.Context, key, value string) (string, error)) MutatorFunc {
return func(ctx context.Context, originalKey, resolvedKey, originalValue, currentValue string) (newValue string, stop bool, err error) {
v, err := fn(ctx, originalKey, currentValue)
return v, true, err
}
}

4
vendor/modules.txt generated vendored
View File

@@ -801,8 +801,8 @@ github.com/segmentio/backo-go
# github.com/sergi/go-diff v1.3.1
## explicit; go 1.12
github.com/sergi/go-diff/diffmatchpatch
# github.com/sethvargo/go-envconfig v0.9.0
## explicit; go 1.17
# github.com/sethvargo/go-envconfig v1.0.0
## explicit; go 1.21
github.com/sethvargo/go-envconfig
# github.com/sirupsen/logrus v1.9.2
## explicit; go 1.13