Skip to content

Instantly share code, notes, and snippets.

@mmirolim
Created January 24, 2018 08:35
Show Gist options
  • Save mmirolim/3cc1470a9bf2466556deab9b0c95d17a to your computer and use it in GitHub Desktop.
Save mmirolim/3cc1470a9bf2466556deab9b0c95d17a to your computer and use it in GitHub Desktop.
Set struct props from env
// prints evn variable names
func printEnvNames(cfg interface{}) error {
envs := make([]string, 0, 10)
collectEnv := func(elem reflect.Value, i int) error {
// get env variable name from struct.field tag
name := elem.Type().Field(i).Tag.Get("env")
// skip if env tag not set
if name != "" {
envs = append(envs, name)
}
return nil
}
// visit and collect all properties recursively
err := Walk(cfg, collectEnv)
if err != nil {
return err
}
sort.Slice(envs, func(i, j int) bool {
return envs[i] < envs[j]
})
fmt.Fprintln(os.Stderr, "Environment variables")
for i := range envs {
fmt.Fprintf(os.Stderr, " %s\n", envs[i])
}
return nil
}
// SetFromEnv fills the entire config declared in interface
// should be passed pointer to struct
// fill is recursive and sets all setable
// embeddable structs
// if env is empty field skipped
func SetFromEnv(cfg interface{}) error {
setVars := func(elem reflect.Value, i int) error {
// get env variable name from struct.field tag
val := os.Getenv(elem.Type().Field(i).Tag.Get("env"))
// skip if env value is empty
if val == "" {
return nil
}
switch elem.Field(i).Interface().(type) {
case bool:
v, err := strconv.ParseBool(val)
if err != nil {
return fmt.Errorf("%v: %v, parse bool err %+v", ErrInvalidBool, val, err)
}
elem.Field(i).SetBool(v)
case int, int32:
v, err := strconv.ParseInt(val, 0, 32)
if err != nil {
return fmt.Errorf("%v: %v parse int err %+v", ErrInvalidInt, val, err)
}
elem.Field(i).SetInt(v)
case int64:
v, err := strconv.ParseInt(val, 0, 64)
if err != nil {
return fmt.Errorf("%v: %v parse int64 err %+v", ErrInvalidInt64, val, err)
}
elem.Field(i).SetInt(v)
case uint, uint32:
v, err := strconv.ParseUint(val, 0, 32)
if err != nil {
return fmt.Errorf("%v: %v parse uint err %+v", ErrInvalidUint, val, err)
}
elem.Field(i).SetUint(v)
case uint64:
v, err := strconv.ParseUint(val, 0, 64)
if err != nil {
return fmt.Errorf("%v: %v parse uint64 err %+v", ErrInvalidUint64, val, err)
}
elem.Field(i).SetUint(v)
case string:
elem.Field(i).SetString(val)
case time.Duration:
v, err := time.ParseDuration(val)
if err != nil {
return fmt.Errorf("%v: %v parse duration err %+v", ErrParseDuration, val, err)
}
elem.Field(i).SetInt(int64(v))
}
return nil
}
return Walk(cfg, setVars)
}
// Walk visits recursively all fields of provided type resolving all struct types and
// appling fn func to each base type field
func Walk(cfg interface{}, fn func(elem reflect.Value, fieldNum int) error) error {
// we can only work with struct and it should be pointer
// so we could set values to it
if reflect.TypeOf(cfg).Kind() != reflect.Ptr {
return ErrStructPtrReq
}
if reflect.Indirect(reflect.ValueOf(cfg)).Kind() != reflect.Struct {
return ErrStructPtrReq
}
elem := reflect.ValueOf(cfg).Elem()
for i := 0; i < elem.NumField(); i++ {
// add boilerplate so we do not panic
f := elem.Field(i)
// check if we can obtain add
// skip all not addressable fields
// TODO do not skip return some error
if !f.CanAddr() {
continue
}
faddr := f.Addr()
// check if we can use it without panic
if !faddr.CanInterface() {
continue
}
// this will panic if field unexported
// so above check required
addri := faddr.Interface()
if f.Kind() == reflect.Struct {
// go recursive with namespace
err := Walk(addri, fn)
if err != nil {
return err
}
// skip if field is struct
// only values for struct properties should be set
continue
}
err := fn(elem, i)
if err != nil {
return err
}
}
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment