Skip to content

Commit 432567c

Browse files
authoredJun 19, 2024··
fix: retract v11.0.1, gate init nil pointers (#318)
* 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 <caarlos0@users.noreply.github.com> * fix: typo * perf: cheap first Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com> --------- Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
1 parent de7a9cc commit 432567c

File tree

4 files changed

+101
-8
lines changed

4 files changed

+101
-8
lines changed
 

‎README.md

+14
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,20 @@ $ PORT=8080 go run main.go
227227
{Host:localhost Port:8080 Address:localhost:8080}
228228
```
229229

230+
## Init `nil` pointers
231+
232+
You can automatically initialize `nil` pointers regardless of if a variable is
233+
set for them or not.
234+
This behavior can be enabled by using the `init` tag option.
235+
236+
Example:
237+
238+
```go
239+
type config struct {
240+
URL *url.URL `env:"URL,init"`
241+
}
242+
```
243+
230244
## Not Empty fields
231245

232246
While `required` demands the environment variable to be set, it doesn't check

‎env.go

+7-4
Original file line numberDiff line numberDiff line change
@@ -300,13 +300,13 @@ func doParseField(refField reflect.Value, refTypeField reflect.StructField, proc
300300
return err
301301
}
302302

303-
if isStructPtr(refField) && refField.IsNil() {
303+
if params.Init && isStructPtr(refField) && refField.IsNil() {
304304
refField.Set(reflect.New(refField.Type().Elem()))
305305
refField = refField.Elem()
306-
}
307306

308-
if _, ok := opts.FuncMap[refField.Type()]; ok {
309-
return nil
307+
if _, ok := opts.FuncMap[refField.Type()]; ok {
308+
return nil
309+
}
310310
}
311311

312312
if reflect.Struct == refField.Kind() {
@@ -361,6 +361,7 @@ type FieldParams struct {
361361
Unset bool
362362
NotEmpty bool
363363
Expand bool
364+
Init bool
364365
}
365366

366367
func parseFieldParams(field reflect.StructField, opts Options) (FieldParams, error) {
@@ -393,6 +394,8 @@ func parseFieldParams(field reflect.StructField, opts Options) (FieldParams, err
393394
result.NotEmpty = true
394395
case "expand":
395396
result.Expand = true
397+
case "init":
398+
result.Init = true
396399
default:
397400
return FieldParams{}, newNoSupportedTagOptionError(tag)
398401
}

‎env_test.go

+78-4
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,10 @@ type Config struct {
137137
}
138138

139139
type ParentStruct struct {
140-
InnerStruct *InnerStruct
141-
unexported *InnerStruct
142-
Ignored *http.Client
140+
InnerStruct *InnerStruct `env:",init"`
141+
NilInnerStruct *InnerStruct
142+
unexported *InnerStruct
143+
Ignored *http.Client
143144
}
144145

145146
type InnerStruct struct {
@@ -2041,7 +2042,7 @@ func TestIssue234(t *testing.T) {
20412042
Str string `env:"TEST"`
20422043
}
20432044
type ComplexConfig struct {
2044-
Foo *Test `envPrefix:"FOO_"`
2045+
Foo *Test `envPrefix:"FOO_" env:",init"`
20452046
Bar Test `envPrefix:"BAR_"`
20462047
Clean *Test
20472048
}
@@ -2077,3 +2078,76 @@ func TestIssue308(t *testing.T) {
20772078
isNoErr(t, Parse(&cfg))
20782079
isEqual(t, Issue308Map{"FOO": []string{"BAR", "ZAZ"}}, cfg.Inner)
20792080
}
2081+
2082+
func TestIssue317(t *testing.T) {
2083+
type TestConfig struct {
2084+
U1 *url.URL `env:"U1"`
2085+
U2 *url.URL `env:"U2,init"`
2086+
}
2087+
cases := []struct {
2088+
desc string
2089+
environment map[string]string
2090+
expectedU1, expectedU2 *url.URL
2091+
}{
2092+
{
2093+
desc: "unset",
2094+
environment: map[string]string{},
2095+
expectedU1: nil,
2096+
expectedU2: &url.URL{},
2097+
},
2098+
{
2099+
desc: "empty",
2100+
environment: map[string]string{"U1": "", "U2": ""},
2101+
expectedU1: nil,
2102+
expectedU2: &url.URL{},
2103+
},
2104+
{
2105+
desc: "set",
2106+
environment: map[string]string{"U1": "https://example.com/"},
2107+
expectedU1: &url.URL{Scheme: "https", Host: "example.com", Path: "/"},
2108+
expectedU2: &url.URL{},
2109+
},
2110+
}
2111+
for _, tc := range cases {
2112+
t.Run(tc.desc, func(t *testing.T) {
2113+
cfg := TestConfig{}
2114+
err := ParseWithOptions(&cfg, Options{Environment: tc.environment})
2115+
isNoErr(t, err)
2116+
isEqual(t, tc.expectedU1, cfg.U1)
2117+
isEqual(t, tc.expectedU2, cfg.U2)
2118+
})
2119+
}
2120+
}
2121+
2122+
func TestIssue310(t *testing.T) {
2123+
type TestConfig struct {
2124+
URL *url.URL
2125+
}
2126+
cfg, err := ParseAs[TestConfig]()
2127+
isNoErr(t, err)
2128+
isEqual(t, nil, cfg.URL)
2129+
}
2130+
2131+
func TestMultipleTagOptions(t *testing.T) {
2132+
type TestConfig struct {
2133+
URL *url.URL `env:"URL,init,unset"`
2134+
}
2135+
t.Run("unset", func(t *testing.T) {
2136+
cfg, err := ParseAs[TestConfig]()
2137+
isNoErr(t, err)
2138+
isEqual(t, &url.URL{}, cfg.URL)
2139+
})
2140+
t.Run("empty", func(t *testing.T) {
2141+
t.Setenv("URL", "")
2142+
cfg, err := ParseAs[TestConfig]()
2143+
isNoErr(t, err)
2144+
isEqual(t, &url.URL{}, cfg.URL)
2145+
})
2146+
t.Run("set", func(t *testing.T) {
2147+
t.Setenv("URL", "https://github.com/caarlos0")
2148+
cfg, err := ParseAs[TestConfig]()
2149+
isNoErr(t, err)
2150+
isEqual(t, &url.URL{Scheme: "https", Host: "github.com", Path: "/caarlos0"}, cfg.URL)
2151+
isEqual(t, "", os.Getenv("URL"))
2152+
})
2153+
}

‎go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
module github.com/caarlos0/env/v11
22

3+
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.
4+
35
go 1.18

0 commit comments

Comments
 (0)
Please sign in to comment.