-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathenvcfg.go
474 lines (412 loc) · 13.5 KB
/
envcfg.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
package envcfg
import (
"reflect"
"regexp"
"strings"
"github.com/sethpollack/envcfg/internal/decoder"
"github.com/sethpollack/envcfg/internal/loader"
"github.com/sethpollack/envcfg/internal/matcher"
"github.com/sethpollack/envcfg/internal/parser"
"github.com/sethpollack/envcfg/internal/walker"
"github.com/sethpollack/envcfg/sources/dotenv"
"github.com/sethpollack/envcfg/sources/mapenv"
"github.com/sethpollack/envcfg/sources/osenv"
)
type Option func(*Options)
type Options struct {
Walker *walker.Walker
Loader *loader.Loader
Decoder *decoder.Decoder
Parser *parser.Parser
Matcher *matcher.Matcher
}
func build(opts ...Option) (*Options, error) {
o := &Options{
Walker: walker.New(),
Decoder: decoder.New(),
Loader: &loader.Loader{},
Matcher: matcher.New(),
Parser: parser.New(),
}
for _, opt := range opts {
opt(o)
}
if len(o.Loader.Sources) == 0 {
o.Loader.Sources = []loader.Source{osenv.New()}
}
loaded, err := o.Loader.Load()
if err != nil {
return nil, err
}
o.Matcher.EnvVars = loaded
o.Walker.Matcher = o.Matcher
o.Walker.Decoder = o.Decoder
o.Walker.Parser = o.Parser
return o, nil
}
// WithTagName sets a custom struct tag name to override the default "env" tag.
func WithTagName(tag string) Option {
return func(o *Options) {
o.Walker.TagName = tag
o.Matcher.TagName = tag
}
}
// WithDelimiterTag sets the struct tag name used for the delimiter.
// The default tag name is "delim".
func WithDelimiterTag(tag string) Option {
return func(o *Options) {
o.Walker.DelimTag = tag
}
}
// WithDelimiter sets the delimiter used to separate slice/map elements
// in environment variable values. The default delimiter is ",".
func WithDelimiter(delim string) Option {
return func(o *Options) {
o.Walker.DefaultDelim = delim
}
}
// WithSeparatorTag sets the struct tag name used for the separator.
// The default tag name is "sep".
func WithSeparatorTag(tag string) Option {
return func(o *Options) {
o.Walker.SepTag = tag
}
}
// WithSeparator sets the separator used for key-value pairs in map environment
// variable values. The default separator is ":".
func WithSeparator(sep string) Option {
return func(o *Options) {
o.Walker.DefaultSep = sep
}
}
// WithDecodeUnsetTag sets the struct tag name used for decoding unset environment variables.
// The default tag name is "decodeunset".
func WithDecodeUnsetTag(tag string) Option {
return func(o *Options) {
o.Walker.DecodeUnsetTag = tag
}
}
// WithDecodeUnset enables decoding unset environment variables.
// By default, unset environment variables are not decoded.
func WithDecodeUnset() Option {
return func(o *Options) {
o.Walker.DecodeUnset = true
}
}
// WithInitTag sets the struct tag name used for initialization mode.
// The default tag name is "init".
func WithInitTag(tag string) Option {
return func(o *Options) {
o.Walker.InitTag = tag
}
}
// WithInitAny enables automatic initialization nil pointers
// if environment variables are found or if default values are provided.
// By default they are initialized only when a matching
// environment variable is found.
func WithInitAny() Option {
return func(o *Options) {
o.Walker.InitMode = walker.InitAny
}
}
// WithInitNever disables automatic initialization of nil pointers
// By default they are initialized only when a matching
// environment variable is found.
func WithInitNever() Option {
return func(o *Options) {
o.Walker.InitMode = walker.InitNever
}
}
// WithInitAlways enables automatic initialization of nil pointers
// regardless of whether matching environment variables are found.
// By default they are initialized only when a matching
// environment variable is found.
func WithInitAlways() Option {
return func(o *Options) {
o.Walker.InitMode = walker.InitAlways
}
}
// WithDefaultTag sets the struct tag name used for default values.
// The default tag name is "default".
func WithDefaultTag(tag string) Option {
return func(o *Options) {
o.Matcher.DefaultTag = tag
}
}
// WithExpandTag sets the struct tag name used for environment variable expansion.
// The default tag name is "expand".
func WithExpandTag(tag string) Option {
return func(o *Options) {
o.Matcher.ExpandTag = tag
}
}
// WithFileTag sets the struct tag name used for file paths.
// The default tag name is "file".
func WithFileTag(tag string) Option {
return func(o *Options) {
o.Matcher.FileTag = tag
}
}
// WithNotEmptyTag sets the struct tag name used for validating that values are not empty.
// The default tag name is "notempty".
func WithNotEmptyTag(tag string) Option {
return func(o *Options) {
o.Matcher.NotEmptyTag = tag
}
}
// WithNotEmpty is a global setting to validate that values are not empty.
// By default, empty values are not allowed.
func WithNotEmpty() Option {
return func(o *Options) {
o.Matcher.NotEmpty = true
}
}
// WithExpand is a global setting to expand environment variables in values.
// By default, environment variables are not expanded.
func WithExpand() Option {
return func(o *Options) {
o.Matcher.Expand = true
}
}
// WithRequiredTag sets the struct tag name used for required values.
// The default tag name is "required".
func WithRequiredTag(tag string) Option {
return func(o *Options) {
o.Matcher.RequiredTag = tag
}
}
// WithRequired is a global setting to validate that values are required.
// By default, fields are not required.
func WithRequired() Option {
return func(o *Options) {
o.Matcher.Required = true
}
}
// WithDisableFallback enforces strict matching using the "env" tag.
// By default, it will try the field name, snake case field name, and all struct tags until a match is found.
func WithDisableFallback() Option {
return func(o *Options) {
o.Matcher.DisableFallback = true
}
}
// WithDecoder registers a custom decoder function for a specific interface.
func WithDecoder(iface any, f func(v any, value string) error) Option {
return func(o *Options) {
o.Decoder.Decoders[iface] = f
}
}
// WithTypeParser registers a custom parser function for a specific type.
// This allows extending the parser to support additional types beyond
// the built-in supported types.
func WithTypeParser(t reflect.Type, f func(value string) (any, error)) Option {
return func(o *Options) {
o.Parser.TypeParsers[t] = f
}
}
// WithTypeParsers registers multiple custom parser functions for specific types.
// This allows extending the parser to support additional types beyond
// the built-in supported types.
// This is a convenience function for registering multiple type parsers at once.
func WithTypeParsers(parsers map[reflect.Type]func(value string) (any, error)) Option {
return func(o *Options) {
for t, f := range parsers {
o.Parser.TypeParsers[t] = f
}
}
}
// WithKindParser registers a custom parser function for a specific reflect.Kind.
// This allows extending the parser to support additional kinds beyond
// the built-in supported kinds.
func WithKindParser(k reflect.Kind, f func(value string) (any, error)) Option {
return func(o *Options) {
o.Parser.KindParsers[k] = f
}
}
// WithKindParsers registers multiple custom parser functions for specific reflect.Kinds.
// This allows extending the parser to support additional kinds beyond
// the built-in supported kinds.
// This is a convenience function for registering multiple kind parsers at once.
func WithKindParsers(parsers map[reflect.Kind]func(value string) (any, error)) Option {
return func(o *Options) {
for k, f := range parsers {
o.Parser.KindParsers[k] = f
}
}
}
type LoaderOption func(*loader.Loader)
func WithLoader(opts ...LoaderOption) Option {
return func(o *Options) {
l := &loader.Loader{}
for _, opt := range opts {
opt(l)
}
if len(l.Sources) == 0 {
l.Sources = []loader.Source{osenv.New()}
}
o.Loader.Sources = append(o.Loader.Sources, l)
}
}
// WithSource adds a source to the loader.
func WithSource(source loader.Source) LoaderOption {
return func(l *loader.Loader) {
l.Sources = append(l.Sources, source)
}
}
// WithSources adds multiple sources to the loader.
// This is a convenience function for adding multiple sources at once.
func WithSources(sources ...loader.Source) LoaderOption {
return func(l *loader.Loader) {
l.Sources = append(l.Sources, sources...)
}
}
// WithFilter registers a custom filter function for environment variables.
// The filter function is used to determine which environment variables should be used.
func WithFilter(filter func(string) bool) LoaderOption {
return func(l *loader.Loader) {
l.Filters = append(l.Filters, filter)
}
}
// WithTransform registers a custom transformation function for environment variables.
// The transformation function is used to modify environment variable keys before they are applied.
func WithTransform(transform func(string) string) LoaderOption {
return func(l *loader.Loader) {
l.Transforms = append(l.Transforms, transform)
}
}
// WithPrefix filters environment variables by prefix and strips the prefix
// before matching. For example, with prefix "APP_", the environment variable
// "APP_PORT=8080" would be matched as "PORT=8080".
func WithPrefix(prefix string) LoaderOption {
return func(l *loader.Loader) {
l.Filters = append(l.Filters, func(key string) bool {
return strings.HasPrefix(key, prefix)
})
l.Transforms = append(l.Transforms, func(key string) string {
return strings.TrimPrefix(key, prefix)
})
}
}
// WithSuffix filters environment variables by suffix and strips the suffix
// during matching. For example, with suffix "_TEST", the environment variable
// "PORT_TEST=8080" would be matched as "PORT=8080".
func WithSuffix(suffix string) LoaderOption {
return func(l *loader.Loader) {
l.Filters = append(l.Filters, func(key string) bool {
return strings.HasSuffix(key, suffix)
})
l.Transforms = append(l.Transforms, func(key string) string {
return strings.TrimSuffix(key, suffix)
})
}
}
// WithHasPrefix filters environment variables by prefix but preserves the prefix
// during matching. For example, with prefix "APP_", the environment variable
// "APP_PORT=8080" would be matched as "APP_PORT=8080".
func WithHasPrefix(prefix string) LoaderOption {
return func(l *loader.Loader) {
l.Filters = append(l.Filters, func(key string) bool {
return strings.HasPrefix(key, prefix)
})
}
}
// WithHasSuffix filters environment variables by suffix but preserves the suffix
// during matching. For example, with suffix "_TEST", the environment variable
// "PORT_TEST=8080" would be matched as "PORT_TEST=8080".
func WithHasSuffix(suffix string) LoaderOption {
return func(l *loader.Loader) {
l.Filters = append(l.Filters, func(key string) bool {
return strings.HasSuffix(key, suffix)
})
}
}
// WithHasMatch filters environment variables using a regular expression pattern.
func WithHasMatch(pattern *regexp.Regexp) LoaderOption {
return func(l *loader.Loader) {
l.Filters = append(l.Filters, func(key string) bool {
return pattern.MatchString(key)
})
}
}
// WithKeys filters environment variables by specific keys.
// This is a convenience function for adding multiple keys at once.
func WithKeys(keys ...string) LoaderOption {
return func(l *loader.Loader) {
l.Filters = append(l.Filters, func(key string) bool {
for _, k := range keys {
if k == key {
return true
}
}
return false
})
}
}
// WithTrimPrefix removes the specified prefix from environment variable names
// before matching. Unlike WithPrefix, it does not filter variables.
func WithTrimPrefix(prefix string) LoaderOption {
return func(l *loader.Loader) {
l.Transforms = append(l.Transforms, func(key string) string {
return strings.TrimPrefix(key, prefix)
})
}
}
// WithTrimSuffix removes the specified suffix from environment variable names
// before matching. Unlike WithHasSuffix, it does not filter variables.
func WithTrimSuffix(suffix string) LoaderOption {
return func(l *loader.Loader) {
l.Transforms = append(l.Transforms, func(key string) string {
return strings.TrimSuffix(key, suffix)
})
}
}
// WithMapEnvSource uses the provided map of environment variables instead of reading
// from the OS environment.
func WithMapEnvSource(envs map[string]string) LoaderOption {
return func(l *loader.Loader) {
l.Sources = append(l.Sources, mapenv.New(envs))
}
}
// WithOSEnvSource adds OS environment variables as a source.
func WithOSEnvSource() LoaderOption {
return func(l *loader.Loader) {
l.Sources = append(l.Sources, osenv.New())
}
}
// WithDotEnvSource adds environment variables from a file as a source.
// The file should contain environment variables in KEY=VALUE format.
func WithDotEnvSource(path string) LoaderOption {
return func(l *loader.Loader) {
l.Sources = append(l.Sources, dotenv.New(path))
}
}
// Parse processes the provided configuration struct using environment variables
// and the specified options. It traverses the struct fields and applies the
// environment configuration according to the defined rules and options.
func Parse(cfg any, opts ...Option) error {
b, err := build(opts...)
if err != nil {
return err
}
return b.Walker.Walk(cfg)
}
// MustParse is like Parse but panics if an error occurs during parsing.
func MustParse(cfg any, opts ...Option) {
if err := Parse(cfg, opts...); err != nil {
panic(err)
}
}
// ParseAs is a generic version of Parse that creates and returns a new instance
// of the specified type T with the environment configuration applied.
func ParseAs[T any](opts ...Option) (T, error) {
var t T
err := Parse(&t, opts...)
return t, err
}
// MustParseAs is like ParseAs but panics if an error occurs during parsing.
func MustParseAs[T any](opts ...Option) T {
t, err := ParseAs[T](opts...)
if err != nil {
panic(err)
}
return t
}