From 01357aad9f0541afcd7e648f4cf0fb02f3d5fcd8 Mon Sep 17 00:00:00 2001 From: Oleg Balunenko Date: Mon, 27 Mar 2023 18:17:58 +0400 Subject: [PATCH] feat: Add support of []time.Duration (#87) --- README.md | 3 +- getenv.go | 1 + getenv_test.go | 82 +++++++++++++++++++++++++++++++++++++++ internal/constraint.go | 2 +- internal/iface.go | 33 +++++++++++++--- internal/iface_test.go | 29 ++++++++++++++ internal/parsers.go | 23 +++++++++++ internal/parsers_test.go | 83 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 248 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f000dab1..8057af1d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Go Reference](https://pkg.go.dev/badge/github.com/obalunenko/getenv.svg)](https://pkg.go.dev/github.com/obalunenko/getenv) [![Go Report Card](https://goreportcard.com/badge/github.com/obalunenko/getenv)](https://goreportcard.com/report/github.com/obalunenko/getenv) [![codecov](https://codecov.io/gh/obalunenko/getenv/branch/master/graph/badge.svg)](https://codecov.io/gh/obalunenko/getenv) -![coverbadger-tag-do-not-edit](https://img.shields.io/badge/coverage-98.11%25-brightgreen?longCache=true&style=flat) +![coverbadger-tag-do-not-edit](https://img.shields.io/badge/coverage-97.73%25-brightgreen?longCache=true&style=flat) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=obalunenko_getenv&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=obalunenko_getenv) # getenv @@ -39,6 +39,7 @@ Types supported: - time.Time - []time.Time - time.Duration +- []time.Duration - bool - url.URL - net.IP diff --git a/getenv.go b/getenv.go index 0d323602..6de977f8 100644 --- a/getenv.go +++ b/getenv.go @@ -30,6 +30,7 @@ // - time.Time // - []time.Time // - time.Duration +// - []time.Duration // - bool // - url.URL // - net.IP diff --git a/getenv_test.go b/getenv_test.go index 31bcf8d2..95633eae 100644 --- a/getenv_test.go +++ b/getenv_test.go @@ -1398,6 +1398,88 @@ func TestTimeSliceOrDefault(t *testing.T) { } } +func TestDurationSliceOrDefault(t *testing.T) { + type args struct { + key string + defaultVal []time.Duration + separator string + } + + type expected struct { + val []time.Duration + } + + var tests = []struct { + name string + precond precondition + args args + expected expected + }{ + { + name: "env not set - default returned", + precond: precondition{ + setenv: setenv{ + isSet: false, + val: "2m,3h", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []time.Duration{time.Second}, + separator: ",", + }, + expected: expected{ + val: []time.Duration{time.Second}, + }, + }, + { + name: "env set - env value returned", + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "2m,3h", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []time.Duration{time.Second}, + separator: ",", + }, + expected: expected{ + val: []time.Duration{ + 2 * time.Minute, 3 * time.Hour, + }, + }, + }, + { + name: "empty env value set - default returned", + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []time.Duration{time.Second}, + separator: ",", + }, + expected: expected{ + val: []time.Duration{time.Second}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.precond.maybeSetEnv(t, tt.args.key) + + got := getenv.EnvOrDefault(tt.args.key, tt.args.defaultVal, option.WithSeparator(",")) + assert.Equal(t, tt.expected.val, got) + }) + } +} + func TestDurationOrDefault(t *testing.T) { type args struct { key string diff --git a/internal/constraint.go b/internal/constraint.go index 063abb1a..b6626e10 100644 --- a/internal/constraint.go +++ b/internal/constraint.go @@ -34,6 +34,6 @@ type ( // Time is a constraint for time.Time and time.Duration. Time interface { - time.Time | []time.Time | time.Duration + time.Time | []time.Time | time.Duration | []time.Duration } ) diff --git a/internal/iface.go b/internal/iface.go index 7b314af9..21bfb22a 100644 --- a/internal/iface.go +++ b/internal/iface.go @@ -23,12 +23,8 @@ func NewEnvParser(v any) EnvParser { p = boolParser(t) case float32, []float32, float64, []float64: p = newFloatParser(t) - case time.Time: - p = timeParser(t) - case []time.Time: - p = timeSliceParser(t) - case time.Duration: - p = durationParser(t) + case time.Time, []time.Time, time.Duration, []time.Duration: + p = newTimeParser(t) case url.URL: p = urlParser(t) case net.IP: @@ -124,6 +120,21 @@ func newFloatParser(v any) EnvParser { } } +func newTimeParser(v any) EnvParser { + switch t := v.(type) { + case time.Time: + return timeParser(t) + case []time.Time: + return timeSliceParser(t) + case time.Duration: + return durationParser(t) + case []time.Duration: + return durationSliceParser(t) + default: + return nil + } +} + // EnvParser interface for parsing environment variables. type EnvParser interface { ParseEnv(key string, defaltVal any, options Parameters) any @@ -302,6 +313,16 @@ func (t timeSliceParser) ParseEnv(key string, defaltVal any, options Parameters) return val } +type durationSliceParser []time.Duration + +func (t durationSliceParser) ParseEnv(key string, defaltVal any, options Parameters) any { + sep := options.Separator + + val := durationSliceOrDefault(key, defaltVal.([]time.Duration), sep) + + return val +} + type durationParser time.Duration func (d durationParser) ParseEnv(key string, defaltVal any, _ Parameters) any { diff --git a/internal/iface_test.go b/internal/iface_test.go index c03ccc6c..484c4d05 100644 --- a/internal/iface_test.go +++ b/internal/iface_test.go @@ -256,6 +256,14 @@ func TestNewEnvParser(t *testing.T) { want: timeSliceParser([]time.Time{}), wantPanic: assert.NotPanics, }, + { + name: "[]time.Duration", + args: args{ + v: []time.Duration{}, + }, + want: durationSliceParser([]time.Duration{}), + wantPanic: assert.NotPanics, + }, { name: "time.Duration", args: args{ @@ -882,6 +890,27 @@ func Test_ParseEnv(t *testing.T) { time.Date(2023, time.March, 24, 0, 0, 0, 0, time.UTC), }, }, + { + name: "durationSliceParser", + s: durationSliceParser([]time.Duration{}), + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "2m,3h", + }, + }, + args: args{ + key: testEnvKey, + defaltVal: []time.Duration{}, + in2: Parameters{ + Separator: ",", + }, + }, + want: []time.Duration{ + 2 * time.Minute, + 3 * time.Hour, + }, + }, { name: "urlParser", s: urlParser(url.URL{}), diff --git a/internal/parsers.go b/internal/parsers.go index 9ce983d1..b67f201c 100644 --- a/internal/parsers.go +++ b/internal/parsers.go @@ -319,6 +319,29 @@ func timeSliceOrDefault(key string, defaultVal []time.Time, layout, separator st return val } +// durationSliceOrDefault retrieves the []time.Duration value of the environment variable named +// by the key represented by layout. +// If variable not set or value is empty - defaultVal will be returned. +func durationSliceOrDefault(key string, defaultVal []time.Duration, separator string) []time.Duration { + valraw := stringSliceOrDefault(key, nil, separator) + if valraw == nil { + return defaultVal + } + + val := make([]time.Duration, 0, len(valraw)) + + for _, s := range valraw { + v, err := time.ParseDuration(s) + if err != nil { + return defaultVal + } + + val = append(val, v) + } + + return val +} + // int64OrDefault retrieves the int64 value of the environment variable named // by the key. // If variable not set or value is empty - defaultVal will be returned. diff --git a/internal/parsers_test.go b/internal/parsers_test.go index 0e9453ef..a35a7bab 100644 --- a/internal/parsers_test.go +++ b/internal/parsers_test.go @@ -2374,6 +2374,89 @@ func Test_timeSliceOrDefault(t *testing.T) { } } +func Test_durationSliceOrDefault(t *testing.T) { + type args struct { + key string + defaultVal []time.Duration + separator string + } + + type expected struct { + val []time.Duration + } + + var tests = []struct { + name string + precond precondition + args args + expected expected + }{ + { + name: "env not set - default returned", + precond: precondition{ + setenv: setenv{ + isSet: false, + val: "2m,3h", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []time.Duration{time.Second}, + separator: ",", + }, + expected: expected{ + val: []time.Duration{time.Second}, + }, + }, + { + name: "env set - env value returned", + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "2m,3h", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []time.Duration{time.Second}, + separator: ",", + }, + expected: expected{ + val: []time.Duration{ + 2 * time.Minute, 3 * time.Hour, + }, + }, + }, + { + name: "empty env value set - default returned", + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []time.Duration{time.Second}, + separator: ",", + }, + expected: expected{ + val: []time.Duration{time.Second}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.precond.maybeSetEnv(t, tt.args.key) + + got := durationSliceOrDefault(tt.args.key, tt.args.defaultVal, tt.args.separator) + + assert.Equal(t, tt.expected.val, got) + }) + } +} + func Test_durationOrDefault(t *testing.T) { type args struct { key string