// Copyright The envconfig Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.
// Package envconfig populates struct fields based on environment variable// values (or anything that responds to "Lookup"). Structs declare their// environment dependencies using the "env" tag with the key being the name of// the environment variable, case sensitive.//// type MyStruct struct {// A string `env:"A"` // resolves A to $A// B string `env:"B,required"` // resolves B to $B, errors if $B is unset// C string `env:"C,default=foo"` // resolves C to $C, defaults to "foo"//// D string `env:"D,required,default=foo"` // error, cannot be required and default// E string `env:""` // error, must specify key// }//// All built-in types are supported except Func and Chan. If you need to define// a custom decoder, implement Decoder://// type MyStruct struct {// field string// }//// func (v *MyStruct) EnvDecode(val string) error {// v.field = fmt.Sprintf("PREFIX-%s", val)// return nil// }//// In the environment, slices are specified as comma-separated values://// export MYVAR="a,b,c,d" // []string{"a", "b", "c", "d"}//// In the environment, maps are specified as comma-separated key:value pairs://// export MYVAR="a:b,c:d" // map[string]string{"a":"b", "c":"d"}//// For more configuration options and examples, see the documentation.
package envconfigimport ()const ( envTag = "env" optDecodeUnset = "decodeunset" optDefault = "default=" optDelimiter = "delimiter=" optNoInit = "noinit" optOverwrite = "overwrite" optPrefix = "prefix=" optRequired = "required" optSeparator = "separator=")// internalError is a custom error type for errors returned by envconfig.type internalError string// Error implements error.func ( internalError) () string {returnstring()}const (ErrInvalidEnvvarName = internalError("invalid environment variable name")ErrInvalidMapItem = internalError("invalid map item")ErrLookuperNil = internalError("lookuper cannot be nil")ErrMissingKey = internalError("missing key")ErrMissingRequired = internalError("missing required value")ErrNoInitNotPtr = internalError("field must be a pointer to have noinit")ErrNotPtr = internalError("input must be a pointer")ErrNotStruct = internalError("input must be a struct")ErrPrefixNotStruct = internalError("prefix is only valid on struct types")ErrPrivateField = internalError("cannot parse private fields")ErrRequiredAndDefault = internalError("field cannot be required and have a default value")ErrUnknownOption = internalError("unknown option"))// Lookuper is an interface that provides a lookup for a string-based key.typeLookuperinterface {// Lookup searches for the given key and returns the corresponding string // value. If a value is found, it returns the value and true. If a value is // not found, it returns the empty string and false.Lookup(key string) (string, bool)}// osLookuper looks up environment configuration from the local environment.type osLookuper struct{}// Verify implements interface.var _ Lookuper = (*osLookuper)(nil)func ( *osLookuper) ( string) (string, bool) {returnos.LookupEnv()}// OsLookuper returns a lookuper that uses the environment ([os.LookupEnv]) to// resolve values.func () Lookuper {returnnew(osLookuper)}type mapLookuper map[string]stringvar _ Lookuper = (*mapLookuper)(nil)func ( mapLookuper) ( string) (string, bool) { , := []return , }// MapLookuper looks up environment configuration from a provided map. This is// useful for testing, especially in parallel, since it does not require you to// mutate the parent environment (which is stateful).func ( map[string]string) Lookuper {returnmapLookuper()}type multiLookuper struct { ls []Lookuper}var _ Lookuper = (*multiLookuper)(nil)func ( *multiLookuper) ( string) (string, bool) {for , := range .ls {if , := .Lookup(); {return , true } }return"", false}// PrefixLookuper looks up environment configuration using the specified prefix.// This is useful if you want all your variables to start with a particular// prefix like "MY_APP_".func ( string, Lookuper) Lookuper {if , := .(*prefixLookuper); {return &prefixLookuper{prefix: .prefix + , l: .l} }return &prefixLookuper{prefix: , l: }}type prefixLookuper struct { l Lookuper prefix string}func ( *prefixLookuper) ( string) (string, bool) {return .l.Lookup(.Key())}func ( *prefixLookuper) ( string) string {return .prefix + }func ( *prefixLookuper) () Lookuper { := .lfor , := .(unwrappableLookuper); ; { = .Unwrap() }return}// unwrappableLookuper is a lookuper that can return the underlying lookuper.type unwrappableLookuper interface { Unwrap() Lookuper}// MultiLookuper wraps a collection of lookupers. It does not combine them, and// lookups appear in the order in which they are provided to the initializer.func ( ...Lookuper) Lookuper {return &multiLookuper{ls: }}// keyedLookuper is an extension to the [Lookuper] interface that returns the// underlying key (used by the [PrefixLookuper] or custom implementations).type keyedLookuper interface { Key(key string) string}// Decoder is an interface that custom types/fields can implement to control how// decoding takes place. For example://// type MyType string//// func (mt MyType) EnvDecode(val string) error {// return "CUSTOM-"+val// }typeDecoderinterface {EnvDecode(val string) error}// options are internal options for decoding.type options struct { Default string Delimiter string Prefix string Separator string NoInit bool Overwrite bool DecodeUnset bool Required bool}// Config represent inputs to the envconfig decoding.typeConfigstruct {// Target is the destination structure to decode. This value is required, and // it must be a pointer to a struct. Target any// Lookuper is the lookuper implementation to use. If not provided, it // defaults to the OS Lookuper. Lookuper Lookuper// DefaultDelimiter is the default value to use for the delimiter in maps and // slices. This can be overridden on a per-field basis, which takes // precedence. The default value is ",". DefaultDelimiter string// DefaultSeparator is the default value to use for the separator in maps. // This can be overridden on a per-field basis, which takes precedence. The // default value is ":". DefaultSeparator string// DefaultNoInit is the default value for skipping initialization of // unprovided fields. The default value is false (deeply initialize all // fields and nested structs). DefaultNoInit bool// DefaultOverwrite is the default value for overwriting an existing value set // on the struct before processing. The default value is false. DefaultOverwrite bool// DefaultDecodeUnset is the default value for running decoders even when no // value was given for the environment variable. DefaultDecodeUnset bool// DefaultRequired is the default value for marking a field as required. The // default value is false. DefaultRequired bool// Mutators is an optional list of mutators to apply to lookups. Mutators []Mutator}// Process decodes the struct using values from environment variables. See// [ProcessWith] for a more customizable version.//// As a special case, if the input for the target is a [*Config], then this// function will call [ProcessWith] using the provided config, with any mutation// appended.func ( context.Context, any, ...Mutator) error {if , := .(*Config); { .Mutators = append(.Mutators, ...)returnProcessWith(, ) }returnProcessWith(, &Config{Target: ,Mutators: , })}// MustProcess is a helper that calls [Process] and panics if an error is// encountered. Unlike [Process], the input value is returned, making it ideal// for anonymous initializations://// var env = envconfig.MustProcess(context.Background(), &struct{// Field string `env:"FIELD,required"`// })//// This is not recommend for production services, but it can be useful for quick// CLIs and scripts that want to take advantage of envconfig's environment// parsing at the expense of testability and graceful error handling.func [ any]( context.Context, , ...Mutator) {if := Process(, , ...); != nil {panic() }return}// ProcessWith executes the decoding process using the provided [Config].func ( context.Context, *Config) error {if == nil { = new(Config) }if .Lookuper == nil { .Lookuper = OsLookuper() }// Deep copy the slice and remove any nil mutators.var []Mutatorfor , := range .Mutators {if != nil { = append(, ) } } .Mutators = returnprocessWith(, )}// processWith is a helper that retains configuration from the parent structs.func processWith( context.Context, *Config) error { := .Target := .Lookuperif == nil {returnErrLookuperNil } := reflect.ValueOf()if .Kind() != reflect.Ptr {returnErrNotPtr } := .Elem()if .Kind() != reflect.Struct {returnErrNotStruct } := .Type() := .DefaultDelimiterif == "" { = "," } := .DefaultNoInit := .DefaultSeparatorif == "" { = ":" } := .DefaultOverwrite := .DefaultDecodeUnset := .DefaultRequired := .Mutatorsfor := 0; < .NumField(); ++ { := .Field() := .Field() := .Tag.Get(envTag)if !.CanSet() {if != "" {// There's an "env" tag on a private field, we can't alter it, and it's // likely a mistake. Return an error so the user can handle.returnfmt.Errorf("%s: %w", .Name, ErrPrivateField) }// Otherwise continue to the next field.continue }// Parse the key and options. , , := keyAndOpts()if != nil {returnfmt.Errorf("%s: %w", .Name, ) }// NoInit is only permitted on pointers.if .NoInit && .Kind() != reflect.Ptr && .Kind() != reflect.Slice && .Kind() != reflect.Map && .Kind() != reflect.UnsafePointer {returnfmt.Errorf("%s: %w", .Name, ErrNoInitNotPtr) }// Compute defaults from local tags. := if := .Delimiter; != "" { = } := if := .Separator; != "" { = } := || .NoInit := || .Overwrite := || .DecodeUnset := || .Required := false := func( reflect.Value) { := .Field()if { := reflect.New(.Type().Elem()).Interface()// If a struct (after traversal) equals to the empty value, it means // nothing was changed in any sub-fields. With the noinit opt, we skip // setting the empty value to the original struct pointer (keep it nil).if !reflect.DeepEqual(.Interface(), ) || ! { .Set() } } }// Initialize pointer structs. := falsefor .Kind() == reflect.Ptr {if .IsNil() {if .Type().Elem().Kind() != reflect.Struct {// This is a nil pointer to something that isn't a struct, like // *string. Move along.break } = true// Use an empty struct of the type so we can traverse. = reflect.New(.Type().Elem()).Elem() } else { = true = .Elem() } }// Special case handle structs. This has to come after the value resolution in // case the struct has a custom decoder.if .Kind() == reflect.Struct {for .CanAddr() { = .Addr() }// Lookup the value, ignoring an error if the key isn't defined. This is // required for nested structs that don't declare their own `env` keys, // but have internal fields with an `env` defined. , , , := lookup(, , .Default, )if != nil && !errors.Is(, ErrMissingKey) {returnfmt.Errorf("%s: %w", .Name, ) }if || || {if , := processAsDecoder(, ); {if != nil {return } ()continue } } := if .Prefix != "" { = PrefixLookuper(.Prefix, ) }if := (, &Config{Target: .Interface(),Lookuper: ,DefaultDelimiter: ,DefaultSeparator: ,DefaultNoInit: ,DefaultOverwrite: ,DefaultRequired: ,Mutators: , }); != nil {returnfmt.Errorf("%s: %w", .Name, ) } ()continue }// It's invalid to have a prefix on a non-struct field.if .Prefix != "" {returnErrPrefixNotStruct }// Stop processing if there's no env tag (this comes after nested parsing), // in case there's an env tag in an embedded struct.if == "" {continue }// The field already has a non-zero value and overwrite is false, do not // overwrite.if ( || !.IsZero()) && ! {continue } , , , := lookup(, , .Default, )if != nil {returnfmt.Errorf("%s: %w", .Name, ) }// If the field already has a non-zero value and there was no value directly // specified, do not overwrite the existing field. We only want to overwrite // when the envvar was provided directly.if ( || !.IsZero()) && ! {continue }// Apply any mutators. Mutators are applied after the lookup, but before any // type conversions. They always resolve to a string (or error), so we don't // call mutators when the environment variable was not set.if || { := := if , := .(keyedLookuper); { = .Key() } := := falsefor , := range { , , = .EnvMutate(, , , , )if != nil {returnfmt.Errorf("%s: %w", .Name, ) }if {break } } }// Set value.if := processField(, , , , ); != nil {returnfmt.Errorf("%s(%q): %w", .Name, , ) } }returnnil}// SplitString splits the given string on the provided rune, unless the rune is// escaped by the escape character.func splitString(, , string) []string { := strings.Split(, )for := len() - 2; >= 0; -- {ifstrings.HasSuffix([], ) { [] = [][:len([])-len()] + + [+1] = append([:+1], [+2:]...) } }return}// keyAndOpts parses the given tag value (e.g. env:"foo,required") and// returns the key name and options as a list.func keyAndOpts( string) (string, *options, error) { := splitString(, ",", "\\") , := strings.TrimSpace([0]), [1:]if != "" && !validateEnvName() {return"", nil, fmt.Errorf("%q: %w ", , ErrInvalidEnvvarName) }varoptions:for , := range { = strings.TrimLeftFunc(, unicode.IsSpace) := strings.ToLower()switch {case == optDecodeUnset: .DecodeUnset = truecase == optOverwrite: .Overwrite = truecase == optRequired: .Required = truecase == optNoInit: .NoInit = truecasestrings.HasPrefix(, optPrefix): .Prefix = strings.TrimPrefix(, optPrefix)casestrings.HasPrefix(, optDelimiter): .Delimiter = strings.TrimPrefix(, optDelimiter)casestrings.HasPrefix(, optSeparator): .Separator = strings.TrimPrefix(, optSeparator)casestrings.HasPrefix(, optDefault):// If a default value was given, assume everything after is the provided // value, including comma-seprated items. = strings.TrimLeft(strings.Join([:], ","), " ") .Default = strings.TrimPrefix(, optDefault)breakdefault:return"", nil, fmt.Errorf("%q: %w", , ErrUnknownOption) } }return , &, nil}// lookup looks up the given key using the provided Lookuper and options. The// first boolean parameter indicates whether the value was found in the// lookuper. The second boolean parameter indicates whether the default value// was used.func lookup( string, bool, string, Lookuper) (string, bool, bool, error) {if == "" {// The struct has something like `env:",required"`, which is likely a // mistake. We could try to infer the envvar from the field name, but that // feels too magical.return"", false, false, ErrMissingKey }if && != "" {// Having a default value on a required value doesn't make sense.return"", false, false, ErrRequiredAndDefault }// Lookup value. , := .Lookup()if ! {if {if , := .(keyedLookuper); { = .Key() }return"", false, false, fmt.Errorf("%w: %s", ErrMissingRequired, ) }if != "" {// Expand the default value. This allows for a default value that maps to // a different environment variable. = os.Expand(, func( string) string { := if , := .(unwrappableLookuper); { = .Unwrap() } , := .Lookup()if {return }return"" })return , false, true, nil } }return , , false, nil}// processAsDecoder processes the given value as a decoder or custom// unmarshaller.func processAsDecoder( string, reflect.Value) (bool, error) {// Keep a running error. It's possible that a property might implement // multiple decoders, and we don't know *which* decoder will succeed. If we // get through all of them, we'll return the most recent error.varboolvarerror// Resolve any pointers.for .CanAddr() { = .Addr() }if .CanInterface() { := .Interface()// If a developer chooses to implement the Decoder interface on a type, // never attempt to use other decoders in case of failure. EnvDecode's // decoding logic is "the right one", and the error returned (if any) // is the most specific we can get.if , := .(Decoder); { = true = .EnvDecode()return , }if , := .(encoding.TextUnmarshaler); { = trueif = .UnmarshalText([]byte()); == nil {return , nil } }if , := .(json.Unmarshaler); { = trueif = .UnmarshalJSON([]byte()); == nil {return , nil } }if , := .(encoding.BinaryUnmarshaler); { = trueif = .UnmarshalBinary([]byte()); == nil {return , nil } }if , := .(gob.GobDecoder); { = trueif = .GobDecode([]byte()); == nil {return , nil } } }return , }func processField( string, reflect.Value, , string, bool) error {// If the input value is empty and initialization is skipped, do nothing.if == "" && {returnnil }// Handle pointers and uninitialized pointers.for .Type().Kind() == reflect.Ptr {if .IsNil() { .Set(reflect.New(.Type().Elem())) } = .Elem() } := .Type() := .Kind()// Handle existing decoders.if , := processAsDecoder(, ); {return }// We don't check if the value is empty earlier, because the user might want // to define a custom decoder and treat the empty variable as a special case. // However, if we got this far, none of the remaining parsers will succeed, so // bail out now.if == "" {returnnil }switch {casereflect.Bool: , := strconv.ParseBool()if != nil {return } .SetBool()casereflect.Float32, reflect.Float64: , := strconv.ParseFloat(, .Bits())if != nil {return } .SetFloat()casereflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: , := strconv.ParseInt(, 0, .Bits())if != nil {return } .SetInt()casereflect.Int64:// Special case time.Duration values.if .PkgPath() == "time" && .Name() == "Duration" { , := time.ParseDuration()if != nil {return } .SetInt(int64()) } else { , := strconv.ParseInt(, 0, .Bits())if != nil {return } .SetInt() }casereflect.String: .SetString()casereflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: , := strconv.ParseUint(, 0, .Bits())if != nil {return } .SetUint()casereflect.Interface:returnfmt.Errorf("cannot decode into interfaces")// Mapscasereflect.Map: := strings.Split(, ) := reflect.MakeMapWithSize(, len())for , := range { := strings.SplitN(, , 2)iflen() < 2 {returnfmt.Errorf("%s: %w", , ErrInvalidMapItem) } , := strings.TrimSpace([0]), strings.TrimSpace([1]) := reflect.New(.Key()).Elem()if := (, , , , ); != nil {returnfmt.Errorf("%s: %w", , ) } := reflect.New(.Elem()).Elem()if := (, , , , ); != nil {returnfmt.Errorf("%s: %w", , ) } .SetMapIndex(, ) } .Set()// Slicescasereflect.Slice:// Special case: []byteif .Elem().Kind() == reflect.Uint8 { .Set(reflect.ValueOf([]byte())) } else { := strings.Split(, ) := reflect.MakeSlice(, len(), len())for , := range { = strings.TrimSpace()if := (, .Index(), , , ); != nil {returnfmt.Errorf("%s: %w", , ) } } .Set() } }returnnil}// validateEnvName validates the given string conforms to being a valid// environment variable.//// Per IEEE Std 1003.1-2001 environment variables consist solely of uppercase// letters, digits, and _, and do not begin with a digit.func validateEnvName( string) bool {if == "" {returnfalse }for , := range {if ( == 0 && !isLetter()) || (!isLetter() && !isNumber() && != '_') {returnfalse } }returntrue}// isLetter returns true if the given rune is a letter between a-z,A-Z. This is// different than unicode.IsLetter which includes all L character cases.func isLetter( rune) bool {return ( >= 'a' && <= 'z') || ( >= 'A' && <= 'Z')}// isNumber returns true if the given run is a number between 0-9. This is// different than unicode.IsNumber in that it only allows 0-9.func isNumber( rune) bool {return >= '0' && <= '9'}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.