こんにちは。てぃろです。
今回はGo言語のコーディングのメモです。
最近APIのリクエストパラメータの定義としてenumを使おうとしていたのですが、Go言語には言語仕様としてのenumがありません。
しかしenumっぽい書き方はできるので、それを利用することにしました。
ただし、 iota
は今回使わずに文字列ベースのenumを定義して使用としたところある問題に気付きました。
それはenumとして定数定義していない文字列も変数に入れてしまえる、ということです。
つまりこういうこと。
package main
import "fmt"
type Color string
const (
Red Color = "Red"
Blue Color = "Blue"
Yellow Color = "Yellow"
)
func main() {
var c Color
c = "Black"
fmt.Println(c) // Black
}
プログラムの内容を見れば当たり前のように見えるのですが、ここで定義しているColorをenumのように使いたい!と思うと意図としては”Red”か”Blue”か”Yellow”の3つ以外は c
には代入されてほしくないわけです。
他の言語でenum定義されている場合には、当たり前にこのような定義の場合 “Black” は入らないのでそれだけでバリデーションチェックができてしまうのですが、上記のようにGo言語ではenumという言語仕様がないので値を定義通りにチェックするのはこれではできません。
そこで、以下のようにバリデーションチェックの関数を作ることにします。
package main
import "fmt"
type Color string
const (
Red Color = "Red"
Blue Color = "Blue"
Yellow Color = "Yellow"
)
func (c Color) Valid() error {
switch c {
case Red, Blue, Yellow:
return nil
}
return fmt.Errorf("invalid Color: %s", c)
}
func main() {
var c Color
c = "Black"
if err := c.Valid(); err != nil {
fmt.Println(err.Error())
} else {
fmt.Println("valid")
}
}
これでenum定義を補強するバリデーションチェックができるようになります。
より厳密にしようとすると、 c.Set()
のようなJavaでいうgetterやsetterを作るようにするといいのかもしれませんが、書く量も増えるし規約も増えてしまうのであまり好みではありません。
その代わり c.Valid()
をしっかり呼ばないといけないという規約があるので、好みの問題かなと思います。
今後文字列ベースで作るときには c.Valid()
だけは作っていきたいなと思っています。
参考
追記(2022/12/3)
この記事のツイートに引用で以下のような情報を頂きました!
#golang でenumはiota+stringerがおすすめ。コンパイル時に未定義の定数はエラーになるのでバリデーションいらず。
— のぼのぼ⛱ (@nobonobo) November 28, 2022
JSONで文字列化したいなら以下のツールをstringerの代わりに使おう。https://t.co/k7TwqzE2G3 https://t.co/DLHHd70zIh
ここでご紹介いただいたツールでは、getter、setterみたいなイメージでmershalの中でバリデーション入れる形で考えているようですね。後出しではありますが、これは一度は考えていたことでしたし、これが作れるツールというのは非常に魅力的だと思います。
ただ、本記事でそもそも気にしていたのは、enum
の定義になっているある一つの要素に値を自由に入れてしまえることだったので少しやりたかったこととは違いました。
あとはiota
+ stringer
でどこまで今回気にしていた enum
定義の値にデータを自由に入れられないようにするバリデーションを実現できるのか?が気になります。
私が不勉強なだけで先駆者が何かしらやっていたり、うまくやる方法があったりする気がするので、もうちょっと調べたり試したりしたいと思います。
のぼのぼさん、情報いただきありがとうございました!