Created
January 16, 2023 13:50
-
-
Save ulexxander/45760fbe9c4f5e0c1bfad27f5b7ad6a3 to your computer and use it in GitHub Desktop.
Comparing few Go libraries for scanning environment variables into structs
This file contains hidden or 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
| package envcompare_test | |
| import ( | |
| "net" | |
| "testing" | |
| "github.com/caarlos0/env/v6" | |
| envsimpler "github.com/go-simpler/env" | |
| "github.com/kelseyhightower/envconfig" | |
| "github.com/sirupsen/logrus" | |
| "github.com/stretchr/testify/require" | |
| ) | |
| // github.com/kelseyhightower/envconfig | |
| // envconfig.Process("", &parsed) | |
| // github.com/caarlos0/env/v6 | |
| // env.Parse(&parsed) | |
| // github.com/go-simpler/env | |
| // envsimpler.Load(&parsed) | |
| // Needed capabilities: | |
| // 1. Scan into struct, map fields to env variables via tags | |
| // 2. Support standard scalar types, encoding.TextUnmarshaler, maybe slices, arrays and maps | |
| // 3. Should report error if fails to parse variable | |
| // 4. Required / optional variables | |
| func TestKelseyhightowerEnvconfig(t *testing.T) { | |
| t.Log("https://github.com/kelseyhightower/envconfig - 4.3k stars") | |
| t.Run("supports different types", func(t *testing.T) { | |
| t.Setenv("LOG_LEVEL", "trace") | |
| t.Setenv("POSTGRES_HOST", "postgres") | |
| t.Setenv("POSTGRES_PORT", "5432") | |
| t.Setenv("BTC_ENABLED", "true") | |
| t.Setenv("AUTH_CREDENTIALS", "user1:abc123,user2:def456") | |
| t.Setenv("IP_WHITELIST", "192.168.86.102,127.0.0.1") | |
| type config struct { | |
| LogLevel logrus.Level `envconfig:"LOG_LEVEL"` | |
| PostgresHost string `envconfig:"POSTGRES_HOST"` | |
| PostgresPort int `envconfig:"POSTGRES_PORT"` | |
| BTCEnabled bool `envconfig:"BTC_ENABLED"` | |
| AuthCredentials map[string]string `envconfig:"AUTH_CREDENTIALS"` | |
| IPWhitelist []net.IP `envconfig:"IP_WHITELIST"` | |
| } | |
| var parsed config | |
| err := envconfig.Process("", &parsed) | |
| require.NoError(t, err) | |
| require.Equal(t, config{ | |
| LogLevel: logrus.TraceLevel, | |
| PostgresHost: "postgres", | |
| PostgresPort: 5432, | |
| BTCEnabled: true, | |
| AuthCredentials: map[string]string{ | |
| "user1": "abc123", | |
| "user2": "def456", | |
| }, | |
| IPWhitelist: []net.IP{ | |
| net.ParseIP("192.168.86.102"), | |
| net.ParseIP("127.0.0.1"), | |
| }, | |
| }, parsed) | |
| }) | |
| t.Run("reports parsing errors", func(t *testing.T) { | |
| t.Setenv("LOG_LEVEL", "123") | |
| t.Setenv("POSTGRES_PORT", "abc") | |
| t.Setenv("BTC_ENABLED", "???") | |
| type config struct { | |
| LogLevel logrus.Level `envconfig:"LOG_LEVEL"` | |
| PostgresPort int `envconfig:"POSTGRES_PORT"` | |
| BTCEnabled bool `envconfig:"BTC_ENABLED"` | |
| } | |
| var parsed config | |
| err := envconfig.Process("", &parsed) | |
| require.EqualError(t, err, "envconfig.Process: assigning LOG_LEVEL to LogLevel: "+ | |
| "converting '123' to type logrus.Level. details: not a valid logrus Level: \"123\"") | |
| t.Fatal("Only first error is reported") | |
| }) | |
| t.Run("reports missing variables", func(t *testing.T) { | |
| type config struct { | |
| PostgresHost string `envconfig:"POSTGRES_HOST" required:"true"` | |
| PostgresPort int `envconfig:"POSTGRES_PORT" required:"true"` | |
| } | |
| var parsed config | |
| err := envconfig.Process("", &parsed) | |
| require.EqualError(t, err, "required key POSTGRES_HOST missing value") | |
| t.Fatal("Only first missing field is reported") | |
| }) | |
| t.Run("supports default values via tags", func(t *testing.T) { | |
| type config struct { | |
| PostgresHost string `envconfig:"POSTGRES_HOST" required:"true" default:"postgres"` | |
| PostgresPort int `envconfig:"POSTGRES_PORT" required:"true" default:"5432"` | |
| } | |
| var parsed config | |
| err := envconfig.Process("", &parsed) | |
| require.NoError(t, err) | |
| require.Equal(t, config{ | |
| PostgresHost: "postgres", | |
| PostgresPort: 5432, | |
| }, parsed) | |
| }) | |
| } | |
| func TestCaarlos0Env(t *testing.T) { | |
| t.Log("https://github.com/caarlos0/env - 2.9k stars") | |
| // You can make all fields that don't have a default value be required by setting the RequiredIfNoDef: true in the Options. | |
| t.Run("supports different types", func(t *testing.T) { | |
| t.Setenv("LOG_LEVEL", "trace") | |
| t.Setenv("POSTGRES_HOST", "postgres") | |
| t.Setenv("POSTGRES_PORT", "5432") | |
| t.Setenv("BTC_ENABLED", "true") | |
| // t.Setenv("AUTH_CREDENTIALS", "user1:abc123,user2:def456") | |
| t.Setenv("IP_WHITELIST", "192.168.86.102,127.0.0.1") | |
| type config struct { | |
| LogLevel logrus.Level `env:"LOG_LEVEL"` | |
| PostgresHost string `env:"POSTGRES_HOST"` | |
| PostgresPort int `env:"POSTGRES_PORT"` | |
| BTCEnabled bool `env:"BTC_ENABLED"` | |
| // AuthCredentials map[string]string `env:"AUTH_CREDENTIALS"` | |
| IPWhitelist []net.IP `env:"IP_WHITELIST"` | |
| } | |
| var parsed config | |
| err := env.Parse(&parsed) | |
| require.NoError(t, err) | |
| require.Equal(t, config{ | |
| LogLevel: logrus.TraceLevel, | |
| PostgresHost: "postgres", | |
| PostgresPort: 5432, | |
| BTCEnabled: true, | |
| // AuthCredentials: map[string]string{ | |
| // "user1": "abc123", | |
| // "user2": "def456", | |
| // }, | |
| IPWhitelist: []net.IP{ | |
| net.ParseIP("192.168.86.102"), | |
| net.ParseIP("127.0.0.1"), | |
| }, | |
| }, parsed) | |
| t.Log("Maps are not supported out the box") | |
| }) | |
| t.Run("reports parsing errors", func(t *testing.T) { | |
| t.Setenv("LOG_LEVEL", "123") | |
| t.Setenv("POSTGRES_PORT", "abc") | |
| t.Setenv("BTC_ENABLED", "???") | |
| type config struct { | |
| LogLevel logrus.Level `env:"LOG_LEVEL"` | |
| PostgresPort int `env:"POSTGRES_PORT"` | |
| BTCEnabled bool `env:"BTC_ENABLED"` | |
| } | |
| var parsed config | |
| err := env.Parse(&parsed) | |
| require.EqualError(t, err, | |
| `env: parse error on field "LogLevel" of type "logrus.Level": not a valid logrus Level: "123"; `+ | |
| `parse error on field "PostgresPort" of type "int": strconv.ParseInt: parsing "abc": invalid syntax; `+ | |
| `parse error on field "BTCEnabled" of type "bool": strconv.ParseBool: parsing "???": invalid syntax`) | |
| }) | |
| t.Run("reports missing variables", func(t *testing.T) { | |
| type config struct { | |
| PostgresHost string `env:"POSTGRES_HOST,required"` | |
| PostgresPort int `env:"POSTGRES_PORT,required"` | |
| } | |
| var parsed config | |
| err := env.Parse(&parsed) | |
| require.EqualError(t, err, | |
| `env: required environment variable "POSTGRES_HOST" is not set; `+ | |
| `required environment variable "POSTGRES_PORT" is not set`) | |
| }) | |
| t.Run("reports empty variables", func(t *testing.T) { | |
| t.Setenv("POSTGRES_HOST", "") | |
| t.Setenv("POSTGRES_PORT", "") | |
| type config struct { | |
| PostgresHost string `env:"POSTGRES_HOST,notEmpty"` | |
| PostgresPort int `env:"POSTGRES_PORT,notEmpty"` | |
| } | |
| var parsed config | |
| err := env.Parse(&parsed) | |
| require.EqualError(t, err, | |
| `env: environment variable "POSTGRES_HOST" should not be empty; `+ | |
| `environment variable "POSTGRES_PORT" should not be empty`) | |
| }) | |
| t.Run("supports default values via tags", func(t *testing.T) { | |
| type config struct { | |
| PostgresHost string `env:"POSTGRES_HOST,required" envDefault:"postgres"` | |
| PostgresPort int `env:"POSTGRES_PORT,required" envDefault:"5432"` | |
| } | |
| var parsed config | |
| err := env.Parse(&parsed) | |
| require.NoError(t, err) | |
| require.Equal(t, config{ | |
| PostgresHost: "postgres", | |
| PostgresPort: 5432, | |
| }, parsed) | |
| }) | |
| t.Run("required if no def", func(t *testing.T) { | |
| t.Setenv("POSTGRES_HOST", "") | |
| type config struct { | |
| PostgresHost string `env:"POSTGRES_HOST"` | |
| PostgresPort int `env:"POSTGRES_PORT" envDefault:"5432"` | |
| } | |
| var parsed config | |
| err := env.Parse(&parsed, env.Options{ | |
| RequiredIfNoDef: true, | |
| }) | |
| require.EqualError(t, err, `env: required environment variable "POSTGRES_HOST" is not set`) | |
| }) | |
| } | |
| func TestGoSimplerEnv(t *testing.T) { | |
| t.Log("https://github.com/go-simpler/env - 36 stars") | |
| // - No "not empty" option. | |
| // - Use the expand option to automatically expand the value of the environment variable using os.Expand. | |
| // - For cases where most environment variables are required, strict mode is available, | |
| // in which all variables without the default tag are treated as required. | |
| // To enable this mode, use the WithStrictMode option: | |
| t.Run("supports different types", func(t *testing.T) { | |
| t.Setenv("LOG_LEVEL", "trace") | |
| t.Setenv("POSTGRES_HOST", "postgres") | |
| t.Setenv("POSTGRES_PORT", "5432") | |
| t.Setenv("BTC_ENABLED", "true") | |
| // t.Setenv("AUTH_CREDENTIALS", "user1:abc123,user2:def456") | |
| t.Setenv("IP_WHITELIST", "192.168.86.102 127.0.0.1") | |
| type config struct { | |
| LogLevel logrus.Level `env:"LOG_LEVEL"` | |
| PostgresHost string `env:"POSTGRES_HOST"` | |
| PostgresPort int `env:"POSTGRES_PORT"` | |
| BTCEnabled bool `env:"BTC_ENABLED"` | |
| // AuthCredentials map[string]string `env:"AUTH_CREDENTIALS"` | |
| IPWhitelist []net.IP `env:"IP_WHITELIST"` | |
| } | |
| var parsed config | |
| err := envsimpler.Load(&parsed) | |
| require.NoError(t, err) | |
| require.Equal(t, config{ | |
| LogLevel: logrus.TraceLevel, | |
| PostgresHost: "postgres", | |
| PostgresPort: 5432, | |
| BTCEnabled: true, | |
| // AuthCredentials: map[string]string{ | |
| // "user1": "abc123", | |
| // "user2": "def456", | |
| // }, | |
| IPWhitelist: []net.IP{ | |
| net.ParseIP("192.168.86.102"), | |
| net.ParseIP("127.0.0.1"), | |
| }, | |
| }, parsed) | |
| t.Log("Default slice separator is space, not comma") | |
| t.Log("Maps are not supported out the box") | |
| }) | |
| t.Run("reports parsing errors", func(t *testing.T) { | |
| t.Setenv("LOG_LEVEL", "123") | |
| t.Setenv("POSTGRES_PORT", "abc") | |
| t.Setenv("BTC_ENABLED", "???") | |
| type config struct { | |
| LogLevel logrus.Level `env:"LOG_LEVEL"` | |
| PostgresPort int `env:"POSTGRES_PORT"` | |
| BTCEnabled bool `env:"BTC_ENABLED"` | |
| } | |
| var parsed config | |
| err := envsimpler.Load(&parsed) | |
| require.EqualError(t, err, `unmarshaling text: not a valid logrus Level: "123"`) | |
| t.Fatal("Only first error is reported") | |
| }) | |
| t.Run("reports missing variables", func(t *testing.T) { | |
| type config struct { | |
| PostgresHost string `env:"POSTGRES_HOST,required"` | |
| PostgresPort int `env:"POSTGRES_PORT,required"` | |
| } | |
| var parsed config | |
| err := envsimpler.Load(&parsed) | |
| require.EqualError(t, err, `env: [POSTGRES_HOST POSTGRES_PORT] are required but not set`) | |
| }) | |
| t.Run("reports empty variables", func(t *testing.T) { | |
| t.Setenv("POSTGRES_HOST", "") | |
| t.Setenv("POSTGRES_PORT", "") | |
| type config struct { | |
| PostgresHost string `env:"POSTGRES_HOST,required"` | |
| PostgresPort int `env:"POSTGRES_PORT,required"` | |
| } | |
| var parsed config | |
| err := envsimpler.Load(&parsed) | |
| require.EqualError(t, err, `parsing int: strconv.ParseInt: parsing "": invalid syntax`) | |
| t.Fatal("Not possible to ensure variable is not set to empty value") | |
| }) | |
| t.Run("supports default values via tags", func(t *testing.T) { | |
| type config struct { | |
| PostgresHost string `env:"POSTGRES_HOST" default:"postgres"` | |
| PostgresPort int `env:"POSTGRES_PORT" default:"5432"` | |
| } | |
| var parsed config | |
| err := envsimpler.Load(&parsed) | |
| require.NoError(t, err) | |
| require.Equal(t, config{ | |
| PostgresHost: "postgres", | |
| PostgresPort: 5432, | |
| }, parsed) | |
| t.Fatal("Not possible to ensure variable is not set to empty value") | |
| }) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment