I'm trying to do something like this:
Define structs with tags named env
:
type Env struct {
Port string `env:"PORT"`
}
Call some function which will get the environment variable names using os.Getenv
and put set it in the struct.
Right now, I have this:
package main
import (
"fmt"
"os"
"reflect"
)
func ParseEnv(t interface{}, v interface{}) {
it := reflect.TypeOf(t)
for i := 0; i < it.NumField(); i++ {
field := it.Field(i)
value := os.Getenv(field.Tag.Get("env"))
if value == "" {
continue
}
reflect.ValueOf(v).Elem().FieldByName(field.Name).SetString(value)
}
}
type Env struct {
Port string `env:"PORT"`
DatabaseURL string `env:"DATABASE_URL"`
}
func main() {
os.Setenv("PORT", "8080")
os.Setenv("DATABASE_URL", "postgres://user:pass@host:5432/my-db")
env := Env{}
ParseEnv(env, &env)
fmt.Println(env)
}
http://play.golang.org/p/b8uPPVo4aV
But, as you can see, I have to pass both the reference and the pointer to my function.
While this works, it is very ugly (at least I think it is).
If I try to pass the pointer only, I can't get the type right (because it will be an *interface{}
) and, if I pass only the reference, I can't set the values using reflect
(even if I could, it would not work).
Is there a sane way of doing this?
Below is a "saner" way of doing what you want. You will notice that, instead of passing in two copies of the struct, we only need a pointer to the struct.
func ParseEnv(val interface{}) {
ptrRef := reflect.ValueOf(val)
if ptrRef.Kind() != reflect.Ptr {
panic("pointer to struct expected")
}
ref := ptrRef.Elem()
if ref.Kind() != reflect.Struct {
panic("pointer to struct expected")
}
refType := ref.Type()
for i := 0; i < refType.NumField(); i++ {
field := refType.Field(i)
value := os.Getenv(field.Tag.Get("env"))
if value == "" {
continue
}
ref.Field(i).SetString(value)
}
}
The above function should be invoked in the following way:
ParseEnv(&env)
Example: https://play.golang.org/p/_BwWz2oUql