🦝

コード生成型バリデーターgovalidをtanukirpcで使う

に公開

https://zenn.dev/sivchari/articles/483c9ecc234e72

上記の記事にあるように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というキーがあるとHelloHandlerreq.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