From c08b0f906b39a1fbc978eeeb262840f9acd735ff Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Wed, 1 Mar 2023 10:54:41 -0300 Subject: [PATCH] feat: use field name by default (#253) Signed-off-by: Carlos A Becker --- env.go | 29 ++++++++++++++++++++++++++--- env_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/env.go b/env.go index 6bcdd90..ce84c62 100644 --- a/env.go +++ b/env.go @@ -9,6 +9,7 @@ import ( "strconv" "strings" "time" + "unicode" ) // nolint: gochecknoglobals @@ -102,15 +103,20 @@ type Options struct { // TagName specifies another tagname to use rather than the default env. TagName string - // RequiredIfNoDef automatically sets all env as required if they do not declare 'envDefault' + // RequiredIfNoDef automatically sets all env as required if they do not + // declare 'envDefault'. RequiredIfNoDef bool - // OnSet allows to run a function when a value is set + // OnSet allows to run a function when a value is set. OnSet OnSetFn - // Prefix define a prefix for each key + // Prefix define a prefix for each key. Prefix string + // UseFieldNameByDefault defines whether or not env should use the field + // name by default if the `env` key is missing. + UseFieldNameByDefault bool + // Sets to true if we have already configured once. configured bool } @@ -145,6 +151,7 @@ func configure(opts []Options) []Options { if item.Prefix != "" { opt.Prefix = item.Prefix } + opt.UseFieldNameByDefault = item.UseFieldNameByDefault opt.RequiredIfNoDef = item.RequiredIfNoDef } @@ -243,6 +250,19 @@ func doParseField(refField reflect.Value, refTypeField reflect.StructField, func return nil } +const underscore rune = '_' + +func toEnvName(input string) string { + var output []rune + for i, c := range input { + if i > 0 && output[i-1] != underscore && c != underscore && unicode.ToUpper(c) == c { + output = append(output, underscore) + } + output = append(output, unicode.ToUpper(c)) + } + return string(output) +} + func get(field reflect.StructField, opts []Options) (val string, err error) { var exists bool var isDefault bool @@ -253,6 +273,9 @@ func get(field reflect.StructField, opts []Options) (val string, err error) { required := opts[0].RequiredIfNoDef prefix := opts[0].Prefix ownKey, tags := parseKeyForOption(field.Tag.Get(getTagName(opts))) + if ownKey == "" && opts[0].UseFieldNameByDefault { + ownKey = toEnvName(field.Name) + } key := prefix + ownKey for _, tag := range tags { switch tag { diff --git a/env_test.go b/env_test.go index 9b601c7..c23f72d 100644 --- a/env_test.go +++ b/env_test.go @@ -1691,6 +1691,37 @@ func TestComplePrefix(t *testing.T) { isEqual(t, "blahhh", cfg.Blah) } +func TestNoEnvKey(t *testing.T) { + type Config struct { + Foo string + FooBar string + bar string + } + var cfg Config + isNoErr(t, Parse(&cfg, Options{ + UseFieldNameByDefault: true, + Environment: map[string]string{ + "FOO": "fooval", + "FOO_BAR": "foobarval", + }, + })) + isEqual(t, "fooval", cfg.Foo) + isEqual(t, "foobarval", cfg.FooBar) + isEqual(t, "", cfg.bar) +} + +func TestToEnv(t *testing.T) { + for in, out := range map[string]string{ + "Foo": "FOO", + "FooBar": "FOO_BAR", + "fooBar": "FOO_BAR", + "Foo_Bar": "FOO_BAR", + "Foo__Bar": "FOO__BAR", + } { + isEqual(t, out, toEnvName(in)) + } +} + func isTrue(tb testing.TB, b bool) { tb.Helper()