Skip to content

Commit 011a926

Browse files
authored
refactor: Add error handling to env parsers (#111)
* refactor: Add error handling to env parsers * refactor: Get rid of internal default value parsers * style: Fix linter warnings
1 parent 1887d16 commit 011a926

File tree

9 files changed

+1689
-1269
lines changed

9 files changed

+1689
-1269
lines changed

getenv.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ package getenv
4949
import (
5050
"errors"
5151
"fmt"
52-
"os"
53-
"reflect"
5452

5553
"github.com/obalunenko/getenv/internal"
5654
"github.com/obalunenko/getenv/option"
@@ -64,40 +62,42 @@ var (
6462
)
6563

6664
// Env retrieves the value of the environment variable named by the key.
67-
// If the variable is present in the environment the value will be parsed and returned.
65+
// If the variable is present in the environment, the value will be parsed and returned.
6866
// Otherwise, an error will be returned.
6967
func Env[T internal.EnvParsable](key string, options ...option.Option) (T, error) {
70-
// Create a default value of the same type as the value that we want to get.
71-
var defVal T
68+
var t T
69+
70+
w := internal.NewEnvParser(t)
7271

73-
val := EnvOrDefault(key, defVal, options...)
72+
params := newParseParams(options)
73+
74+
val, err := w.ParseEnv(key, params)
75+
if err != nil {
76+
if errors.Is(err, internal.ErrNotSet) {
77+
return t, fmt.Errorf("failed to get environment variable[%s]: %w", key, ErrNotSet)
78+
}
7479

75-
// If the value is equal to the default value, it means that the value was not parsed.
76-
// This means that the environment variable was not set, or it was set to an invalid value.
77-
if reflect.DeepEqual(val, defVal) {
78-
v, ok := os.LookupEnv(key)
79-
if !ok {
80-
return val, fmt.Errorf("could not get variable[%s]: %w", key, ErrNotSet)
80+
if errors.Is(err, internal.ErrInvalidValue) {
81+
return t, fmt.Errorf("failed to parse environment variable[%s]: %w", key, ErrInvalidValue)
8182
}
8283

83-
return val, fmt.Errorf("could not parse variable[%s] value[%v] to type[%T]: %w", key, v, defVal, ErrInvalidValue)
84+
return t, fmt.Errorf("failed to parse environment variable[%s]: %w", key, err)
8485
}
8586

86-
return val, nil
87+
return val.(T), nil
8788
}
8889

8990
// EnvOrDefault retrieves the value of the environment variable named by the key.
9091
// If the variable is present in the environment the value will be parsed and returned.
9192
// Otherwise, the default value will be returned.
9293
// The value returned will be of the same type as the default value.
9394
func EnvOrDefault[T internal.EnvParsable](key string, defaultVal T, options ...option.Option) T {
94-
w := internal.NewEnvParser(defaultVal)
95-
96-
params := newParseParams(options)
97-
98-
val := w.ParseEnv(key, defaultVal, params)
95+
val, err := Env[T](key, options...)
96+
if err != nil {
97+
return defaultVal
98+
}
9999

100-
return val.(T)
100+
return val
101101
}
102102

103103
// newParseParams creates new parameters from options.

getenv_example_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ func ExampleEnv() {
229229
// Output:
230230
// [string]: golly; err: <nil>
231231
// [int]: 123; err: <nil>
232-
// [int]: 0; err: could not parse variable[GH_GETENV_TEST] value[123s4] to type[int]: invalid value
232+
// [int]: 0; err: failed to parse environment variable[GH_GETENV_TEST]: invalid value
233233
// [time.Time]: 2022-01-20 00:00:00 +0000 UTC; err: <nil>
234234
// [[]float64]: [26.89 0.67]; err: <nil>
235235
// [time.Duration]: 2h35m0s; err: <nil>

getenv_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3858,7 +3858,7 @@ func TestEnvInt(t *testing.T) {
38583858
expected: expected{
38593859
val: 0,
38603860
wantError: func(t assert.TestingT, err error, i ...interface{}) bool {
3861-
return assert.Error(t, err) && assert.ErrorIs(t, err, getenv.ErrNotSet)
3861+
return assert.Error(t, err) && assert.ErrorContains(t, err, getenv.ErrNotSet.Error())
38623862
},
38633863
},
38643864
},
@@ -3892,7 +3892,7 @@ func TestEnvInt(t *testing.T) {
38923892
expected: expected{
38933893
val: 0,
38943894
wantError: func(t assert.TestingT, err error, i ...interface{}) bool {
3895-
return assert.Error(t, err) && assert.ErrorIs(t, err, getenv.ErrInvalidValue)
3895+
return assert.Error(t, err) && assert.ErrorContains(t, err, getenv.ErrInvalidValue.Error())
38963896
},
38973897
},
38983898
},
@@ -3945,7 +3945,7 @@ func TestEnvIntSlice(t *testing.T) {
39453945
expected: expected{
39463946
val: nil,
39473947
wantError: func(t assert.TestingT, err error, i ...interface{}) bool {
3948-
return assert.Error(t, err) && assert.ErrorIs(t, err, getenv.ErrNotSet)
3948+
return assert.Error(t, err) && assert.ErrorContains(t, err, getenv.ErrNotSet.Error())
39493949
},
39503950
},
39513951
},
@@ -3981,7 +3981,7 @@ func TestEnvIntSlice(t *testing.T) {
39813981
expected: expected{
39823982
val: nil,
39833983
wantError: func(t assert.TestingT, err error, i ...interface{}) bool {
3984-
return assert.Error(t, err) && assert.ErrorIs(t, err, getenv.ErrInvalidValue)
3984+
return assert.Error(t, err) && assert.ErrorContains(t, err, getenv.ErrInvalidValue.Error())
39853985
},
39863986
},
39873987
},
@@ -4000,7 +4000,7 @@ func TestEnvIntSlice(t *testing.T) {
40004000
expected: expected{
40014001
val: nil,
40024002
wantError: func(t assert.TestingT, err error, i ...interface{}) bool {
4003-
return assert.Error(t, err) && assert.ErrorIs(t, err, getenv.ErrInvalidValue)
4003+
return assert.Error(t, err) && assert.ErrorContains(t, err, getenv.ErrNotSet.Error())
40044004
},
40054005
},
40064006
},

internal/common_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ func (p precondition) maybeSetEnv(tb testing.TB, key string) {
3333
}
3434
}
3535

36-
// getURL is a helper function for getting url.URL from string.
37-
func getURL(tb testing.TB, rawURL string) url.URL {
36+
// getTestURL is a helper function for getting url.URL from string.
37+
func getTestURL(tb testing.TB, rawURL string) url.URL {
3838
tb.Helper()
3939

4040
val, err := url.Parse(rawURL)
@@ -43,8 +43,8 @@ func getURL(tb testing.TB, rawURL string) url.URL {
4343
return *val
4444
}
4545

46-
// getIP is a helper function for getting net.IP from string.
47-
func getIP(tb testing.TB, raw string) net.IP {
46+
// getTestIP is a helper function for getting net.IP from string.
47+
func getTestIP(tb testing.TB, raw string) net.IP {
4848
tb.Helper()
4949

5050
return net.ParseIP(raw)

internal/errors.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
11
package internal
22

3-
import "errors"
3+
import (
4+
"errors"
5+
"fmt"
6+
)
47

58
var (
69
// ErrNotSet is an error that is returned when the environment variable is not set.
710
ErrNotSet = errors.New("not set")
811
// ErrInvalidValue is an error that is returned when the environment variable is not valid.
912
ErrInvalidValue = errors.New("invalid value")
1013
)
14+
15+
func newErrInvalidValue(msg string) error {
16+
return newWrapErr(msg, ErrInvalidValue)
17+
}
18+
19+
func newErrNotSet(msg string) error {
20+
return newWrapErr(msg, ErrNotSet)
21+
}
22+
23+
func newWrapErr(msg string, wrapErr error) error {
24+
return fmt.Errorf("%s: %w", msg, wrapErr)
25+
}

internal/iface.go

Lines changed: 33 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -200,158 +200,126 @@ func newBoolParser(v any) EnvParser {
200200
// EnvParser interface for parsing environment variables.
201201
type EnvParser interface {
202202
// ParseEnv parses environment variable by key and returns value.
203-
ParseEnv(key string, defaltVal any, options Parameters) any
203+
ParseEnv(key string, options Parameters) (any, error)
204204
}
205205

206206
// stringParser is a parser for string type.
207207
type stringParser string
208208

209-
func (s stringParser) ParseEnv(key string, defaltVal any, _ Parameters) any {
210-
val := stringOrDefault(key, defaltVal.(string))
211-
212-
return val
209+
func (s stringParser) ParseEnv(key string, _ Parameters) (any, error) {
210+
return getString(key)
213211
}
214212

215213
type stringSliceParser []string
216214

217-
func (s stringSliceParser) ParseEnv(key string, defaltVal any, options Parameters) any {
215+
func (s stringSliceParser) ParseEnv(key string, options Parameters) (any, error) {
218216
sep := options.Separator
219217

220-
val := stringSliceOrDefault(key, defaltVal.([]string), sep)
221-
222-
return val
218+
return getStringSlice(key, sep)
223219
}
224220

225221
type numberParser[T Number] struct{}
226222

227-
func (n numberParser[T]) ParseEnv(key string, defaltVal any, _ Parameters) any {
228-
val := numberOrDefaultGen[T](key, defaltVal.(T))
229-
230-
return val
223+
func (n numberParser[T]) ParseEnv(key string, _ Parameters) (any, error) {
224+
return getNumberGen[T](key)
231225
}
232226

233227
type numberSliceParser[S []T, T Number] struct{}
234228

235-
func (i numberSliceParser[S, T]) ParseEnv(key string, defaltVal any, options Parameters) any {
229+
func (i numberSliceParser[S, T]) ParseEnv(key string, options Parameters) (any, error) {
236230
sep := options.Separator
237231

238-
val := numberSliceOrDefaultGen(key, defaltVal.(S), sep)
239-
240-
return val
232+
return getNumberSliceGen[S, T](key, sep)
241233
}
242234

243235
type boolParser bool
244236

245-
func (b boolParser) ParseEnv(key string, defaltVal any, _ Parameters) any {
246-
val := boolOrDefault(key, defaltVal.(bool))
247-
248-
return val
237+
func (b boolParser) ParseEnv(key string, _ Parameters) (any, error) {
238+
return getBool(key)
249239
}
250240

251241
type timeParser time.Time
252242

253-
func (t timeParser) ParseEnv(key string, defaltVal any, options Parameters) any {
243+
func (t timeParser) ParseEnv(key string, options Parameters) (any, error) {
254244
layout := options.Layout
255245

256-
val := timeOrDefault(key, defaltVal.(time.Time), layout)
257-
258-
return val
246+
return getTime(key, layout)
259247
}
260248

261249
type timeSliceParser []time.Time
262250

263-
func (t timeSliceParser) ParseEnv(key string, defaltVal any, options Parameters) any {
251+
func (t timeSliceParser) ParseEnv(key string, options Parameters) (any, error) {
264252
layout := options.Layout
265253
sep := options.Separator
266254

267-
val := timeSliceOrDefault(key, defaltVal.([]time.Time), layout, sep)
268-
269-
return val
255+
return getTimeSlice(key, layout, sep)
270256
}
271257

272258
type durationSliceParser []time.Duration
273259

274-
func (t durationSliceParser) ParseEnv(key string, defaltVal any, options Parameters) any {
260+
func (t durationSliceParser) ParseEnv(key string, options Parameters) (any, error) {
275261
sep := options.Separator
276262

277-
val := durationSliceOrDefault(key, defaltVal.([]time.Duration), sep)
278-
279-
return val
263+
return getDurationSlice(key, sep)
280264
}
281265

282266
type durationParser time.Duration
283267

284-
func (d durationParser) ParseEnv(key string, defaltVal any, _ Parameters) any {
285-
val := durationOrDefault(key, defaltVal.(time.Duration))
286-
287-
return val
268+
func (d durationParser) ParseEnv(key string, _ Parameters) (any, error) {
269+
return getDuration(key)
288270
}
289271

290272
// stringSliceParser is a parser for []string
291273
type urlParser url.URL
292274

293-
func (t urlParser) ParseEnv(key string, defaltVal any, _ Parameters) any {
294-
val := urlOrDefault(key, defaltVal.(url.URL))
295-
296-
return val
275+
func (t urlParser) ParseEnv(key string, _ Parameters) (any, error) {
276+
return getURL(key)
297277
}
298278

299279
// urlSliceParser is a parser for []url.URL
300280
type urlSliceParser []url.URL
301281

302-
func (t urlSliceParser) ParseEnv(key string, defaltVal any, opts Parameters) any {
282+
func (t urlSliceParser) ParseEnv(key string, opts Parameters) (any, error) {
303283
separator := opts.Separator
304284

305-
val := urlSliceOrDefault(key, defaltVal.([]url.URL), separator)
306-
307-
return val
285+
return getURLSlice(key, separator)
308286
}
309287

310288
// ipParser is a parser for net.IP
311289
type ipParser net.IP
312290

313-
func (t ipParser) ParseEnv(key string, defaltVal any, _ Parameters) any {
314-
val := ipOrDefault(key, defaltVal.(net.IP))
315-
316-
return val
291+
func (t ipParser) ParseEnv(key string, _ Parameters) (any, error) {
292+
return getIP(key)
317293
}
318294

319295
// ipSliceParser is a parser for []net.IP
320296
type ipSliceParser []net.IP
321297

322-
func (t ipSliceParser) ParseEnv(key string, defaltVal any, opts Parameters) any {
298+
func (t ipSliceParser) ParseEnv(key string, opts Parameters) (any, error) {
323299
separator := opts.Separator
324300

325-
val := ipSliceOrDefault(key, defaltVal.([]net.IP), separator)
326-
327-
return val
301+
return getIPSlice(key, separator)
328302
}
329303

330304
// boolSliceParser is a parser for []bool
331305
type boolSliceParser []bool
332306

333-
func (b boolSliceParser) ParseEnv(key string, defaltVal any, options Parameters) any {
307+
func (b boolSliceParser) ParseEnv(key string, options Parameters) (any, error) {
334308
sep := options.Separator
335309

336-
val := boolSliceOrDefault(key, defaltVal.([]bool), sep)
337-
338-
return val
310+
return getBoolSlice(key, sep)
339311
}
340312

341313
type complexParser[T Complex] struct{}
342314

343-
func (n complexParser[T]) ParseEnv(key string, defaltVal any, _ Parameters) any {
344-
val := complexOrDefaultGen[T](key, defaltVal.(T))
345-
346-
return val
315+
func (n complexParser[T]) ParseEnv(key string, _ Parameters) (any, error) {
316+
return getComplexGen[T](key)
347317
}
348318

349319
type complexSliceParser[S []T, T Complex] struct{}
350320

351-
func (i complexSliceParser[S, T]) ParseEnv(key string, defaltVal any, options Parameters) any {
321+
func (i complexSliceParser[S, T]) ParseEnv(key string, options Parameters) (any, error) {
352322
sep := options.Separator
353323

354-
val := complexSliceOrDefaultGen(key, defaltVal.(S), sep)
355-
356-
return val
324+
return getComplexSliceGen[S, T](key, sep)
357325
}

internal/iface_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -200,24 +200,24 @@ func TestNewEnvParser(t *testing.T) {
200200
want: durationSliceParser([]time.Duration{time.Minute}),
201201
},
202202
{
203-
v: getURL(t, "http://example.com"),
203+
v: getTestURL(t, "http://example.com"),
204204
wantPanic: assert.NotPanics,
205-
want: urlParser(getURL(t, "http://example.com")),
205+
want: urlParser(getTestURL(t, "http://example.com")),
206206
},
207207
{
208-
v: []url.URL{getURL(t, "http://example.com")},
208+
v: []url.URL{getTestURL(t, "http://example.com")},
209209
wantPanic: assert.NotPanics,
210-
want: urlSliceParser([]url.URL{getURL(t, "http://example.com")}),
210+
want: urlSliceParser([]url.URL{getTestURL(t, "http://example.com")}),
211211
},
212212
{
213-
v: getIP(t, "0.0.0.0"),
213+
v: getTestIP(t, "0.0.0.0"),
214214
wantPanic: assert.NotPanics,
215-
want: ipParser(getIP(t, "0.0.0.0")),
215+
want: ipParser(getTestIP(t, "0.0.0.0")),
216216
},
217217
{
218-
v: []net.IP{getIP(t, "0.0.0.0")},
218+
v: []net.IP{getTestIP(t, "0.0.0.0")},
219219
wantPanic: assert.NotPanics,
220-
want: ipSliceParser([]net.IP{getIP(t, "0.0.0.0")}),
220+
want: ipSliceParser([]net.IP{getTestIP(t, "0.0.0.0")}),
221221
},
222222
{
223223
v: uintptr(2),

0 commit comments

Comments
 (0)