From 35bea46bfbd0e5865950de341f10d71603c71efd Mon Sep 17 00:00:00 2001 From: Oleg Balunenko Date: Mon, 27 Mar 2023 22:50:55 +0400 Subject: [PATCH] feat: Add support of []net.IP (#89) --- README.md | 3 +- getenv.go | 1 + getenv_test.go | 85 +++++++++++ internal/constraint.go | 2 +- internal/iface.go | 12 ++ internal/iface_test.go | 32 ++++- internal/parsers.go | 23 +++ internal/parsers_test.go | 298 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 451 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index eeb6350f..95f950f5 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-97.58%25-brightgreen?longCache=true&style=flat) +![coverbadger-tag-do-not-edit](https://img.shields.io/badge/coverage-98.93%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 @@ -44,6 +44,7 @@ Types supported: - url.URL - []url.URL - net.IP +- []net.IP ## Examples diff --git a/getenv.go b/getenv.go index 5fe4a32b..3ba4aeec 100644 --- a/getenv.go +++ b/getenv.go @@ -35,6 +35,7 @@ // - url.URL // - []url.URL // - net.IP +// - []net.IP package getenv import ( diff --git a/getenv_test.go b/getenv_test.go index 0cc78d93..9098bdb5 100644 --- a/getenv_test.go +++ b/getenv_test.go @@ -3050,6 +3050,91 @@ func TestIPOrDefault(t *testing.T) { } } +func TestIPSliceOrDefault(t *testing.T) { + const rawDefault = "0.0.0.0" + + type args struct { + key string + defaultVal []net.IP + separator string + } + + type expected struct { + val []net.IP + } + + var tests = []struct { + name string + precond precondition + args args + expected expected + }{ + { + name: "env not set - default returned", + precond: precondition{ + setenv: setenv{ + isSet: false, + val: "192.168.8.0,2001:cb8::17", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []net.IP{getIP(t, rawDefault)}, + separator: ",", + }, + expected: expected{ + val: []net.IP{getIP(t, rawDefault)}, + }, + }, + { + name: "env set - env value returned", + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "192.168.8.0,2001:cb8::17", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []net.IP{getIP(t, rawDefault)}, + separator: ",", + }, + expected: expected{ + val: []net.IP{ + getIP(t, "192.168.8.0"), + getIP(t, "2001:cb8::17"), + }, + }, + }, + { + name: "empty env value set - default returned", + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []net.IP{getIP(t, rawDefault)}, + separator: ",", + }, + expected: expected{ + val: []net.IP{getIP(t, rawDefault)}, + }, + }, + } + + 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(tt.args.separator)) + assert.Equal(t, tt.expected.val, got) + }) + } +} + func TestURLSliceOrDefault(t *testing.T) { type args struct { key string diff --git a/internal/constraint.go b/internal/constraint.go index 4a0e04cf..8bf4293a 100644 --- a/internal/constraint.go +++ b/internal/constraint.go @@ -9,7 +9,7 @@ import ( type ( // EnvParsable is a constraint for supported environment variable types parsers. EnvParsable interface { - String | Int | Uint | Float | Time | bool | url.URL | []url.URL | net.IP + String | Int | Uint | Float | Time | bool | url.URL | []url.URL | net.IP | []net.IP } // String is a constraint for strings and slice of strings. diff --git a/internal/iface.go b/internal/iface.go index 99ceb87b..4b74e35d 100644 --- a/internal/iface.go +++ b/internal/iface.go @@ -31,6 +31,8 @@ func NewEnvParser(v any) EnvParser { p = urlSliceParser(t) case net.IP: p = ipParser(t) + case []net.IP: + p = ipSliceParser(t) default: p = nil } @@ -448,3 +450,13 @@ func (t ipParser) ParseEnv(key string, defaltVal any, _ Parameters) any { return val } + +type ipSliceParser []net.IP + +func (t ipSliceParser) ParseEnv(key string, defaltVal any, opts Parameters) any { + separator := opts.Separator + + val := ipSliceOrDefault(key, defaltVal.([]net.IP), separator) + + return val +} diff --git a/internal/iface_test.go b/internal/iface_test.go index 1aaef77d..65f13be0 100644 --- a/internal/iface_test.go +++ b/internal/iface_test.go @@ -296,6 +296,14 @@ func TestNewEnvParser(t *testing.T) { want: ipParser(net.IP{}), wantPanic: assert.NotPanics, }, + { + name: "[]net.IP", + args: args{ + v: []net.IP{}, + }, + want: ipSliceParser([]net.IP{}), + wantPanic: assert.NotPanics, + }, { name: "not supported - panics", args: args{ @@ -972,12 +980,30 @@ func Test_ParseEnv(t *testing.T) { args: args{ key: testEnvKey, defaltVal: net.IP{}, + in2: Parameters{}, + }, + want: getIP(t, "2001:cb8::17"), + }, + { + name: "ipSliceParser", + s: ipSliceParser([]net.IP{}), + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "2001:cb8::17,192.168.0.1", + }, + }, + args: args{ + key: testEnvKey, + defaltVal: []net.IP{}, in2: Parameters{ - Separator: "", - Layout: time.DateOnly, + Separator: ",", }, }, - want: getIP(t, "2001:cb8::17"), + want: []net.IP{ + getIP(t, "2001:cb8::17"), + getIP(t, "192.168.0.1"), + }, }, } diff --git a/internal/parsers.go b/internal/parsers.go index 7e585076..ad9a9feb 100644 --- a/internal/parsers.go +++ b/internal/parsers.go @@ -778,3 +778,26 @@ func ipOrDefault(key string, defaultVal net.IP) net.IP { return val } + +// ipSliceOrDefault retrieves the net.IP slice value of the environment variable named +// by the key and separated by sep. +// If variable not set or value is empty - defaultVal will be returned. +func ipSliceOrDefault(key string, defaultVal []net.IP, sep string) []net.IP { + valraw := stringSliceOrDefault(key, nil, sep) + if valraw == nil { + return defaultVal + } + + val := make([]net.IP, 0, len(valraw)) + + for _, s := range valraw { + v := net.ParseIP(s) + if v == nil { + return defaultVal + } + + val = append(val, v) + } + + return val +} diff --git a/internal/parsers_test.go b/internal/parsers_test.go index 948cdb11..c2d5cef5 100644 --- a/internal/parsers_test.go +++ b/internal/parsers_test.go @@ -1426,6 +1426,136 @@ func Test_int32SliceOrDefault(t *testing.T) { } } +func Test_uintSliceOrDefault(t *testing.T) { + type args struct { + key string + defaultVal []uint + sep string + } + + type expected struct { + val []uint + } + + var tests = []struct { + name string + precond precondition + args args + expected expected + }{ + { + name: "env not set - default returned", + precond: precondition{ + setenv: setenv{ + isSet: false, + val: "1.05,2.07", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []uint{99}, + sep: ",", + }, + expected: expected{ + val: []uint{99}, + }, + }, + { + name: "env set - env value returned", + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "1,2", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []uint{99}, + sep: ",", + }, + expected: expected{ + val: []uint{1, 2}, + }, + }, + { + name: "env set, no separator - default value returned", + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "1,2", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []uint{99}, + sep: "", + }, + expected: expected{ + val: []uint{99}, + }, + }, + { + name: "env set, wrong separator - default value returned", + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "1,2", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []uint{99}, + sep: "|", + }, + expected: expected{ + val: []uint{99}, + }, + }, + { + name: "empty env value set - default returned", + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []uint{99}, + }, + expected: expected{ + val: []uint{99}, + }, + }, + { + name: "malformed env value set - default returned", + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "sssss,999", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []uint{99}, + sep: ",", + }, + expected: expected{ + val: []uint{99}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.precond.maybeSetEnv(t, tt.args.key) + + got := uintSliceOrDefault(tt.args.key, tt.args.defaultVal, tt.args.sep) + assert.Equal(t, tt.expected.val, got) + }) + } +} + func Test_uint8SliceOrDefault(t *testing.T) { type args struct { key string @@ -2225,6 +2355,22 @@ func Test_urlOrDefault(t *testing.T) { val: getURL(t, "postgres://user:pass@host.com:5432/path?k=v#f"), }, }, + { + name: "env set, corrupted - default value returned", + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "postgres://user:pass@host.com:5432/path?k=v#f%%2", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: getURL(t, rawDefault), + }, + expected: expected{ + val: getURL(t, rawDefault), + }, + }, { name: "empty env value set - default returned", precond: precondition{ @@ -2427,6 +2573,23 @@ func Test_durationSliceOrDefault(t *testing.T) { }, }, }, + { + name: "env set, corrupted - default returned", + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "2m,3hddd", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []time.Duration{time.Second}, + separator: ",", + }, + expected: expected{ + val: []time.Duration{time.Second}, + }, + }, { name: "empty env value set - default returned", precond: precondition{ @@ -3172,6 +3335,22 @@ func Test_ipOrDefault(t *testing.T) { val: getIP(t, "192.168.8.0"), }, }, + { + name: "env set, corrupted - default value returned", + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "192.168.8.0ssss", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: getIP(t, rawDefault), + }, + expected: expected{ + val: getIP(t, rawDefault), + }, + }, { name: "empty env value set - default returned", precond: precondition{ @@ -3254,6 +3433,23 @@ func Test_urlSliceOrDefault(t *testing.T) { }, }, }, + { + name: "env set, corrupted - default value returned", + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "https://google.com,htps://%%2github.com", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []url.URL{getURL(t, "https://bing.com")}, + separator: ",", + }, + expected: expected{ + val: []url.URL{getURL(t, "https://bing.com")}, + }, + }, { name: "empty env value set - default returned", precond: precondition{ @@ -3282,3 +3478,105 @@ func Test_urlSliceOrDefault(t *testing.T) { }) } } + +func Test_ipSliceOrDefault(t *testing.T) { + const rawDefault = "0.0.0.0" + + type args struct { + key string + defaultVal []net.IP + separator string + } + + type expected struct { + val []net.IP + } + + var tests = []struct { + name string + precond precondition + args args + expected expected + }{ + { + name: "env not set - default returned", + precond: precondition{ + setenv: setenv{ + isSet: false, + val: "192.168.8.0,2001:cb8::17", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []net.IP{getIP(t, rawDefault)}, + separator: ",", + }, + expected: expected{ + val: []net.IP{getIP(t, rawDefault)}, + }, + }, + { + name: "env set - env value returned", + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "192.168.8.0,2001:cb8::17", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []net.IP{getIP(t, rawDefault)}, + separator: ",", + }, + expected: expected{ + val: []net.IP{ + getIP(t, "192.168.8.0"), + getIP(t, "2001:cb8::17"), + }, + }, + }, + { + name: "env set, corrupted - default value returned", + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "192.168.8.0,sdsdsd", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []net.IP{getIP(t, rawDefault)}, + separator: ",", + }, + expected: expected{ + val: []net.IP{getIP(t, rawDefault)}, + }, + }, + { + name: "empty env value set - default returned", + precond: precondition{ + setenv: setenv{ + isSet: true, + val: "", + }, + }, + args: args{ + key: testEnvKey, + defaultVal: []net.IP{getIP(t, rawDefault)}, + separator: ",", + }, + expected: expected{ + val: []net.IP{getIP(t, rawDefault)}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.precond.maybeSetEnv(t, tt.args.key) + + got := ipSliceOrDefault(tt.args.key, tt.args.defaultVal, tt.args.separator) + assert.Equal(t, tt.expected.val, got) + }) + } +}