Skip to content

Commit 4f9dea7

Browse files
authored
feat: Add support of url.URL (#80)
1 parent 607a9e0 commit 4f9dea7

18 files changed

+3782
-3
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
[![Go Reference](https://pkg.go.dev/badge/github.com/obalunenko/getenv.svg)](https://pkg.go.dev/github.com/obalunenko/getenv)
33
[![Go Report Card](https://goreportcard.com/badge/github.com/obalunenko/getenv)](https://goreportcard.com/report/github.com/obalunenko/getenv)
44
[![codecov](https://codecov.io/gh/obalunenko/getenv/branch/master/graph/badge.svg)](https://codecov.io/gh/obalunenko/getenv)
5-
![coverbadger-tag-do-not-edit](https://img.shields.io/badge/coverage-97.03%25-brightgreen?longCache=true&style=flat)
5+
![coverbadger-tag-do-not-edit](https://img.shields.io/badge/coverage-97.56%25-brightgreen?longCache=true&style=flat)
66
[![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)
7-
87
# getenv
98

109
Package getenv provides functionality for loading environment variables and parse them into go builtin types.
@@ -37,6 +36,7 @@ Types supported:
3736
- []time.Time
3837
- time.Duration
3938
- bool
39+
- url.URL
4040

4141
## Examples
4242

getenv.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
// - []time.Time
2929
// - time.Duration
3030
// - bool
31+
// - url.URL
3132
package getenv
3233

3334
import (

getenv_example_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package getenv_test
22

33
import (
44
"fmt"
5+
"net/url"
56
"os"
67
"time"
78

@@ -65,10 +66,19 @@ func ExampleEnvOrDefault() {
6566
val = getenv.EnvOrDefault(key, time.Second)
6667
fmt.Printf("[%T]: %v\n", val, val)
6768

69+
// url.URL
70+
if err := os.Setenv(key, "https://test:abcd123@golangbyexample.com:8000/tutorials/intro?type=advance&compact=false#history"); err != nil {
71+
panic(err)
72+
}
73+
74+
val = getenv.EnvOrDefault(key, url.URL{})
75+
fmt.Printf("[%T]: %v\n", val, val)
76+
6877
// Output:
6978
// [string]: golly
7079
// [int]: 123
7180
// [time.Time]: 2022-01-20 00:00:00 +0000 UTC
7281
// [[]float64]: [26.89 0.67]
7382
// [time.Duration]: 2h35m0s
83+
// [url.URL]: {https test:abcd123 golangbyexample.com:8000 /tutorials/intro false false type=advance&compact=false history }
7484
}

getenv_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package getenv_test
22

33
import (
4+
"net/url"
45
"testing"
56
"time"
67

78
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
810

911
"github.com/obalunenko/getenv"
1012
"github.com/obalunenko/getenv/option"
@@ -2454,3 +2456,88 @@ func TestInt16SliceOrDefault(t *testing.T) {
24542456
})
24552457
}
24562458
}
2459+
2460+
func getURL(tb testing.TB, rawURL string) url.URL {
2461+
val, err := url.Parse(rawURL)
2462+
require.NoError(tb, err)
2463+
2464+
return *val
2465+
}
2466+
2467+
func TestURLOrDefault(t *testing.T) {
2468+
const rawDefault = "https://test:abcd123@golangbyexample.com:8000/tutorials/intro?type=advance&compact=false#history"
2469+
2470+
type args struct {
2471+
key string
2472+
defaultVal url.URL
2473+
}
2474+
2475+
type expected struct {
2476+
val url.URL
2477+
}
2478+
2479+
var tests = []struct {
2480+
name string
2481+
precond precondition
2482+
args args
2483+
expected expected
2484+
}{
2485+
{
2486+
name: "env not set - default returned",
2487+
precond: precondition{
2488+
setenv: setenv{
2489+
isSet: false,
2490+
val: "postgres://user:pass@host.com:5432/path?k=v#f",
2491+
},
2492+
},
2493+
args: args{
2494+
key: testEnvKey,
2495+
defaultVal: getURL(t, rawDefault),
2496+
},
2497+
expected: expected{
2498+
val: getURL(t, rawDefault),
2499+
},
2500+
},
2501+
{
2502+
name: "env set - env value returned",
2503+
precond: precondition{
2504+
setenv: setenv{
2505+
isSet: true,
2506+
val: "postgres://user:pass@host.com:5432/path?k=v#f",
2507+
},
2508+
},
2509+
args: args{
2510+
key: testEnvKey,
2511+
defaultVal: getURL(t, rawDefault),
2512+
},
2513+
expected: expected{
2514+
val: getURL(t, "postgres://user:pass@host.com:5432/path?k=v#f"),
2515+
},
2516+
},
2517+
{
2518+
name: "empty env value set - default returned",
2519+
precond: precondition{
2520+
setenv: setenv{
2521+
isSet: true,
2522+
val: "",
2523+
},
2524+
},
2525+
args: args{
2526+
key: testEnvKey,
2527+
defaultVal: getURL(t, rawDefault),
2528+
},
2529+
expected: expected{
2530+
val: getURL(t, rawDefault),
2531+
},
2532+
},
2533+
}
2534+
2535+
for _, tt := range tests {
2536+
t.Run(tt.name, func(t *testing.T) {
2537+
tt.precond.maybeSetEnv(t, tt.args.key)
2538+
2539+
got := getenv.EnvOrDefault(tt.args.key, tt.args.defaultVal)
2540+
assert.Equal(t, tt.expected.val, got)
2541+
})
2542+
}
2543+
}

internal/common_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package internal
22

33
import (
4+
"net/url"
45
"testing"
56

67
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
79
)
810

911
type panicAssertionFunc func(t assert.TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) bool
@@ -24,3 +26,10 @@ func (p precondition) maybeSetEnv(tb testing.TB, key string) {
2426
tb.Setenv(key, p.setenv.val)
2527
}
2628
}
29+
30+
func getURL(tb testing.TB, rawURL string) url.URL {
31+
val, err := url.Parse(rawURL)
32+
require.NoError(tb, err)
33+
34+
return *val
35+
}

internal/constraint.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package internal
22

33
import (
4+
"net/url"
45
"time"
56
)
67

78
type (
89
// EnvParsable is a constraint for supported environment variable types parsers.
910
EnvParsable interface {
10-
String | Int | Uint | Float | Time | bool
11+
String | Int | Uint | Float | Time | bool | url.URL
1112
}
1213

1314
// String is a constraint for strings and slice of strings.

internal/iface.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package internal
33

44
import (
55
"fmt"
6+
"net/url"
67
"time"
78
)
89

@@ -27,6 +28,8 @@ func NewEnvParser(v any) EnvParser {
2728
p = timeSliceParser(t)
2829
case time.Duration:
2930
p = durationParser(t)
31+
case url.URL:
32+
p = urlParser(t)
3033
default:
3134
p = nil
3235
}
@@ -357,3 +360,11 @@ func (d uint32Parser) ParseEnv(key string, defaltVal any, _ Parameters) any {
357360

358361
return val
359362
}
363+
364+
type urlParser url.URL
365+
366+
func (t urlParser) ParseEnv(key string, defaltVal any, _ Parameters) any {
367+
val := urlOrDefault(key, defaltVal.(url.URL))
368+
369+
return val
370+
}

internal/iface_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package internal
22

33
import (
4+
"net/url"
45
"testing"
56
"time"
67

@@ -238,6 +239,14 @@ func TestNewEnvParser(t *testing.T) {
238239
want: durationParser(time.Minute),
239240
wantPanic: assert.NotPanics,
240241
},
242+
{
243+
name: "url.URL",
244+
args: args{
245+
v: url.URL{},
246+
},
247+
want: urlParser(url.URL{}),
248+
wantPanic: assert.NotPanics,
249+
},
241250
{
242251
name: "not supported - panics",
243252
args: args{
@@ -783,6 +792,25 @@ func Test_ParseEnv(t *testing.T) {
783792
time.Date(2023, time.March, 24, 0, 0, 0, 0, time.UTC),
784793
},
785794
},
795+
{
796+
name: "urlParser",
797+
s: urlParser(url.URL{}),
798+
precond: precondition{
799+
setenv: setenv{
800+
isSet: true,
801+
val: "https.google.com",
802+
},
803+
},
804+
args: args{
805+
key: testEnvKey,
806+
defaltVal: url.URL{},
807+
in2: Parameters{
808+
Separator: "",
809+
Layout: time.DateOnly,
810+
},
811+
},
812+
want: getURL(t, "https.google.com"),
813+
},
786814
}
787815

788816
for _, tt := range tests {

internal/parsers.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package internal
22

33
import (
4+
"net/url"
45
"os"
56
"strconv"
67
"strings"
@@ -613,3 +614,20 @@ func uint32OrDefault(key string, defaultVal uint32) uint32 {
613614

614615
return uint32(val)
615616
}
617+
618+
// urlOrDefault retrieves the url.URL value of the environment variable named
619+
// by the key represented by layout.
620+
// If variable not set or value is empty - defaultVal will be returned.
621+
func urlOrDefault(key string, defaultVal url.URL) url.URL {
622+
env := stringOrDefault(key, "")
623+
if env == "" {
624+
return defaultVal
625+
}
626+
627+
val, err := url.Parse(env)
628+
if err != nil {
629+
return defaultVal
630+
}
631+
632+
return *val
633+
}

internal/parsers_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package internal
22

33
import (
4+
"net/url"
45
"testing"
56
"time"
67

@@ -1652,6 +1653,84 @@ func Test_timeOrDefault(t *testing.T) {
16521653
}
16531654
}
16541655

1656+
func TestURLOrDefault(t *testing.T) {
1657+
const rawDefault = "https://test:abcd123@golangbyexample.com:8000/tutorials/intro?type=advance&compact=false#history"
1658+
1659+
type args struct {
1660+
key string
1661+
defaultVal url.URL
1662+
}
1663+
1664+
type expected struct {
1665+
val url.URL
1666+
}
1667+
1668+
var tests = []struct {
1669+
name string
1670+
precond precondition
1671+
args args
1672+
expected expected
1673+
}{
1674+
{
1675+
name: "env not set - default returned",
1676+
precond: precondition{
1677+
setenv: setenv{
1678+
isSet: false,
1679+
val: "postgres://user:pass@host.com:5432/path?k=v#f",
1680+
},
1681+
},
1682+
args: args{
1683+
key: testEnvKey,
1684+
defaultVal: getURL(t, rawDefault),
1685+
},
1686+
expected: expected{
1687+
val: getURL(t, rawDefault),
1688+
},
1689+
},
1690+
{
1691+
name: "env set - env value returned",
1692+
precond: precondition{
1693+
setenv: setenv{
1694+
isSet: true,
1695+
val: "postgres://user:pass@host.com:5432/path?k=v#f",
1696+
},
1697+
},
1698+
args: args{
1699+
key: testEnvKey,
1700+
defaultVal: getURL(t, rawDefault),
1701+
},
1702+
expected: expected{
1703+
val: getURL(t, "postgres://user:pass@host.com:5432/path?k=v#f"),
1704+
},
1705+
},
1706+
{
1707+
name: "empty env value set - default returned",
1708+
precond: precondition{
1709+
setenv: setenv{
1710+
isSet: true,
1711+
val: "",
1712+
},
1713+
},
1714+
args: args{
1715+
key: testEnvKey,
1716+
defaultVal: getURL(t, rawDefault),
1717+
},
1718+
expected: expected{
1719+
val: getURL(t, rawDefault),
1720+
},
1721+
},
1722+
}
1723+
1724+
for _, tt := range tests {
1725+
t.Run(tt.name, func(t *testing.T) {
1726+
tt.precond.maybeSetEnv(t, tt.args.key)
1727+
1728+
got := urlOrDefault(tt.args.key, tt.args.defaultVal)
1729+
assert.Equal(t, tt.expected.val, got)
1730+
})
1731+
}
1732+
}
1733+
16551734
func Test_timeSliceOrDefault(t *testing.T) {
16561735
const layout = "2006/02/01 15:04"
16571736

0 commit comments

Comments
 (0)