Go 言語で、テストに必要な Variables がちゃんとセットされているか確認したいという要望があった。その Variables もいろんなところに分散していたら面倒なので、一か所で書きたかった。つまりこんなイメージ。
type Foo struct {
Bar string `env:"TF_VAR_hello"`
Baz string `env:"TF_VAR_world"`
}
こんな感じで構造体を書いておくと、自動で、TF_VAR_hello
と TF_VAR_world
の環境変数を読み込んで、それをそれぞれのフィールドにセットしてほしい、そしてすべてのフィールドがちゃんとなんかしらの値がセットされていてほしい。
このような要件の時はリフレクションだけど、Goではやったことなかったので調べて試してみた。
フィールドの読み込み
定義されたすべてのフィールドの読み込みは次のコードで可能になる。ちなみに、s
は、ポインターでも実体でも良い。
func validate(s interface{}) {
val := reflect.ValueOf(s).Elem()
for i := 0; i < val.NumField(); i++ {
valueField := val.Field(i)
typeField := val.Type().Field(i)
tag := typeField.Tag
fmt.Printf("Field Name: %v, \t Field Value: %s,\t Tag value: %s\n", typeField.Name, valueField, tag)
}
}
Mainから実行してみる。
func main() {
foo := &Foo{
Bar: "abc",
Baz: "def",
}
validate(foo)
}
実行結果
Field Name: Bar, Field Value: abc, Tag value: env:"TF_VAR_hello"
Field Name: Baz, Field Value: def, Tag value: env:"TF_VAR_world"
フィールドへの値のセット
フィールドへの値のセットは次のように行う。interface{}
なので、型チェックをしてから、SetString()
などで型を明確にしてとると良い。
for i := 0; i < val.NumField(); i++ {
if val.Field(i).Kind() == reflect.String {
val.Field(i).SetString("overriden")
}
}
更新した後は、ついでに、Tagの値をとるようにしましょう。とても簡単。
for i := 0; i < val.NumField(); i++ {
valueField := val.Field(i)
typeField := val.Type().Field(i)
tag := typeField.Tag
fmt.Printf("Field Name: %v, \t Field Value: %s,\t Tag value: %s\n", typeField.Name, valueField, tag.Get("env"))
}
実行結果
$ go run *.go
Field Name: Bar, Field Value: abc, Tag value: env:"TF_VAR_hello"
Field Name: Baz, Field Value: def, Tag value: env:"TF_VAR_world"
Field Name: Bar, Field Value: overriden, Tag value: TF_VAR_hello
Field Name: Baz, Field Value: overriden, Tag value: TF_VAR_world
ちなみに、interface{}
を元の型にキャストしたいときはこんな感じ。TypeValidationが必要です。ちなみに、DeserializeVariablesStruct
は自分で書いた関数で、Tagを元に環境変数から値をセットして、
Structを戻す関数です。
result := DeserializeVariablesStruct(&Foo{})
foo := result.(*Foo)
思ったよりずっと簡単やった。もっとAPIを掘りたいところやけど、今日はここまで。
全ソースコード
package main
import (
"fmt"
"reflect"
)
// Foo is a testing struct
type Foo struct {
Bar string `env:"TF_VAR_hello"`
Baz string `env:"TF_VAR_world"`
}
func main() {
foo := &Foo{
Bar: "abc",
Baz: "def",
}
validate(foo)
}
func validate(s interface{}) {
val := reflect.ValueOf(s).Elem()
for i := 0; i < val.NumField(); i++ {
valueField := val.Field(i)
typeField := val.Type().Field(i)
tag := typeField.Tag
fmt.Printf("Field Name: %v, \t Field Value: %s,\t Tag value: %s\n", typeField.Name, valueField, tag)
}
for i := 0; i < val.NumField(); i++ {
if val.Field(i).Kind() == reflect.String {
val.Field(i).SetString("overriden")
}
}
for i := 0; i < val.NumField(); i++ {
valueField := val.Field(i)
typeField := val.Type().Field(i)
tag := typeField.Tag
fmt.Printf("Field Name: %v, \t Field Value: %s,\t Tag value: %s\n", typeField.Name, valueField, tag.Get("env"))
}
}