From e9f5affe7c122be27aa1ddc3dfd7c641efa65b76 Mon Sep 17 00:00:00 2001 From: Dean Davidson Date: Sun, 30 Mar 2025 11:26:42 -0700 Subject: [PATCH] Trim whitespace by default --- README.md | 4 ++-- dotconfig.go | 16 ++++++++++++---- dotconfig_test.go | 22 ++++++++++++++++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 440e9eb..898642d 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,6 @@ if err != nil { ``` ## Contributing -Contributions are always welcome. Have a new idea or find a bug? Submit a pull request or create an issue! +Contributions are always welcome. Have a new idea or find a bug? Submit a pull request or create an issue! -**IMPORTANT**: This package is being used in production and any future updates should maintain backwards compatibility. \ No newline at end of file +This package is being used in production and any future updates should maintain backwards compatibility. This is why we have options; to allow us to introduce optional new features while maintaining backwards compatibility. \ No newline at end of file diff --git a/dotconfig.go b/dotconfig.go index b536416..d611d71 100644 --- a/dotconfig.go +++ b/dotconfig.go @@ -18,11 +18,13 @@ type DecodeOption int const ( ReturnFileIOErrors DecodeOption = iota // Return file IO errors EnforceStructTags // Make sure all fields in config struct have `env` struct tags + AllowWhitespace // Allow leading/trailing whitespace in string values ) type options struct { ReturnFileIOErrors bool EnforceStructTags bool + AllowWhitespace bool } func optsFromVariadic(opts []DecodeOption) options { @@ -33,6 +35,8 @@ func optsFromVariadic(opts []DecodeOption) options { v.ReturnFileIOErrors = true case EnforceStructTags: v.EnforceStructTags = true + case AllowWhitespace: + v.AllowWhitespace = true } } return v @@ -169,7 +173,7 @@ func fromEnv[T any](opts options) (T, error) { continue } // Parse env tag into environment variable key and options - envKey, opts := parseTag(envTag) + envKey, tagOpts := parseTag(envTag) envValue, keyExists := os.LookupEnv(envKey) // Missing env var if !keyExists { @@ -177,7 +181,7 @@ func fromEnv[T any](opts options) (T, error) { defaultVal := fieldType.Tag.Get("default") if defaultVal != "" { envValue = defaultVal - } else if opts.Contains("optional") { + } else if tagOpts.Contains("optional") { // Optional so skip missing error continue } else { @@ -185,10 +189,14 @@ func fromEnv[T any](opts options) (T, error) { continue } } + // If the consumer hasn't explicitely allowed whitespace, we trim it by default + if !opts.AllowWhitespace { + envValue = strings.TrimSpace(envValue) + } // Empty value - if strings.TrimSpace(envValue) == "" { + if envValue == "" { // If required option is set, this is an error - if opts.Contains("required") { + if tagOpts.Contains("required") { errs.Add(fmt.Errorf("%w: %v", ErrMissingRequiredField, envKey)) } // Otherwise zero-values are fine diff --git a/dotconfig_test.go b/dotconfig_test.go index 43650be..d92c2ba 100644 --- a/dotconfig_test.go +++ b/dotconfig_test.go @@ -214,3 +214,25 @@ func TestFileIO(t *testing.T) { t.Fatal(err) } } + +type testAllowWhitespace struct { + Value string `env:"WHITESPACE_VALUE"` +} + +func TestAllowWhitespace(t *testing.T) { + os.Setenv("WHITESPACE_VALUE", " whitespace ") + config, err := dotconfig.FromFileName[testAllowWhitespace]("doesn't exist") + if err != nil { + t.Errorf("Wasnt't expecting error. Got: %v", err) + } + if config.Value != "whitespace" { + t.Error("Expected to trim whitespace.") + } + config, err = dotconfig.FromFileName[testAllowWhitespace]("doesn't exist", dotconfig.AllowWhitespace) + if err != nil { + t.Errorf("Wasnt't expecting error. Got: %v", err) + } + if config.Value != " whitespace " { + t.Error("Expected to allow whitespace.") + } +}