255 lines
6.0 KiB
Go
255 lines
6.0 KiB
Go
|
// 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"
|
||
|
"reflect"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
// ErrInvalidSpecification indicates that a specification is of the wrong type.
|
||
|
var ErrInvalidSpecification = errors.New("specification must be a struct pointer")
|
||
|
|
||
|
// 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)
|
||
|
}
|
||
|
|
||
|
// Process populates the specified struct based on environment variables
|
||
|
func Process(prefix string, spec interface{}) error {
|
||
|
s := reflect.ValueOf(spec)
|
||
|
|
||
|
if s.Kind() != reflect.Ptr {
|
||
|
return ErrInvalidSpecification
|
||
|
}
|
||
|
s = s.Elem()
|
||
|
if s.Kind() != reflect.Struct {
|
||
|
return ErrInvalidSpecification
|
||
|
}
|
||
|
typeOfSpec := s.Type()
|
||
|
for i := 0; i < s.NumField(); i++ {
|
||
|
f := s.Field(i)
|
||
|
ftype := typeOfSpec.Field(i)
|
||
|
if !f.CanSet() || ftype.Tag.Get("ignored") == "true" {
|
||
|
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()
|
||
|
}
|
||
|
|
||
|
alt := ftype.Tag.Get("envconfig")
|
||
|
fieldName := ftype.Name
|
||
|
if alt != "" {
|
||
|
fieldName = alt
|
||
|
}
|
||
|
|
||
|
key := fieldName
|
||
|
if prefix != "" {
|
||
|
key = fmt.Sprintf("%s_%s", prefix, key)
|
||
|
}
|
||
|
key = strings.ToUpper(key)
|
||
|
|
||
|
if f.Kind() == reflect.Struct {
|
||
|
// honor Decode if present
|
||
|
if decoderFrom(f) == nil && setterFrom(f) == nil && textUnmarshaler(f) == nil {
|
||
|
innerPrefix := prefix
|
||
|
if !ftype.Anonymous {
|
||
|
innerPrefix = key
|
||
|
}
|
||
|
|
||
|
embeddedPtr := f.Addr().Interface()
|
||
|
if err := Process(innerPrefix, embeddedPtr); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
f.Set(reflect.ValueOf(embeddedPtr).Elem())
|
||
|
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `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(key)
|
||
|
if !ok && alt != "" {
|
||
|
key := strings.ToUpper(fieldName)
|
||
|
value, ok = lookupEnv(key)
|
||
|
}
|
||
|
|
||
|
def := ftype.Tag.Get("default")
|
||
|
if def != "" && !ok {
|
||
|
value = def
|
||
|
}
|
||
|
|
||
|
req := ftype.Tag.Get("required")
|
||
|
if !ok && def == "" {
|
||
|
if req == "true" {
|
||
|
return fmt.Errorf("required key %s missing value", key)
|
||
|
}
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
err := processField(value, f)
|
||
|
if err != nil {
|
||
|
return &ParseError{
|
||
|
KeyName: key,
|
||
|
FieldName: fieldName,
|
||
|
TypeName: f.Type().String(),
|
||
|
Value: value,
|
||
|
Err: err,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// 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 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)
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|