Created
January 24, 2018 08:35
-
-
Save mmirolim/3cc1470a9bf2466556deab9b0c95d17a to your computer and use it in GitHub Desktop.
Set struct props from env
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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