Proposal Details
Today, programs can query environment variables using methods in os: os.Getenv, os.Environ, os.LookupEnv, etc.
foo := os.Getenv("FOO")
bar, found := os.LookupEnv("BAR")
all := os.Environ()
This works and is very simple, but can get more complicated if you need to further validate or especially convert the string values to other types.
e := os.Getenv("NUM_FOOS")
if e == "" {
// NUM_FOOS must be set!
}
i, err := strconv.Atoi(e)
if err != nil {
// NUM_FOOS must be parseable as an int!
}
If a program relies on many environment variables to configure its behavior, a common smells can creep in: authors call os.Getenv from deep within their code, which can make it hard to test (T.Setenv helps)
The alternative, slightly beter, is to populate a struct at the top of their program from env vars, and pass around this struct after validation/conversion is done.
https://github.com/kelseyhightower/envconfig is a very popular, very simple module to make this second approach simpler, by populating a struct from env vars, with type conversion and basic validation built in and configurable using struct tags.
Before:
e := os.Getenv("NUM_FOOS")
if e == "" {
// NUM_FOOS must be set!
}
i, err := strconv.Atoi(e)
if err != nil {
// NUM_FOOS must be parseable as an int!
}
... for each env var to process
After:
var env struct {
Debug bool
Port int `required:"true"`
User string `default:"foobar"`
Users []string
Rate float32
Timeout time.Duration
ColorCodes map[string]int
}
if err := envconfig.Process("", &env); err != nil {
// Something went wrong!
}
The second arg lets callers pass a prefix to env vars, so using envconfig.Process("MY", &env) would populat TimeoutfromMY_TIMEOUT`.
envconfig currently has 12k+ dependents on GitHub: https://github.com/kelseyhightower/envconfig/network/dependents
It has no dependencies outside of stdlib: https://github.com/kelseyhightower/envconfig/blob/10e87fe9eaec671f89425dc366f004a9336bcc8f/go.mod
I propose that some functionality like this be included in the stdlib directly.
There may be functionality included in envconfig that Go authors don't consider worth including in stdlib, or would implement or expose differently, and IMO that's totally fine.
I'm mainly opening this to get feedback and gauge interest in such a thing being included by default.
Personally I'd propose dropping the MY_ prefixing feature, and rename the package to os/env (or just env? Or include it as os.ProcessEnv?). If the required and default struct tags make it, they should be namespaced like env:"required" etc.
If multiple values failed validation, all the errors should be returned with errors.Join.
There's also support for custom decoders -- perhaps those should leverage TextUnmarshaler?
Proposal Details
Today, programs can query environment variables using methods in
os:os.Getenv,os.Environ,os.LookupEnv, etc.This works and is very simple, but can get more complicated if you need to further validate or especially convert the string values to other types.
If a program relies on many environment variables to configure its behavior, a common smells can creep in: authors call
os.Getenvfrom deep within their code, which can make it hard to test (T.Setenvhelps)The alternative, slightly beter, is to populate a struct at the top of their program from env vars, and pass around this struct after validation/conversion is done.
https://github.com/kelseyhightower/envconfig is a very popular, very simple module to make this second approach simpler, by populating a struct from env vars, with type conversion and basic validation built in and configurable using struct tags.
Before:
After:
The second arg lets callers pass a prefix to env vars, so using
envconfig.Process("MY", &env) would populatTimeoutfromMY_TIMEOUT`.envconfigcurrently has 12k+ dependents on GitHub: https://github.com/kelseyhightower/envconfig/network/dependentsIt has no dependencies outside of stdlib: https://github.com/kelseyhightower/envconfig/blob/10e87fe9eaec671f89425dc366f004a9336bcc8f/go.mod
I propose that some functionality like this be included in the stdlib directly.
There may be functionality included in
envconfigthat Go authors don't consider worth including in stdlib, or would implement or expose differently, and IMO that's totally fine.I'm mainly opening this to get feedback and gauge interest in such a thing being included by default.
Personally I'd propose dropping the
MY_prefixing feature, and rename the package toos/env(or justenv? Or include it asos.ProcessEnv?). If therequiredanddefaultstruct tags make it, they should be namespaced likeenv:"required"etc.If multiple values failed validation, all the errors should be returned with
errors.Join.There's also support for custom decoders -- perhaps those should leverage
TextUnmarshaler?