2018-04-25 13:05:29 +02:00
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
// Use of this source code is governed by the MIT License that can be found in
// the LICENSE file.
package envconfig
import (
"encoding"
"errors"
"fmt"
"os"
"reflect"
"regexp"
"strconv"
"strings"
"time"
)
// ErrInvalidSpecification indicates that a specification is of the wrong type.
var ErrInvalidSpecification = errors . New ( "specification must be a struct pointer" )
2018-07-27 09:03:18 +02:00
var gatherRegexp = regexp . MustCompile ( "([^A-Z]+|[A-Z][^A-Z]+|[A-Z]+)" )
2018-04-25 13:05:29 +02:00
// A ParseError occurs when an environment variable cannot be converted to
// the type required by a struct field during assignment.
type ParseError struct {
KeyName string
FieldName string
TypeName string
Value string
Err error
}
// Decoder has the same semantics as Setter, but takes higher precedence.
// It is provided for historical compatibility.
type Decoder interface {
Decode ( value string ) error
}
// Setter is implemented by types can self-deserialize values.
// Any type that implements flag.Value also implements Setter.
type Setter interface {
Set ( value string ) error
}
func ( e * ParseError ) Error ( ) string {
return fmt . Sprintf ( "envconfig.Process: assigning %[1]s to %[2]s: converting '%[3]s' to type %[4]s. details: %[5]s" , e . KeyName , e . FieldName , e . Value , e . TypeName , e . Err )
}
// varInfo maintains information about the configuration variable
type varInfo struct {
Name string
Alt string
Key string
Field reflect . Value
Tags reflect . StructTag
}
// GatherInfo gathers information about the specified struct
func gatherInfo ( prefix string , spec interface { } ) ( [ ] varInfo , error ) {
s := reflect . ValueOf ( spec )
if s . Kind ( ) != reflect . Ptr {
return nil , ErrInvalidSpecification
}
s = s . Elem ( )
if s . Kind ( ) != reflect . Struct {
return nil , ErrInvalidSpecification
}
typeOfSpec := s . Type ( )
// over allocate an info array, we will extend if needed later
infos := make ( [ ] varInfo , 0 , s . NumField ( ) )
for i := 0 ; i < s . NumField ( ) ; i ++ {
f := s . Field ( i )
ftype := typeOfSpec . Field ( i )
if ! f . CanSet ( ) || isTrue ( ftype . Tag . Get ( "ignored" ) ) {
continue
}
for f . Kind ( ) == reflect . Ptr {
if f . IsNil ( ) {
if f . Type ( ) . Elem ( ) . Kind ( ) != reflect . Struct {
// nil pointer to a non-struct: leave it alone
break
}
// nil pointer to struct: create a zero instance
f . Set ( reflect . New ( f . Type ( ) . Elem ( ) ) )
}
f = f . Elem ( )
}
// Capture information about the config variable
info := varInfo {
Name : ftype . Name ,
Field : f ,
Tags : ftype . Tag ,
Alt : strings . ToUpper ( ftype . Tag . Get ( "envconfig" ) ) ,
}
// Default to the field name as the env var name (will be upcased)
info . Key = info . Name
// Best effort to un-pick camel casing as separate words
if isTrue ( ftype . Tag . Get ( "split_words" ) ) {
2018-07-27 09:03:18 +02:00
words := gatherRegexp . FindAllStringSubmatch ( ftype . Name , - 1 )
2018-04-25 13:05:29 +02:00
if len ( words ) > 0 {
var name [ ] string
for _ , words := range words {
name = append ( name , words [ 0 ] )
}
info . Key = strings . Join ( name , "_" )
}
}
if info . Alt != "" {
info . Key = info . Alt
}
if prefix != "" {
info . Key = fmt . Sprintf ( "%s_%s" , prefix , info . Key )
}
info . Key = strings . ToUpper ( info . Key )
infos = append ( infos , info )
if f . Kind ( ) == reflect . Struct {
// honor Decode if present
if decoderFrom ( f ) == nil && setterFrom ( f ) == nil && textUnmarshaler ( f ) == nil && binaryUnmarshaler ( f ) == nil {
innerPrefix := prefix
if ! ftype . Anonymous {
innerPrefix = info . Key
}
embeddedPtr := f . Addr ( ) . Interface ( )
embeddedInfos , err := gatherInfo ( innerPrefix , embeddedPtr )
if err != nil {
return nil , err
}
infos = append ( infos [ : len ( infos ) - 1 ] , embeddedInfos ... )
continue
}
}
}
return infos , nil
}
// CheckDisallowed checks that no environment variables with the prefix are set
// that we don't know how or want to parse. This is likely only meaningful with
// a non-empty prefix.
func CheckDisallowed ( prefix string , spec interface { } ) error {
infos , err := gatherInfo ( prefix , spec )
if err != nil {
return err
}
vars := make ( map [ string ] struct { } )
for _ , info := range infos {
vars [ info . Key ] = struct { } { }
}
if prefix != "" {
prefix = strings . ToUpper ( prefix ) + "_"
}
for _ , env := range os . Environ ( ) {
if ! strings . HasPrefix ( env , prefix ) {
continue
}
v := strings . SplitN ( env , "=" , 2 ) [ 0 ]
if _ , found := vars [ v ] ; ! found {
return fmt . Errorf ( "unknown environment variable %s" , v )
}
}
return nil
}
// Process populates the specified struct based on environment variables
func Process ( prefix string , spec interface { } ) error {
infos , err := gatherInfo ( prefix , spec )
for _ , info := range infos {
// `os.Getenv` cannot differentiate between an explicitly set empty value
// and an unset value. `os.LookupEnv` is preferred to `syscall.Getenv`,
// but it is only available in go1.5 or newer. We're using Go build tags
// here to use os.LookupEnv for >=go1.5
value , ok := lookupEnv ( info . Key )
if ! ok && info . Alt != "" {
value , ok = lookupEnv ( info . Alt )
}
def := info . Tags . Get ( "default" )
if def != "" && ! ok {
value = def
}
req := info . Tags . Get ( "required" )
if ! ok && def == "" {
if isTrue ( req ) {
return fmt . Errorf ( "required key %s missing value" , info . Key )
}
continue
}
2018-07-27 09:03:18 +02:00
err = processField ( value , info . Field )
2018-04-25 13:05:29 +02:00
if err != nil {
return & ParseError {
KeyName : info . Key ,
FieldName : info . Name ,
TypeName : info . Field . Type ( ) . String ( ) ,
Value : value ,
Err : err ,
}
}
}
return err
}
// MustProcess is the same as Process but panics if an error occurs
func MustProcess ( prefix string , spec interface { } ) {
if err := Process ( prefix , spec ) ; err != nil {
panic ( err )
}
}
func processField ( value string , field reflect . Value ) error {
typ := field . Type ( )
decoder := decoderFrom ( field )
if decoder != nil {
return decoder . Decode ( value )
}
// look for Set method if Decode not defined
setter := setterFrom ( field )
if setter != nil {
return setter . Set ( value )
}
if t := textUnmarshaler ( field ) ; t != nil {
return t . UnmarshalText ( [ ] byte ( value ) )
}
if b := binaryUnmarshaler ( field ) ; b != nil {
return b . UnmarshalBinary ( [ ] byte ( value ) )
}
if typ . Kind ( ) == reflect . Ptr {
typ = typ . Elem ( )
if field . IsNil ( ) {
field . Set ( reflect . New ( typ ) )
}
field = field . Elem ( )
}
switch typ . Kind ( ) {
case reflect . String :
field . SetString ( value )
case reflect . Int , reflect . Int8 , reflect . Int16 , reflect . Int32 , reflect . Int64 :
var (
val int64
err error
)
if field . Kind ( ) == reflect . Int64 && typ . PkgPath ( ) == "time" && typ . Name ( ) == "Duration" {
var d time . Duration
d , err = time . ParseDuration ( value )
val = int64 ( d )
} else {
val , err = strconv . ParseInt ( value , 0 , typ . Bits ( ) )
}
if err != nil {
return err
}
field . SetInt ( val )
case reflect . Uint , reflect . Uint8 , reflect . Uint16 , reflect . Uint32 , reflect . Uint64 :
val , err := strconv . ParseUint ( value , 0 , typ . Bits ( ) )
if err != nil {
return err
}
field . SetUint ( val )
case reflect . Bool :
val , err := strconv . ParseBool ( value )
if err != nil {
return err
}
field . SetBool ( val )
case reflect . Float32 , reflect . Float64 :
val , err := strconv . ParseFloat ( value , typ . Bits ( ) )
if err != nil {
return err
}
field . SetFloat ( val )
case reflect . Slice :
vals := strings . Split ( value , "," )
sl := reflect . MakeSlice ( typ , len ( vals ) , len ( vals ) )
for i , val := range vals {
err := processField ( val , sl . Index ( i ) )
if err != nil {
return err
}
}
field . Set ( sl )
case reflect . Map :
mp := reflect . MakeMap ( typ )
if len ( strings . TrimSpace ( value ) ) != 0 {
pairs := strings . Split ( value , "," )
for _ , pair := range pairs {
kvpair := strings . Split ( pair , ":" )
if len ( kvpair ) != 2 {
return fmt . Errorf ( "invalid map item: %q" , pair )
}
k := reflect . New ( typ . Key ( ) ) . Elem ( )
err := processField ( kvpair [ 0 ] , k )
if err != nil {
return err
}
v := reflect . New ( typ . Elem ( ) ) . Elem ( )
err = processField ( kvpair [ 1 ] , v )
if err != nil {
return err
}
mp . SetMapIndex ( k , v )
}
}
field . Set ( mp )
}
return nil
}
func interfaceFrom ( field reflect . Value , fn func ( interface { } , * bool ) ) {
// it may be impossible for a struct field to fail this check
if ! field . CanInterface ( ) {
return
}
var ok bool
fn ( field . Interface ( ) , & ok )
if ! ok && field . CanAddr ( ) {
fn ( field . Addr ( ) . Interface ( ) , & ok )
}
}
func decoderFrom ( field reflect . Value ) ( d Decoder ) {
interfaceFrom ( field , func ( v interface { } , ok * bool ) { d , * ok = v . ( Decoder ) } )
return d
}
func setterFrom ( field reflect . Value ) ( s Setter ) {
interfaceFrom ( field , func ( v interface { } , ok * bool ) { s , * ok = v . ( Setter ) } )
return s
}
func textUnmarshaler ( field reflect . Value ) ( t encoding . TextUnmarshaler ) {
interfaceFrom ( field , func ( v interface { } , ok * bool ) { t , * ok = v . ( encoding . TextUnmarshaler ) } )
return t
}
func binaryUnmarshaler ( field reflect . Value ) ( b encoding . BinaryUnmarshaler ) {
interfaceFrom ( field , func ( v interface { } , ok * bool ) { b , * ok = v . ( encoding . BinaryUnmarshaler ) } )
return b
}
func isTrue ( s string ) bool {
b , _ := strconv . ParseBool ( s )
return b
}