コード生成型バリデーターgovalidをtanukirpcで使う
上記の記事にあるようにgo-validatorのように使えつつもコード生成を活用してreflect-freeでバリデーションを行うライブラリがあり面白そう。実用的に使うにはということで、拙作のWeb Application Framework tanukirpcと組み合わせて使う例を紹介する。
バリデーションを行うAPIエンドポイントを作る
環境はGo 1.24.5です。なのでgo toolが使える前提で進める。
まず、ディレクトリをGo Modulesとして初期化し、色々ライブラリを入れていく。
$ go mod init
$ go get github.com/mackee/tanukirpc@latest
$ go get -tool github.com/sivchari/govalid/cmd/govalid
そして以下のようにtanukirpcのRouterを記述。基本的な使い方を示す。
package main
import (
"context"
"log/slog"
"github.com/mackee/tanukirpc"
)
func main() {
ctx := context.Background()
router := tanukirpc.NewRouter(struct{}{})
router.Get("/hello", tanukirpc.NewHandler(HelloHandler))
if err := router.ListenAndServe(ctx, ":8080"); err != nil {
slog.ErrorContext(ctx, "Failed to start server", slog.Any("error", err))
}
}
type HelloRequest struct {
Name string `query:"name"`
}
type HelloResponse struct {
Message string `json:"message"`
}
func HelloHandler(ctx tanukirpc.Context[struct{}], req *HelloRequest) (*HelloResponse, error) {
return &HelloResponse{Message: "Hello, " + req.Name}, nil
}
query stringにnameというキーがあるとHelloHandlerのreq.Nameにquery stringの値が入ってくる。
比較のためにgo-playground/validatorを使う例
ところでtanukirpcにはgo-playground/validatorのタグを検知して勝手にvalidationを行う機能がある。govalidとの比較のために一旦そっちの例を示す。
ここで以下のようにHelloRequestのタグにvalidate:"required"を追加する。
type HelloRequest struct {
Name string `query:"name" validate:"required"`
}
この状態で go run ./ とし、サーバーを起動する。そしてcurlで叩いてみる。
$ curl 'http://localhost:8082/hello?name='
{"error":{"message":"Key: 'HelloRequest.Name' Error:Field validation for 'Name' failed on the 'required' tag"}}
このレスポンス形式はtanukirpc.NewRouterのオプションとして渡すtanukirpc.WithErrorHookerで変えることができる。しかし一旦それは置いとく。
govalidをtanukirpcで使う(本題)
ではgovalidで使用するにはどうするか。まずgo generateから実行できるようにする。importの直後に以下の行を追加。
//go:generate go tool govalid ./
そして、HelloRequestの定義にgovalid向けのアノテーションコメントを追加する。
type HelloRequest struct {
// +govalid:required
Name string `query:"name"`
}
この状態でgo generateを実行する。
$ go generate ./
するとmain_hellorequest_validator.goというファイルが生成される。この中にはバリデーションを行うValidateHelloRequestという関数が定義されている。これをリクエストを受けたときにHelloRequestを渡して検証させれば良い。
tanukirpcのRequest Validation機構であるが、先ほど紹介したgo-playground/validatorのタグを記述する他にもう一つある。それは、type Validatable interface { Validate() error }というinterfaceをRequest structに実装するものである。今回はこの方法を利用してgovalidが生成したバリデーション関数を利用する。
HelloRequestに対してValidatableを実装する。
func (r *HelloRequest) Validate() error {
return ValidateHelloRequest(r)
}
これで動かしてみよう。
$ curl 'http://localhost:8082/hello?name='
{"error":{"message":"field HelloRequestName is required"}}
govalidが生成したバリデーションをtanukirpcで構築したWebアプリケーションに組み見込めたことがわかる。
レスポンス形式を変えたければ同様にErrorHookerを用意するのが良いだろう。もしかしたらこれらのValidatorやErrorHookerもコード生成するのが良いかもしれない。
Discussion