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
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,24 @@ examples.
}
```

As a special case where the default value should contain a literal `$`,
escape it with a backslash. Unfortunately this requires a double backslash
in the struct tag:

```go
type MyStruct struct {
Amount string `env:"AMOUNT, default=\\$5.00"` // Default: $5.00
}
```

To have a literal backslash followed by a `$`, escape the backslash:

```go
type MyStruct struct {
Filepath string `env:"FILEPATH, default=C:\\Personal\\\\$name"` // Default: C:\Personal\$name
}
```

- `prefix` - sets the prefix to use for looking up environment variable keys
on child structs and fields. This is useful for shared configurations:

Expand Down
15 changes: 15 additions & 0 deletions envconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,18 @@ func lookup(key string, required bool, defaultValue string, l Lookuper) (string,
}

if defaultValue != "" {
// Handle escaped "$" by replacing the value with a character that is
// invalid to have in an environment variable. A more perfect solution
// would be to re-implement os.Expand to handle this case, but that's been
// proposed and rejected in the stdlib. Additionally, the function is
// dependent on other private functions in the [os] package, so
// duplicating it is toilsome.
//
// While admittidly a hack, replacing the escaped values with invalid
// characters (and then replacing later), is a reasonable solution.
defaultValue = strings.ReplaceAll(defaultValue, "\\\\", "\u0000")
defaultValue = strings.ReplaceAll(defaultValue, "\\$", "\u0008")

// Expand the default value. This allows for a default value that maps to
// a different environment variable.
val = os.Expand(defaultValue, func(i string) string {
Expand All @@ -657,6 +669,9 @@ func lookup(key string, required bool, defaultValue string, l Lookuper) (string,
return ""
})

val = strings.ReplaceAll(val, "\u0000", "\\")
val = strings.ReplaceAll(val, "\u0008", "$")

return val, false, true, nil
}
}
Expand Down
28 changes: 28 additions & 0 deletions envconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1350,6 +1350,34 @@ func TestProcessWith(t *testing.T) {
"DEFAULT": "value",
}))),
},
{
name: "default/escaped_doesnt_interpolate",
target: &struct {
Field string `env:"FIELD,default=\\$DEFAULT"`
}{},
exp: &struct {
Field string `env:"FIELD,default=\\$DEFAULT"`
}{
Field: "$DEFAULT",
},
lookuper: MapLookuper(map[string]string{
"DEFAULT": "should-not-be-replaced",
}),
},
{
name: "default/escaped_escaped_keeps_escape",
target: &struct {
Field string `env:"FIELD,default=C:\\Personal\\\\$DEFAULT"`
}{},
exp: &struct {
Field string `env:"FIELD,default=C:\\Personal\\\\$DEFAULT"`
}{
Field: `C:\Personal\value`,
},
lookuper: MapLookuper(map[string]string{
"DEFAULT": "value",
}),
},
{
name: "default/slice",
target: &struct {
Expand Down