Skip to content

Commit

Permalink
fix: retract v11.0.1, gate init nil pointers (#318)
Browse files Browse the repository at this point in the history
* fix: retract v11.0.1, gate init nil pointers

as it would automatically initialize nil pointers.

this retracts that version, and gate this new feature behind an `init`
tag option.

closes #317
refs  #306

* test: make sure #310 is covered too

closes #310

* test: add test for multiple tag options

Signed-off-by: Carlos Alexandro Becker <[email protected]>

* fix: typo

* perf: cheap first

Signed-off-by: Carlos Alexandro Becker <[email protected]>

---------

Signed-off-by: Carlos Alexandro Becker <[email protected]>
  • Loading branch information
caarlos0 committed Jun 19, 2024
1 parent de7a9cc commit 432567c
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 8 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 7 additions & 4 deletions env.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -361,6 +361,7 @@ type FieldParams struct {
Unset bool
NotEmpty bool
Expand bool
Init bool
}

func parseFieldParams(field reflect.StructField, opts Options) (FieldParams, error) {
Expand Down Expand Up @@ -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)
}
Expand Down
82 changes: 78 additions & 4 deletions env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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"))
})
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 432567c

Please sign in to comment.