From e65b4f9d896885e3dc16b4b274c08f64885e9b89 Mon Sep 17 00:00:00 2001 From: Seth Pollack Date: Tue, 8 Oct 2024 14:11:57 -0400 Subject: [PATCH] add initIfSet option --- README.md | 1 + env.go | 27 ++++++++++++++++++++++++++- env_test.go | 19 +++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 352a8c9..1eb753e 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ Here are all the options available for the `env` tag: - `,expand`: expands environment variables, e.g. `FOO_${BAR}` - `,file`: instructs that the content of the variable is a path to a file that should be read - `,init`: initialize nil pointers +- `,initIfSet`: initialize nil pointers when a value is found in the environment. - `,notEmpty`: make the field errors if the environment variable is empty - `,required`: make the field errors if the environment variable is not set - `,unset`: unset the environment variable after use diff --git a/env.go b/env.go index 246d6c7..51672f8 100644 --- a/env.go +++ b/env.go @@ -371,7 +371,7 @@ func doParseField( return err } - if params.Init && isStructPtr(refField) && refField.IsNil() { + if isStructPtr(refField) && refField.IsNil() && shouldInit(params, optionsWithEnvPrefix(refTypeField, opts)) { refField.Set(reflect.New(refField.Type().Elem())) refField = refField.Elem() @@ -391,6 +391,18 @@ func doParseField( return nil } +func shouldInit(fieldParams FieldParams, opts Options) bool { + if fieldParams.Init { + return true + } + + if fieldParams.InitIfSet { + return hasValue(opts) + } + + return false +} + func isSliceOfStructs(refTypeField reflect.StructField, opts Options) bool { field := refTypeField.Type if reflect.Ptr == field.Kind() { @@ -533,6 +545,7 @@ type FieldParams struct { NotEmpty bool Expand bool Init bool + InitIfSet bool } func parseFieldParams(field reflect.StructField, opts Options) (FieldParams, error) { @@ -567,6 +580,8 @@ func parseFieldParams(field reflect.StructField, opts Options) (FieldParams, err result.Expand = true case "init": result.Init = true + case "initIfSet": + result.InitIfSet = true default: return FieldParams{}, newNoSupportedTagOptionError(tag) } @@ -619,6 +634,16 @@ func get(fieldParams FieldParams, opts Options) (val string, err error) { return val, err } +// hasValue checks if the struct has any values in the environment variables. +func hasValue(opts Options) bool { + for key, _ := range opts.Environment { + if strings.HasPrefix(key, opts.Prefix) { + return true + } + } + return false +} + // split the env tag's key into the expected key and desired option, if any. func parseKeyForOption(key string) (string, []string) { opts := strings.Split(key, ",") diff --git a/env_test.go b/env_test.go index 8a4daee..9c20158 100644 --- a/env_test.go +++ b/env_test.go @@ -2214,3 +2214,22 @@ func TestParseWithOptionsRenamedPrefix(t *testing.T) { isNoErr(t, Parse(cfg)) isEqual(t, "101", cfg.Foo.Str) } + +func TestInitIfSet(t *testing.T) { + type Test struct { + Str string `env:"TEST"` + } + type ComplexConfig struct { + Foo *Test `envPrefix:"FOO_" env:",init"` + Bar *Test `envPrefix:"BAR_" env:",initIfSet"` + Baz *Test `envPrefix:"BAZ_" env:",initIfSet"` + } + + t.Setenv("BAR_TEST", "lel") + + cfg := ComplexConfig{} + isNoErr(t, Parse(&cfg)) + isEqual(t, &Test{}, cfg.Foo) + isEqual(t, &Test{Str: "lel"}, cfg.Bar) + isEqual(t, nil, cfg.Baz) +}