Skip to content

Commit

Permalink
Add notempty
Browse files Browse the repository at this point in the history
  • Loading branch information
Serhii Skrypnik committed May 8, 2024
1 parent 7e286de commit 70a2ee2
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 4 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ examples.
}
```

- `notempty` - marks a field as required to be not empty. If a field is required to be not empty, decoding
will error if the environment variable is not set or is set to an empty string.

```go
type MyStruct struct {
Port string `env:"PORT, notempty"`
}
```

- `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"
Expand Down
17 changes: 13 additions & 4 deletions envconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const (
optOverwrite = "overwrite"
optPrefix = "prefix="
optRequired = "required"
optNotEmpty = "notempty"
optSeparator = "separator="
)

Expand All @@ -91,6 +92,7 @@ const (
ErrLookuperNil = internalError("lookuper cannot be nil")
ErrMissingKey = internalError("missing key")
ErrMissingRequired = internalError("missing required value")
ErrEmptyValue = internalError("empty 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")
Expand Down Expand Up @@ -225,6 +227,7 @@ type options struct {
Overwrite bool
DecodeUnset bool
Required bool
NotEmpty bool
}

// Config represent inputs to the envconfig decoding.
Expand Down Expand Up @@ -428,7 +431,7 @@ func processWith(ctx context.Context, c *Config) error {
// 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, found, usedDefault, err := lookup(key, required, opts.Default, l)
val, found, usedDefault, err := lookup(key, required, opts.NotEmpty, opts.Default, l)
if err != nil && !errors.Is(err, ErrMissingKey) {
return fmt.Errorf("%s: %w", tf.Name, err)
}
Expand Down Expand Up @@ -483,7 +486,7 @@ func processWith(ctx context.Context, c *Config) error {
continue
}

val, found, usedDefault, err := lookup(key, required, opts.Default, l)
val, found, usedDefault, err := lookup(key, required, opts.NotEmpty, opts.Default, l)
if err != nil {
return fmt.Errorf("%s: %w", tf.Name, err)
}
Expand Down Expand Up @@ -565,6 +568,8 @@ LOOP:
opts.Overwrite = true
case search == optRequired:
opts.Required = true
case search == optNotEmpty:
opts.NotEmpty = true
case search == optNoInit:
opts.NoInit = true
case strings.HasPrefix(search, optPrefix):
Expand All @@ -591,15 +596,15 @@ 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, required bool, defaultValue string, l Lookuper) (string, bool, bool, error) {
func lookup(key string, required bool, notEmpty 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
// feels too magical.
return "", false, false, ErrMissingKey
}

if required && defaultValue != "" {
if (required || notEmpty) && defaultValue != "" {
// Having a default value on a required value doesn't make sense.
return "", false, false, ErrRequiredAndDefault
}
Expand Down Expand Up @@ -635,6 +640,10 @@ func lookup(key string, required bool, defaultValue string, l Lookuper) (string,
}
}

if notEmpty && val == "" {
return "", false, false, fmt.Errorf("%w: %s", ErrEmptyValue, key)
}

return val, found, false, nil
}

Expand Down
40 changes: 40 additions & 0 deletions envconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,46 @@ func TestProcessWith(t *testing.T) {
lookuper: MapLookuper(nil),
},

// NotEmpty
{
name: "notempty/empty",
target: &struct {
Field string `env:"FIELD,notempty"`
}{},
exp: &struct {
Field string `env:"FIELD,notempty"`
}{},
lookuper: MapLookuper(map[string]string{
"FIELD": "",
}),
err: ErrEmptyValue,
},
{
name: "notempty/notempty",
target: &struct {
Field string `env:"FIELD,notempty"`
}{},
exp: &struct {
Field string `env:"FIELD,notempty"`
}{
Field: "foobar",
},
lookuper: MapLookuper(map[string]string{
"FIELD": "foobar",
}),
},
{
name: "notempty/notempty-default",
target: &struct {
Field string `env:"FIELD,notempty,default=bar"`
}{},
exp: &struct {
Field string `env:"FIELD,notempty,default=bar"`
}{},
lookuper: MapLookuper(nil),
err: ErrRequiredAndDefault,
},

// Required
{
name: "required/present",
Expand Down

0 comments on commit 70a2ee2

Please sign in to comment.