diff --git a/README.md b/README.md index 9a693a9..4282b73 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,20 @@ $ PORT=8080 go run main.go {Host:localhost Port:8080 Address:localhost:8080} ``` +## Init `nil` pointers + +You can automatically initialize `nil` pointers regardless of if a variable is +set for them or not. +This behavior can be enabled by using the `init` tag option. + +Example: + +```go +type config struct { + URL *url.URL `env:"URL,init"` +} +``` + ## Not Empty fields While `required` demands the environment variable to be set, it doesn't check diff --git a/env.go b/env.go index d4bdf55..8b8b724 100644 --- a/env.go +++ b/env.go @@ -300,13 +300,13 @@ func doParseField(refField reflect.Value, refTypeField reflect.StructField, proc return err } - if isStructPtr(refField) && refField.IsNil() { + if params.Init && isStructPtr(refField) && refField.IsNil() { refField.Set(reflect.New(refField.Type().Elem())) refField = refField.Elem() - } - if _, ok := opts.FuncMap[refField.Type()]; ok { - return nil + if _, ok := opts.FuncMap[refField.Type()]; ok { + return nil + } } if reflect.Struct == refField.Kind() { @@ -361,6 +361,7 @@ type FieldParams struct { Unset bool NotEmpty bool Expand bool + Init bool } func parseFieldParams(field reflect.StructField, opts Options) (FieldParams, error) { @@ -393,6 +394,8 @@ func parseFieldParams(field reflect.StructField, opts Options) (FieldParams, err result.NotEmpty = true case "expand": result.Expand = true + case "init": + result.Init = true default: return FieldParams{}, newNoSupportedTagOptionError(tag) } diff --git a/env_test.go b/env_test.go index f322e10..2891fac 100644 --- a/env_test.go +++ b/env_test.go @@ -137,9 +137,10 @@ type Config struct { } type ParentStruct struct { - InnerStruct *InnerStruct - unexported *InnerStruct - Ignored *http.Client + InnerStruct *InnerStruct `env:",init"` + NilInnerStruct *InnerStruct + unexported *InnerStruct + Ignored *http.Client } type InnerStruct struct { @@ -2041,7 +2042,7 @@ func TestIssue234(t *testing.T) { Str string `env:"TEST"` } type ComplexConfig struct { - Foo *Test `envPrefix:"FOO_"` + Foo *Test `envPrefix:"FOO_" env:",init"` Bar Test `envPrefix:"BAR_"` Clean *Test } @@ -2077,3 +2078,76 @@ func TestIssue308(t *testing.T) { isNoErr(t, Parse(&cfg)) isEqual(t, Issue308Map{"FOO": []string{"BAR", "ZAZ"}}, cfg.Inner) } + +func TestIssue317(t *testing.T) { + type TestConfig struct { + U1 *url.URL `env:"U1"` + U2 *url.URL `env:"U2,init"` + } + cases := []struct { + desc string + environment map[string]string + expectedU1, expectedU2 *url.URL + }{ + { + desc: "unset", + environment: map[string]string{}, + expectedU1: nil, + expectedU2: &url.URL{}, + }, + { + desc: "empty", + environment: map[string]string{"U1": "", "U2": ""}, + expectedU1: nil, + expectedU2: &url.URL{}, + }, + { + desc: "set", + environment: map[string]string{"U1": "https://example.com/"}, + expectedU1: &url.URL{Scheme: "https", Host: "example.com", Path: "/"}, + expectedU2: &url.URL{}, + }, + } + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + cfg := TestConfig{} + err := ParseWithOptions(&cfg, Options{Environment: tc.environment}) + isNoErr(t, err) + isEqual(t, tc.expectedU1, cfg.U1) + isEqual(t, tc.expectedU2, cfg.U2) + }) + } +} + +func TestIssue310(t *testing.T) { + type TestConfig struct { + URL *url.URL + } + cfg, err := ParseAs[TestConfig]() + isNoErr(t, err) + isEqual(t, nil, cfg.URL) +} + +func TestMultipleTagOptions(t *testing.T) { + type TestConfig struct { + URL *url.URL `env:"URL,init,unset"` + } + t.Run("unset", func(t *testing.T) { + cfg, err := ParseAs[TestConfig]() + isNoErr(t, err) + isEqual(t, &url.URL{}, cfg.URL) + }) + t.Run("empty", func(t *testing.T) { + t.Setenv("URL", "") + cfg, err := ParseAs[TestConfig]() + isNoErr(t, err) + isEqual(t, &url.URL{}, cfg.URL) + }) + t.Run("set", func(t *testing.T) { + t.Setenv("URL", "https://github.com/caarlos0") + cfg, err := ParseAs[TestConfig]() + isNoErr(t, err) + isEqual(t, &url.URL{Scheme: "https", Host: "github.com", Path: "/caarlos0"}, cfg.URL) + isEqual(t, "", os.Getenv("URL")) + }) +} diff --git a/go.mod b/go.mod index 135b454..215c28b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/caarlos0/env/v11 +retract v11.0.1 // v11.0.1 accidentally introduced a breaking change regarding the behavior of nil pointers. You can now chose to auto-initialize them by setting the `init` tag option. + go 1.18