はじめに
koanf で環境変数を読み込む際、ProviderWithValue
を使うことでスライスに設定を流し込むことができます。
// 設定オブジェクト。環境変数を読み込んでここにセット
type config struct {
Vals []string
}
func main() {
k := koanf.New(".")
k.Load(env.ProviderWithValue("MYVAR_", ".", func(s string, v string) (string, interface{}) {
key := strings.Replace(strings.ToLower(strings.TrimPrefix(s, "MYVAR_")), "_", ".", -1)
// 値にスペースがあれば、値をスペース区切りでスライス化
if strings.Contains(v, " ") {
return key, strings.Split(v, " ")
}
// それ以外の場合は、文字列をそのまま返す
return key, v
}), nil)
c := config{}
k.Unmarshal("", &c)
fmt.Printf("%#v\n", c)
}
$ export MYVAR_VALS="foo bar baz"
main.config{Vals:[]string{"foo", "bar", "baz"}}
しかし、この環境変数に空文字を指定すると...
空スライス []string{}
ではなく、空文字要素を含むスライス []string{""}
にUnmarshalされてしまいます!
$ export MYVAR_VALS=
main.config{Vals:[]string{""}}
要素無指定にしたつもりが空文字を指定してしまっているので要注意! (無事ハマりました)
バージョン
- koanf v1.4.2
why?
バグではなく、依存パッケージ mapstructure の仕様によるものでした。
Koanf.Unmarshal
は内部的に mapstructure.Decoder.Decode
を呼んでいます。
そして、Decode
は
- 変換元のデータが文字列
かつ
- 変換先フィールドがスライス
の場合に、暗黙的にスライスに変換(lift) してくれます。
この機能によって、環境変数に1要素(foo
)しか指定しなくても、自動でスライスに変換して([]string{"foo"}
)セットしてくれます。
...そして、環境変数が空文字の場合は 「空文字1要素」が「空文字を含むスライス」( []string{""}
)になってしまったというわけです。
対処方法
愚直に、Unmarshal後のconfigを変換して対処することになりそうです(オプション等があれば楽にできそうですが...)。
幸い []string{""}
の場合だけ考えればよいのでそこまで複雑な変換ロジックは不要でした。