14
14
15
15
// Package envconfig populates struct fields based on environment variable
16
16
// values (or anything that responds to "Lookup"). Structs declare their
17
- // environment dependencies using the ` env` tag with the key being the name of
17
+ // environment dependencies using the " env" tag with the key being the name of
18
18
// the environment variable, case sensitive.
19
19
//
20
- // type MyStruct struct {
21
- // A string `env:"A"` // resolves A to $A
22
- // B string `env:"B,required"` // resolves B to $B, errors if $B is unset
23
- // C string `env:"C,default=foo"` // resolves C to $C, defaults to "foo"
20
+ // type MyStruct struct {
21
+ // A string `env:"A"` // resolves A to $A
22
+ // B string `env:"B,required"` // resolves B to $B, errors if $B is unset
23
+ // C string `env:"C,default=foo"` // resolves C to $C, defaults to "foo"
24
24
//
25
- // D string `env:"D,required,default=foo"` // error, cannot be required and default
26
- // E string `env:""` // error, must specify key
27
- // }
25
+ // D string `env:"D,required,default=foo"` // error, cannot be required and default
26
+ // E string `env:""` // error, must specify key
27
+ // }
28
28
//
29
29
// All built-in types are supported except Func and Chan. If you need to define
30
30
// a custom decoder, implement Decoder:
31
31
//
32
- // type MyStruct struct {
33
- // field string
34
- // }
32
+ // type MyStruct struct {
33
+ // field string
34
+ // }
35
35
//
36
- // func (v *MyStruct) EnvDecode(val string) error {
37
- // v.field = fmt.Sprintf("PREFIX-%s", val)
38
- // return nil
39
- // }
36
+ // func (v *MyStruct) EnvDecode(val string) error {
37
+ // v.field = fmt.Sprintf("PREFIX-%s", val)
38
+ // return nil
39
+ // }
40
40
//
41
41
// In the environment, slices are specified as comma-separated values:
42
42
//
43
- // export MYVAR="a,b,c,d" // []string{"a", "b", "c", "d"}
43
+ // export MYVAR="a,b,c,d" // []string{"a", "b", "c", "d"}
44
44
//
45
45
// In the environment, maps are specified as comma-separated key:value pairs:
46
46
//
47
- // export MYVAR="a:b,c:d" // map[string]string{"a":"b", "c":"d"}
47
+ // export MYVAR="a:b,c:d" // map[string]string{"a":"b", "c":"d"}
48
48
//
49
49
// If you need to modify environment variable values before processing, you can
50
50
// specify a custom mutator:
51
51
//
52
- // type Config struct {
53
- // Password `env:"PASSWORD_SECRET"`
54
- // }
52
+ // type Config struct {
53
+ // Password `env:"PASSWORD_SECRET"`
54
+ // }
55
55
//
56
- // func resolveSecretFunc(ctx context.Context, key, value string) (string, error) {
57
- // if strings.HasPrefix(value, "secret://") {
58
- // return secretmanager.Resolve(ctx, value) // example
59
- // }
60
- // return value, nil
61
- // }
62
- //
63
- // var config Config
64
- // ProcessWith(&config, OsLookuper(), resolveSecretFunc)
56
+ // func resolveSecretFunc(ctx context.Context, key, value string) (string, error) {
57
+ // if strings.HasPrefix(value, "secret://") {
58
+ // return secretmanager.Resolve(ctx, value) // example
59
+ // }
60
+ // return value, nil
61
+ // }
65
62
//
63
+ // var config Config
64
+ // ProcessWith(&config, OsLookuper(), resolveSecretFunc)
66
65
package envconfig
67
66
68
67
import (
@@ -74,7 +73,6 @@ import (
74
73
"fmt"
75
74
"os"
76
75
"reflect"
77
- "regexp"
78
76
"strconv"
79
77
"strings"
80
78
"time"
@@ -95,8 +93,6 @@ const (
95
93
defaultSeparator = ":"
96
94
)
97
95
98
- var envvarNameRe = regexp .MustCompile (`\A[a-zA-Z_][a-zA-Z0-9_]*\z` )
99
-
100
96
// Error is a custom error type for errors returned by envconfig.
101
97
type Error string
102
98
@@ -138,7 +134,7 @@ func (o *osLookuper) Lookup(key string) (string, bool) {
138
134
return os .LookupEnv (key )
139
135
}
140
136
141
- // OsLookuper returns a lookuper that uses the environment (os.LookupEnv) to
137
+ // OsLookuper returns a lookuper that uses the environment ([ os.LookupEnv] ) to
142
138
// resolve values.
143
139
func OsLookuper () Lookuper {
144
140
return new (osLookuper )
@@ -203,12 +199,11 @@ func MultiLookuper(lookupers ...Lookuper) Lookuper {
203
199
// Decoder is an interface that custom types/fields can implement to control how
204
200
// decoding takes place. For example:
205
201
//
206
- // type MyType string
207
- //
208
- // func (mt MyType) EnvDecode(val string) error {
209
- // return "CUSTOM-"+val
210
- // }
202
+ // type MyType string
211
203
//
204
+ // func (mt MyType) EnvDecode(val string) error {
205
+ // return "CUSTOM-"+val
206
+ // }
212
207
type Decoder interface {
213
208
EnvDecode (val string ) error
214
209
}
@@ -229,7 +224,7 @@ type options struct {
229
224
Required bool
230
225
}
231
226
232
- // Process processes the struct using the environment. See ProcessWith for a
227
+ // Process processes the struct using the environment. See [ ProcessWith] for a
233
228
// more customizable version.
234
229
func Process (ctx context.Context , i interface {}) error {
235
230
return ProcessWith (ctx , i , OsLookuper ())
@@ -427,7 +422,7 @@ func keyAndOpts(tag string) (string, *options, error) {
427
422
parts := strings .Split (tag , "," )
428
423
key , tagOpts := strings .TrimSpace (parts [0 ]), parts [1 :]
429
424
430
- if key != "" && ! envvarNameRe . MatchString (key ) {
425
+ if key != "" && ! validateEnvName (key ) {
431
426
return "" , nil , fmt .Errorf ("%q: %w " , key , ErrInvalidEnvvarName )
432
427
}
433
428
@@ -689,3 +684,34 @@ func processField(v string, ef reflect.Value, delimiter, separator string, noIni
689
684
690
685
return nil
691
686
}
687
+
688
+ // validateEnvName validates the given string conforms to being a valid
689
+ // environment variable.
690
+ //
691
+ // Per IEEE Std 1003.1-2001 environment variables consist solely of uppercase
692
+ // letters, digits, and _, and do not begin with a digit.
693
+ func validateEnvName (s string ) bool {
694
+ if s == "" {
695
+ return false
696
+ }
697
+
698
+ for i , r := range s {
699
+ if (i == 0 && ! isLetter (r )) || (! isLetter (r ) && ! isNumber (r ) && r != '_' ) {
700
+ return false
701
+ }
702
+ }
703
+
704
+ return true
705
+ }
706
+
707
+ // isLetter returns true if the given rune is a letter between a-z,A-Z. This is
708
+ // different than unicode.IsLetter which includes all L character cases.
709
+ func isLetter (r rune ) bool {
710
+ return (r >= 'a' && r <= 'z' ) || (r >= 'A' && r <= 'Z' )
711
+ }
712
+
713
+ // isNumber returns true if the given run is a number between 0-9. This is
714
+ // different than unicode.IsNumber in that it only allows 0-9.
715
+ func isNumber (r rune ) bool {
716
+ return r >= '0' && r <= '9'
717
+ }
0 commit comments