Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ Here are all the options available for the `env` tag:
- `,file`: instructs that the content of the variable is a path to a file that should be read
- `,init`: initialize nil pointers
- `,notEmpty`: make the field errors if the environment variable is empty
- `,notEmptyIfDefined`: make the field errors if the environment variable is defined but empty (unset falls through to `envDefault`)
- `,required`: make the field errors if the environment variable is not set
- `,unset`: unset the environment variable after use

Expand Down
31 changes: 20 additions & 11 deletions env.go
Original file line number Diff line number Diff line change
Expand Up @@ -534,17 +534,18 @@ func toEnvName(input string) string {

// FieldParams contains information about parsed field tags.
type FieldParams struct {
OwnKey string
Key string
DefaultValue string
HasDefaultValue bool
Required bool
LoadFile bool
Unset bool
NotEmpty bool
Expand bool
Init bool
Ignored bool
OwnKey string
Key string
DefaultValue string
HasDefaultValue bool
Required bool
LoadFile bool
Unset bool
NotEmpty bool
NotEmptyIfDefined bool
Expand bool
Init bool
Ignored bool
}

func parseFieldParams(field reflect.StructField, opts Options) (FieldParams, error) {
Expand Down Expand Up @@ -576,6 +577,8 @@ func parseFieldParams(field reflect.StructField, opts Options) (FieldParams, err
result.Unset = true
case "notEmpty":
result.NotEmpty = true
case "notEmptyIfDefined":
result.NotEmptyIfDefined = true
case "expand":
result.Expand = true
case "init":
Expand Down Expand Up @@ -618,6 +621,12 @@ func get(fieldParams FieldParams, opts Options) (val string, err error) {
return "", newEmptyVarError(fieldParams.Key)
}

if fieldParams.NotEmptyIfDefined {
if raw, defined := opts.Environment[fieldParams.Key]; defined && raw == "" {
return "", newEmptyVarError(fieldParams.Key)
}
}

if fieldParams.LoadFile && val != "" {
filename := val
val, err = getFromFile(filename)
Expand Down
62 changes: 52 additions & 10 deletions env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1757,16 +1757,17 @@ func TestErrorIs(t *testing.T) {
}

type FieldParamsConfig struct {
Simple []string `env:"SIMPLE"`
WithoutEnv string
privateWithEnv string `env:"PRIVATE_WITH_ENV"` //nolint:unused
WithDefault string `env:"WITH_DEFAULT" envDefault:"default"`
Required string `env:"REQUIRED,required"`
File string `env:"FILE,file"`
Unset string `env:"UNSET,unset"`
NotEmpty string `env:"NOT_EMPTY,notEmpty"`
Expand string `env:"EXPAND,expand"`
NestedConfig struct {
Simple []string `env:"SIMPLE"`
WithoutEnv string
privateWithEnv string `env:"PRIVATE_WITH_ENV"` //nolint:unused
WithDefault string `env:"WITH_DEFAULT" envDefault:"default"`
Required string `env:"REQUIRED,required"`
File string `env:"FILE,file"`
Unset string `env:"UNSET,unset"`
NotEmpty string `env:"NOT_EMPTY,notEmpty"`
NotEmptyIfDefined string `env:"NOT_EMPTY_IF_DEFINED,notEmptyIfDefined"`
Expand string `env:"EXPAND,expand"`
NestedConfig struct {
Simple []string `env:"SIMPLE"`
} `envPrefix:"NESTED_"`
}
Expand All @@ -1783,6 +1784,7 @@ func TestGetFieldParams(t *testing.T) {
{OwnKey: "FILE", Key: "FILE", LoadFile: true},
{OwnKey: "UNSET", Key: "UNSET", Unset: true},
{OwnKey: "NOT_EMPTY", Key: "NOT_EMPTY", NotEmpty: true},
{OwnKey: "NOT_EMPTY_IF_DEFINED", Key: "NOT_EMPTY_IF_DEFINED", NotEmptyIfDefined: true},
{OwnKey: "EXPAND", Key: "EXPAND", Expand: true},
{OwnKey: "SIMPLE", Key: "NESTED_SIMPLE"},
}
Expand All @@ -1803,6 +1805,7 @@ func TestGetFieldParamsWithPrefix(t *testing.T) {
{OwnKey: "FILE", Key: "FOO_FILE", LoadFile: true},
{OwnKey: "UNSET", Key: "FOO_UNSET", Unset: true},
{OwnKey: "NOT_EMPTY", Key: "FOO_NOT_EMPTY", NotEmpty: true},
{OwnKey: "NOT_EMPTY_IF_DEFINED", Key: "FOO_NOT_EMPTY_IF_DEFINED", NotEmptyIfDefined: true},
{OwnKey: "EXPAND", Key: "FOO_EXPAND", Expand: true},
{OwnKey: "SIMPLE", Key: "FOO_NESTED_SIMPLE"},
}
Expand Down Expand Up @@ -2417,3 +2420,42 @@ func TestEnvBleed(t *testing.T) {
isEqual(t, "", cfg.Foo)
})
}

func TestNotEmptyIfDefinedSetEmpty(t *testing.T) {
t.Setenv("IS_REQUIRED", "")
type config struct {
IsRequired string `env:"IS_REQUIRED,notEmptyIfDefined"`
}
err := Parse(&config{})
isErrorWithMessage(t, err, `env: environment variable "IS_REQUIRED" should not be empty`)
isTrue(t, errors.Is(err, EmptyVarError{}))
}

func TestNotEmptyIfDefinedSetEmptyWithDefault(t *testing.T) {
t.Setenv("IS_REQUIRED", "")
type config struct {
IsRequired string `env:"IS_REQUIRED,notEmptyIfDefined" envDefault:"important"`
}
err := Parse(&config{})
isErrorWithMessage(t, err, `env: environment variable "IS_REQUIRED" should not be empty`)
isTrue(t, errors.Is(err, EmptyVarError{}))
}

func TestNotEmptyIfDefinedNotSetWithDefault(t *testing.T) {
type config struct {
IsRequired string `env:"IS_REQUIRED,notEmptyIfDefined" envDefault:"important"`
}
cfg := &config{}
isNoErr(t, Parse(cfg))
isEqual(t, "important", cfg.IsRequired)
}

func TestNotEmptyIfDefinedSetValue(t *testing.T) {
t.Setenv("IS_REQUIRED", "value")
type config struct {
IsRequired string `env:"IS_REQUIRED,notEmptyIfDefined"`
}
cfg := &config{}
isNoErr(t, Parse(cfg))
isEqual(t, "value", cfg.IsRequired)
}