Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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.
16 changes: 12 additions & 4 deletions dotconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -33,6 +35,8 @@ func optsFromVariadic(opts []DecodeOption) options {
v.ReturnFileIOErrors = true
case EnforceStructTags:
v.EnforceStructTags = true
case AllowWhitespace:
v.AllowWhitespace = true
}
}
return v
Expand Down Expand Up @@ -169,26 +173,30 @@ 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 {
// Check to see if we have a default value
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 {
errs.Add(fmt.Errorf("%w: %v", ErrMissingEnvVar, envKey))
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
Expand Down
22 changes: 22 additions & 0 deletions dotconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
}
}
Loading