Something went wrong. Try again.
initialize struct from env vars (golang)
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139package envstruct // import "go.vedthiru.com/envstruct"
import ( "fmt" "os" "reflect" "strconv" "strings" "unicode")
func convertCase(prefix string, s string) string { var envVarBuf strings.Builder
if prefix != "" { envVarBuf.WriteString(prefix) }
skip := false for i, c := range s { u := unicode.IsUpper(c) if i == 0 { skip = u } else if !skip && u { envVarBuf.WriteRune('_') skip = true } else if skip && !u { skip = false } envVarBuf.WriteRune(unicode.ToUpper(c)) }
return envVarBuf.String()}
// Initializes s from environment variables. s must be a pointer to a struct// (panics otherwise). prefix may be empty, though it is recommended you set it// so that your environment variables do not clash with others.//// Automatically converts struct field names to environment variable case, using// the following algorithm: non-consecutive uppercase letters in the field name// are preceded by an underscore. Example: the value for field UseJSON is read// from USE_JSON. Nested structs are handled similarly: the field name is added// on as the struct tree is traversed. Example: if UseJSON is inside a struct in// field Log, the value is read from LOG_USE_JSON.//// The fields of s may have the struct tags envVar and envDefault.//// - If a field has the envVar struct tag, the value for that field is read// from the environment variable named envVar.//// - If a field has the envDefault struct tag, and the environment variable// is not defined, the field is initialized with the value of envDefault.// If the environment variable is not defined and envDefault is not set,// returns an error.//// If prefix is not empty, both autogenerated and custom environment variable// names are prefixed unless the field has the tag envNoPrefix:"true". prefix is// converted to uppercase, but is otherwise not modified.//// All fields in s must be string, uint, bool, or implement// [encoding.TextUnmarshaler]. Panics otherwise. Returns an error if an// environment variable's value cannot be parsed into the field's type.//// Will only handle fields exported from the struct.func Parse(prefix string, s any) error { prefix = strings.ToUpper(prefix) v := reflect.ValueOf(s).Elem() st := v.Type() if st.Kind() != reflect.Struct { panic(fmt.Sprintf("expected pointer to struct, got type %s", st.Name())) } for _, f := range reflect.VisibleFields(st) { if !f.IsExported() { continue }
envVar := f.Tag.Get("envVar") envDefault, hasDefault := f.Tag.Lookup("envDefault") p := prefix + "_" if f.Tag.Get("envNoPrefix") == "true" { p = "" } if envVar == "" { envVar = convertCase(p, f.Name) } else { envVar = p + envVar }
vf := v.FieldByName(f.Name) if f.Type.Kind() == reflect.Struct { if f.Anonymous { continue } err := Parse(envVar, vf.Addr().Interface()) if err != nil { return err } continue }
val := os.Getenv(envVar) if val == "" { if !hasDefault { return fmt.Errorf("env var %s must be defined", envVar) } val = envDefault }
ft := f.Type.Kind() switch ft { case reflect.String: vf.SetString(val) case reflect.Bool: b, err := strconv.ParseBool(val) if err != nil { return fmt.Errorf("env var %s should be a bool: %w", envVar, err) } vf.SetBool(b) case reflect.Uint: i, err := strconv.ParseUint(val, 10, 64) if err != nil { return fmt.Errorf("env var %s should be a uint: %w", envVar, err) } vf.SetUint(i) default: unmarshaler := vf.Addr().MethodByName("UnmarshalText") if !unmarshaler.IsValid() { panic(fmt.Sprintf("unsupported type %s (of field %s) does not implement encoding.TextUnmarshaler", f.Type, f.Name)) } e := unmarshaler.Call([]reflect.Value{reflect.ValueOf([]byte(val))})[0] if !e.IsNil() { return fmt.Errorf("env var %s should be convertible to %s: %w", envVar, f.Type.Name(), e.Interface().(error)) } } } return nil}