Skip to content

Commit be1c262

Browse files
committedAug 7, 2024··
docs: more examples
1 parent 19de57d commit be1c262

File tree

3 files changed

+360
-169
lines changed

3 files changed

+360
-169
lines changed
 

‎env_examples_test.go

-143
This file was deleted.

‎env_test.go

-26
Original file line numberDiff line numberDiff line change
@@ -1403,32 +1403,6 @@ func TestPrecedenceUnmarshalText(t *testing.T) {
14031403
isEqual(t, []LogLevel{DebugLevel, InfoLevel}, cfg.LogLevels)
14041404
}
14051405

1406-
func ExampleParseWithOptions() {
1407-
type thing struct {
1408-
desc string
1409-
}
1410-
1411-
type conf struct {
1412-
Thing thing `env:"THING"`
1413-
}
1414-
1415-
os.Setenv("THING", "my thing")
1416-
1417-
c := conf{}
1418-
1419-
err := ParseWithOptions(&c, Options{FuncMap: map[reflect.Type]ParserFunc{
1420-
reflect.TypeOf(thing{}): func(v string) (interface{}, error) {
1421-
return thing{desc: v}, nil
1422-
},
1423-
}})
1424-
if err != nil {
1425-
fmt.Println(err)
1426-
}
1427-
fmt.Println(c.Thing.desc)
1428-
// Output:
1429-
// my thing
1430-
}
1431-
14321406
func TestFile(t *testing.T) {
14331407
type config struct {
14341408
SecretKey string `env:"SECRET_KEY,file"`

‎example_test.go

+360
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
package env
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
"reflect"
8+
)
9+
10+
// Parse the environment into a struct.
11+
func ExampleParse() {
12+
type Config struct {
13+
Home string `env:"HOME"`
14+
}
15+
os.Setenv("HOME", "/tmp/fakehome")
16+
var cfg Config
17+
if err := Parse(&cfg); err != nil {
18+
fmt.Println(err)
19+
}
20+
fmt.Printf("%+v", cfg)
21+
// Output: {Home:/tmp/fakehome}
22+
}
23+
24+
// Parse the environment into a struct using generics.
25+
func ExampleParseAs() {
26+
type Config struct {
27+
Home string `env:"HOME"`
28+
}
29+
os.Setenv("HOME", "/tmp/fakehome")
30+
cfg, err := ParseAs[Config]()
31+
if err != nil {
32+
fmt.Println(err)
33+
}
34+
fmt.Printf("%+v", cfg)
35+
// Output: {Home:/tmp/fakehome}
36+
}
37+
38+
func ExampleParse_required() {
39+
type Config struct {
40+
Nope string `env:"NOPE,required"`
41+
}
42+
var cfg Config
43+
if err := Parse(&cfg); err != nil {
44+
fmt.Println(err)
45+
}
46+
fmt.Printf("%+v", cfg)
47+
// Output: env: required environment variable "NOPE" is not set
48+
// {Nope:}
49+
}
50+
51+
// While `required` demands the environment variable to be set, it doesn't check
52+
// its value. If you want to make sure the environment is set and not empty, you
53+
// need to use the `notEmpty` tag option instead (`env:"SOME_ENV,notEmpty"`).
54+
func ExampleParse_notEmpty() {
55+
type Config struct {
56+
Nope string `env:"NOPE,notEmpty"`
57+
}
58+
os.Setenv("NOPE", "")
59+
var cfg Config
60+
if err := Parse(&cfg); err != nil {
61+
fmt.Println(err)
62+
}
63+
fmt.Printf("%+v", cfg)
64+
// Output: env: environment variable "NOPE" should not be empty
65+
// {Nope:}
66+
}
67+
68+
// The `env` tag option `unset` (e.g., `env:"tagKey,unset"`) can be added
69+
// to ensure that some environment variable is unset after reading it.
70+
func ExampleParse_unset() {
71+
type Config struct {
72+
Secret string `env:"SECRET,unset"`
73+
}
74+
os.Setenv("SECRET", "1234")
75+
var cfg Config
76+
if err := Parse(&cfg); err != nil {
77+
fmt.Println(err)
78+
}
79+
fmt.Printf("%+v - %s", cfg, os.Getenv("SECRET"))
80+
// Output: {Secret:1234} -
81+
}
82+
83+
// If you set the `expand` option, environment variables (either in `${var}` or
84+
// `$var` format) in the string will be replaced according with the actual
85+
// value of the variable. For example:
86+
func ExampleParse_expand() {
87+
type Config struct {
88+
Expand1 string `env:"EXPAND_1,expand"`
89+
Expand2 string `env:"EXPAND_2,expand" envDefault:"ABC_${EXPAND_1}"`
90+
}
91+
os.Setenv("EXPANDING", "HI")
92+
os.Setenv("EXPAND_1", "HELLO_${EXPANDING}")
93+
var cfg Config
94+
if err := Parse(&cfg); err != nil {
95+
fmt.Println(err)
96+
}
97+
fmt.Printf("%+v", cfg)
98+
// Output: {Expand1:HELLO_HI Expand2:ABC_HELLO_HI}
99+
}
100+
101+
// You can automatically initialize `nil` pointers regardless of if a variable
102+
// is set for them or not.
103+
// This behavior can be enabled by using the `init` tag option.
104+
func ExampleParse_init() {
105+
type Inner struct {
106+
A string `env:"OLA" envDefault:"HI"`
107+
}
108+
type Config struct {
109+
NilInner *Inner
110+
InitInner *Inner `env:",init"`
111+
}
112+
var cfg Config
113+
if err := Parse(&cfg); err != nil {
114+
fmt.Println(err)
115+
}
116+
fmt.Print(cfg.NilInner, cfg.InitInner)
117+
// Output: <nil> &{HI}
118+
}
119+
120+
// You can define the default value for a field by either using the
121+
// `envDefault` tag, or when initializing the `struct`.
122+
//
123+
// Default values defined as `struct` tags will overwrite existing values
124+
// during `Parse`.
125+
func ExampleParse_setDefaults() {
126+
type Config struct {
127+
Foo string `env:"FOO"`
128+
Bar string `env:"BAR" envDefault:"bar"`
129+
}
130+
cfg := Config{
131+
Foo: "foo",
132+
}
133+
if err := Parse(&cfg); err != nil {
134+
fmt.Println(err)
135+
}
136+
fmt.Printf("%+v", cfg)
137+
// Output: {Foo:foo Bar:bar}
138+
}
139+
140+
// You might want to listen to value sets and, for example, log something or do
141+
// some other kind of logic.
142+
func ExampleParseWithOptions_onSet() {
143+
type config struct {
144+
Home string `env:"HOME,required"`
145+
Port int `env:"PORT" envDefault:"3000"`
146+
IsProduction bool `env:"PRODUCTION"`
147+
NoEnvTag bool
148+
Inner struct{} `envPrefix:"INNER_"`
149+
}
150+
os.Setenv("HOME", "/tmp/fakehome")
151+
var cfg config
152+
if err := ParseWithOptions(&cfg, Options{
153+
OnSet: func(tag string, value interface{}, isDefault bool) {
154+
fmt.Printf("Set %s to %v (default? %v)\n", tag, value, isDefault)
155+
},
156+
}); err != nil {
157+
fmt.Println("failed:", err)
158+
}
159+
fmt.Printf("%+v", cfg)
160+
// Output: Set HOME to /tmp/fakehome (default? false)
161+
// Set PORT to 3000 (default? true)
162+
// Set PRODUCTION to (default? false)
163+
// {Home:/tmp/fakehome Port:3000 IsProduction:false NoEnvTag:false Inner:{}}
164+
}
165+
166+
// By default, env supports anything that implements the `TextUnmarshaler`
167+
// interface, which includes `time.Time`.
168+
//
169+
// The upside is that depending on the format you need, you don't need to change
170+
// anything.
171+
//
172+
// The downside is that if you do need time in another format, you'll need to
173+
// create your own type and implement `TextUnmarshaler`.
174+
func ExampleParse_customTimeFormat() {
175+
// type MyTime time.Time
176+
//
177+
// func (t *MyTime) UnmarshalText(text []byte) error {
178+
// tt, err := time.Parse("2006-01-02", string(text))
179+
// *t = MyTime(tt)
180+
// return err
181+
// }
182+
183+
type Config struct {
184+
SomeTime MyTime `env:"SOME_TIME"`
185+
}
186+
os.Setenv("SOME_TIME", "2021-05-06")
187+
var cfg Config
188+
if err := Parse(&cfg); err != nil {
189+
fmt.Println(err)
190+
}
191+
fmt.Print(cfg.SomeTime)
192+
// Output: {0 63755856000 <nil>}
193+
}
194+
195+
// Parse using extra options.
196+
func ExampleParseWithOptions_customTypes() {
197+
type Thing struct {
198+
desc string
199+
}
200+
201+
type Config struct {
202+
Thing Thing `env:"THING"`
203+
}
204+
205+
os.Setenv("THING", "my thing")
206+
207+
c := Config{}
208+
err := ParseWithOptions(&c, Options{
209+
FuncMap: map[reflect.Type]ParserFunc{
210+
reflect.TypeOf(Thing{}): func(v string) (interface{}, error) {
211+
return Thing{desc: v}, nil
212+
},
213+
},
214+
})
215+
if err != nil {
216+
fmt.Println(err)
217+
}
218+
fmt.Print(c.Thing.desc)
219+
// Output: my thing
220+
}
221+
222+
// Make all fields required by default.
223+
func ExampleParseWithOptions_allFieldsRequired() {
224+
type Config struct {
225+
Username string `env:"USERNAME" envDefault:"admin"`
226+
Password string `env:"PASSWORD"`
227+
}
228+
229+
var cfg Config
230+
if err := ParseWithOptions(&cfg, Options{
231+
RequiredIfNoDef: true,
232+
}); err != nil {
233+
fmt.Println(err)
234+
}
235+
fmt.Printf("%+v\n", cfg)
236+
// Output: env: required environment variable "PASSWORD" is not set
237+
// {Username:admin Password:}
238+
}
239+
240+
// Set a custom environment.
241+
// By default, `os.Environ()` is used.
242+
func ExampleParseWithOptions_setEnv() {
243+
type Config struct {
244+
Username string `env:"USERNAME" envDefault:"admin"`
245+
Password string `env:"PASSWORD"`
246+
}
247+
248+
var cfg Config
249+
if err := ParseWithOptions(&cfg, Options{
250+
Environment: map[string]string{
251+
"USERNAME": "john",
252+
"PASSWORD": "cena",
253+
},
254+
}); err != nil {
255+
fmt.Println(err)
256+
}
257+
fmt.Printf("%+v\n", cfg)
258+
// Output: {Username:john Password:cena}
259+
}
260+
261+
// Handling slices of complex types.
262+
func ExampleParse_complexSlices() {
263+
type Test struct {
264+
Str string `env:"STR"`
265+
Num int `env:"NUM"`
266+
}
267+
type Config struct {
268+
Foo []Test `envPrefix:"FOO"`
269+
}
270+
271+
os.Setenv("FOO_0_STR", "a")
272+
os.Setenv("FOO_0_NUM", "1")
273+
os.Setenv("FOO_1_STR", "b")
274+
os.Setenv("FOO_1_NUM", "2")
275+
276+
var cfg Config
277+
if err := Parse(&cfg); err != nil {
278+
fmt.Println(err)
279+
}
280+
fmt.Printf("%+v\n", cfg)
281+
// Output: {Foo:[{Str:a Num:1} {Str:b Num:2}]}
282+
}
283+
284+
// Setting prefixes for inner types.
285+
func ExampleParse_prefix() {
286+
type Inner struct {
287+
Foo string `env:"FOO,required"`
288+
}
289+
type Config struct {
290+
A Inner `envPrefix:"A_"`
291+
B Inner `envPrefix:"B_"`
292+
}
293+
os.Setenv("A_FOO", "a")
294+
os.Setenv("B_FOO", "b")
295+
var cfg Config
296+
if err := Parse(&cfg); err != nil {
297+
fmt.Println(err)
298+
}
299+
fmt.Printf("%+v", cfg)
300+
// Output: {A:{Foo:a} B:{Foo:b}}
301+
}
302+
303+
// Use a different tag name than `env`.
304+
func ExampleParseWithOptions_tagName() {
305+
type Config struct {
306+
Home string `json:"HOME"`
307+
}
308+
os.Setenv("HOME", "hello")
309+
var cfg Config
310+
if err := ParseWithOptions(&cfg, Options{
311+
TagName: "json",
312+
}); err != nil {
313+
fmt.Println(err)
314+
}
315+
fmt.Printf("%+v", cfg)
316+
// Output: {Home:hello}
317+
}
318+
319+
// If you don't want to set the `env` tag on every field, you can use the
320+
// `UseFieldNameByDefault` option.
321+
//
322+
// It will use the field name to define the environment variable name.
323+
// So, `Foo` becomes `FOO`, `FooBar` becomes `FOO_BAR`, and so on.
324+
func ExampleParseWithOptions_useFieldName() {
325+
type Config struct {
326+
Foo string
327+
}
328+
os.Setenv("FOO", "bar")
329+
var cfg Config
330+
if err := ParseWithOptions(&cfg, Options{
331+
UseFieldNameByDefault: true,
332+
}); err != nil {
333+
fmt.Println(err)
334+
}
335+
fmt.Printf("%+v", cfg)
336+
// Output: {Foo:bar}
337+
}
338+
339+
// The `env` tag option `file` (e.g., `env:"tagKey,file"`) can be added in
340+
// order to indicate that the value of the variable shall be loaded from a
341+
// file.
342+
//
343+
// The path of that file is given by the environment variable associated with
344+
// it.
345+
func ExampleParse_fromFile() {
346+
f, _ := os.CreateTemp("", "")
347+
_, _ = io.WriteString(f, "super secret")
348+
_ = f.Close()
349+
350+
type Config struct {
351+
Secret string `env:"SECRET,file"`
352+
}
353+
os.Setenv("SECRET", f.Name())
354+
var cfg Config
355+
if err := Parse(&cfg); err != nil {
356+
fmt.Println(err)
357+
}
358+
fmt.Printf("%+v", cfg)
359+
// Output: {Secret:super secret}
360+
}

0 commit comments

Comments
 (0)
Please sign in to comment.