Skip to content

Commit 16fbcc9

Browse files
authored
feat: Implement Env generic function that returns error (#95)
* feat: Implement Env generic function that returns error * chore: Refactor makefile * docs: Update README * chore: Fix linter warings * chore: Fix sonar bugs * docs: Update README
1 parent be45ff6 commit 16fbcc9

File tree

5 files changed

+500
-24
lines changed

5 files changed

+500
-24
lines changed

Makefile

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
.PHONY: help, test-cover, prepare-cover-report, open-cover-report, update-readme-cover, update-readme-doc, test, test-regression, configure, sync-vendor, imports, fmt, format-project, install-tools, vet, lint-full, lint-pipeline, lint-sonar, release, release-local-snapshot, check-releaser, new-version
12
BIN_DIR=./bin
23

34
SHELL := env VERSION=$(VERSION) $(SHELL)
@@ -38,103 +39,84 @@ help:
3839
## Test coverage report.
3940
test-cover:
4041
./scripts/tests/coverage.sh
41-
.PHONY: test-cover
4242

4343
prepare-cover-report: test-cover
4444
$(COMPOSE_TOOLS_CMD_UP) prepare-cover-report prepare-cover-report
45-
.PHONY: prepare-cover-report
4645

4746
## Open coverage report.
4847
open-cover-report: prepare-cover-report
4948
./scripts/open-coverage-report.sh
50-
.PHONY: open-cover-report
5149

5250
## Update readme coverage.
5351
update-readme-cover: prepare-cover-report
5452
$(COMPOSE_TOOLS_CMD_UP) update-readme-coverage update-readme-coverage
55-
.PHONY: update-readme-cover
5653

5754
## Update readme doc.
5855
update-readme-doc:
5956
$(COMPOSE_TOOLS_CMD_UP) update-readme-doc update-readme-doc
60-
.PHONY: update-readme-doc
6157

6258
## Run tests.
6359
test:
6460
$(COMPOSE_TOOLS_CMD_UP) run-tests run-tests
65-
.PHONY: test
6661

6762
## Run regression tests.
6863
test-regression: test
69-
.PHONY: test-regression
7064

7165
## Sync vendor and install needed tools.
7266
configure: sync-vendor install-tools
7367

7468
## Sync vendor with go.mod.
7569
sync-vendor:
7670
./scripts/sync-vendor.sh
77-
.PHONY: sync-vendor
7871

7972
## Fix imports sorting.
8073
imports:
8174
$(COMPOSE_TOOLS_CMD_UP) fix-imports fix-imports
82-
.PHONY: imports
8375

8476
## Format code with go fmt.
8577
fmt:
8678
$(COMPOSE_TOOLS_CMD_UP) fix-fmt fix-fmt
87-
.PHONY: fmt
8879

8980
## Format code and sort imports.
9081
format-project: fmt imports
91-
.PHONY: format-project
9282

9383
## Installs vendored tools.
9484
install-tools:
9585
echo "Installing ${GOTOOLS_IMAGE_TAG}"
9686
$(COMPOSE_TOOLS_CMD_PULL)
97-
.PHONY: install-tools
9887

9988
## vet project
10089
vet:
10190
./scripts/linting/run-vet.sh
102-
.PHONY: vet
10391

10492
## Run full linting
10593
lint-full:
10694
$(COMPOSE_TOOLS_CMD_UP) lint-full lint-full
107-
.PHONY: lint-full
10895

10996
## Run linting for build pipeline
11097
lint-pipeline:
11198
$(COMPOSE_TOOLS_CMD_UP) lint-pipeline lint-pipeline
112-
.PHONY: lint-pipeline
11399

114100
## Run linting for sonar report
115101
lint-sonar:
116102
$(COMPOSE_TOOLS_CMD_UP) lint-sonar lint-sonar
117-
.PHONY: lint-sonar
118103

119104
## Release
120105
release:
121106
./scripts/release/release.sh
122-
.PHONY: release
123107

124108
## Release local snapshot
125109
release-local-snapshot:
126110
./scripts/release/local-snapshot-release.sh
127-
.PHONY: release-local-snapshot
128111

129112
## Check goreleaser config.
130113
check-releaser:
131114
./scripts/release/check.sh
132-
.PHONY: check-releaser
133115

134116
## Issue new release.
135117
new-version: vet test-regression
136118
./scripts/release/new-version.sh
137-
.PHONY: new-release
119+
138120

139121
.DEFAULT_GOAL := help
140122

README.md

Lines changed: 148 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,150 @@ Types supported:
5656

5757
## Examples
5858

59+
### Env
60+
61+
Env retrieves the value of the environment variable named by the key.
62+
If the variable is present in the environment the value will be parsed and returned.
63+
Otherwise, an error will be returned.
64+
65+
```golang
66+
package main
67+
68+
import (
69+
"fmt"
70+
"net"
71+
"net/url"
72+
"os"
73+
"time"
74+
75+
"github.com/obalunenko/getenv"
76+
"github.com/obalunenko/getenv/option"
77+
)
78+
79+
func main() {
80+
const (
81+
key = "GH_GETENV_TEST"
82+
)
83+
84+
var (
85+
val any
86+
err error
87+
)
88+
89+
defer func() {
90+
if err = os.Unsetenv(key); err != nil {
91+
panic(err)
92+
}
93+
}()
94+
95+
// string
96+
if err = os.Setenv(key, "golly"); err != nil {
97+
panic(err)
98+
}
99+
100+
val, err = getenv.Env[string](key)
101+
fmt.Printf("[%T]: %v; err: %v\n", val, val, err)
102+
103+
// int
104+
if err = os.Setenv(key, "123"); err != nil {
105+
panic(err)
106+
}
107+
108+
val, err = getenv.Env[int](key)
109+
fmt.Printf("[%T]: %v; err: %v\n", val, val, err)
110+
111+
// int conversion error
112+
if err = os.Setenv(key, "123s4"); err != nil {
113+
panic(err)
114+
}
115+
116+
val, err = getenv.Env[int](key)
117+
fmt.Printf("[%T]: %v; err: %v\n", val, val, err)
118+
119+
// time.Time
120+
if err = os.Setenv(key, "2022-01-20"); err != nil {
121+
panic(err)
122+
}
123+
124+
val, err = getenv.Env[time.Time](key, option.WithTimeLayout("2006-01-02"))
125+
fmt.Printf("[%T]: %v; err: %v\n", val, val, err)
126+
127+
// []float64
128+
if err = os.Setenv(key, "26.89,0.67"); err != nil {
129+
panic(err)
130+
}
131+
132+
val, err = getenv.Env[[]float64](key, option.WithSeparator(","))
133+
fmt.Printf("[%T]: %v; err: %v\n", val, val, err)
134+
135+
// time.Duration
136+
if err = os.Setenv(key, "2h35m"); err != nil {
137+
panic(err)
138+
}
139+
140+
val, err = getenv.Env[time.Duration](key)
141+
fmt.Printf("[%T]: %v; err: %v\n", val, val, err)
142+
143+
// url.URL
144+
if err = os.Setenv(key, "https://test:abcd123@golangbyexample.com:8000/tutorials/intro?type=advance&compact=false#history"); err != nil {
145+
panic(err)
146+
}
147+
148+
val, err = getenv.Env[url.URL](key)
149+
fmt.Printf("[%T]: %v; err: %v\n", val, val, err)
150+
151+
// net.IP
152+
if err = os.Setenv(key, "2001:cb8::17"); err != nil {
153+
panic(err)
154+
}
155+
156+
val, err = getenv.Env[net.IP](key)
157+
fmt.Printf("[%T]: %v; err: %v\n", val, val, err)
158+
159+
// []string
160+
if err = os.Setenv(key, "a,b,c,d"); err != nil {
161+
panic(err)
162+
}
163+
164+
val, err = getenv.Env[[]string](key, option.WithSeparator(","))
165+
fmt.Printf("[%T]: %v; err: %v\n", val, val, err)
166+
167+
// complex128
168+
if err = os.Setenv(key, "1+2i"); err != nil {
169+
panic(err)
170+
}
171+
172+
val, err = getenv.Env[complex128](key)
173+
fmt.Printf("[%T]: %v; err: %v\n", val, val, err)
174+
175+
// []complex64
176+
if err = os.Setenv(key, "1+2i,3+4i"); err != nil {
177+
panic(err)
178+
}
179+
180+
val, err = getenv.Env[[]complex64](key, option.WithSeparator(","))
181+
fmt.Printf("[%T]: %v; err: %v\n", val, val, err)
182+
183+
}
184+
185+
```
186+
187+
Output:
188+
189+
```
190+
[string]: golly; err: <nil>
191+
[int]: 123; err: <nil>
192+
[int]: 0; err: could not parse variable[GH_GETENV_TEST] value[123s4] to type[int]: invalid value
193+
[time.Time]: 2022-01-20 00:00:00 +0000 UTC; err: <nil>
194+
[[]float64]: [26.89 0.67]; err: <nil>
195+
[time.Duration]: 2h35m0s; err: <nil>
196+
[url.URL]: {https test:abcd123 golangbyexample.com:8000 /tutorials/intro false false type=advance&compact=false history }; err: <nil>
197+
[net.IP]: 2001:cb8::17; err: <nil>
198+
[[]string]: [a b c d]; err: <nil>
199+
[complex128]: (1+2i); err: <nil>
200+
[[]complex64]: [(1+2i) (3+4i)]; err: <nil>
201+
```
202+
59203
### EnvOrDefault
60204

61205
EnvOrDefault retrieves the value of the environment variable named by the key.
@@ -78,10 +222,12 @@ import (
78222
)
79223

80224
func main() {
81-
key := "GH_GETENV_TEST"
225+
const (
226+
key = "GH_GETENV_TEST"
227+
)
82228

83229
defer func() {
84-
if err := os.Unsetenv("GH_GETENV_TEST"); err != nil {
230+
if err := os.Unsetenv(key); err != nil {
85231
panic(err)
86232
}
87233
}()

getenv.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,45 @@
4747
package getenv
4848

4949
import (
50+
"errors"
51+
"fmt"
52+
"os"
53+
"reflect"
54+
5055
"github.com/obalunenko/getenv/internal"
5156
"github.com/obalunenko/getenv/option"
5257
)
5358

59+
var (
60+
// ErrNotSet is an error that is returned when the environment variable is not set.
61+
ErrNotSet = errors.New("not set")
62+
// ErrInvalidValue is an error that is returned when the environment variable is not valid.
63+
ErrInvalidValue = errors.New("invalid value")
64+
)
65+
66+
// 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.
68+
// Otherwise, an error will be returned.
69+
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
72+
73+
val := EnvOrDefault(key, defVal, options...)
74+
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)
81+
}
82+
83+
return val, fmt.Errorf("could not parse variable[%s] value[%v] to type[%T]: %w", key, v, defVal, ErrInvalidValue)
84+
}
85+
86+
return val, nil
87+
}
88+
5489
// EnvOrDefault retrieves the value of the environment variable named by the key.
5590
// If the variable is present in the environment the value will be parsed and returned.
5691
// Otherwise, the default value will be returned.

0 commit comments

Comments
 (0)