protoc-gen-validateを使ったバリデーション入門

はじめに

今回は、私が現場で使っているprotoc-gen-validate(PGV)についてまとめてみました。 復習がてらまとめていきます。

PGVは.protoファイルにバリデーションルールを定義するだけで、各言語のバリデーションコードを自動生成できちゃう優れもの。

PGVってなに?

  • PGVは、Protocol Buffersのコード生成プラグインの1つ
  • .protoファイルで定義されたメッセージに対して、バリデーションルールを指定できる
  • そのルールを元に、PGVが各言語のバリデーションコードを自動生成してくれる

PGVの使い方

インストール方法

  • PGVは、Go言語で実装されてて、シングルバイナリで提供されてます
  • 以下のコマンドでインストールできる
go install github.com/envoyproxy/protoc-gen-validate@latest
  • HomebrewやDocker経由でもインストールできるみたいっすね

.protoファイルでのバリデーションルールの定義方法

  • .protoファイルの中で、こんな感じでバリデーションルールを定義します
message Person {
  uint64 id    = 1 [(validate.rules).uint64.gt    = 999];
  string email = 2 [(validate.rules).string.email = true];
  string name  = 3 [(validate.rules).string       = {min_len: 5, max_len: 20}];

  message Address {
    string street = 1 [(validate.rules).string.min_len = 5];
    string city   = 2 [(validate.rules).string.min_len = 5];
    string state  = 3 [(validate.rules).string = {in: ["CA", "NY"]}];
  }
  Address address = 4 [(validate.rules).message.required = true];
}

上の例だと、Personメッセージの各フィールドにバリデーションルールを定義してる感じっすね。 例えばidは999より大きい整数、emailはメールアドレスの形式を満たす必要があるといった感じ。

コード生成の方法

定義したバリデーションルールを元にコードを生成するには、protocコマンドに--validate_outオプションを指定します。

Go言語の場合はこんな感じ。

protoc \
  -I . \
  -I ${GOPATH}/src \
  -I ${GOPATH}/src/github.com/envoyproxy/protoc-gen-validate \
  --go_out=":./gen" \
  --validate_out="lang=go:./gen" \
  person.proto

上のコマンドを実行すると、genディレクトリ以下にperson.pb.goperson.pb.validate.goが生成されるわけですね

生成されたバリデーションコードの使用方法

  • 生成されたコードは、こんな感じで使えます
p := &Person{
  Id:    1000,
  Email: "john@example.com",
  Name:  "John Doe",
  Address: &Address{
    Street: "123 Main St",
    City:   "Anytown",
    State:  "CA",
  },
}

err := p.Validate()
if err != nil {
  // バリデーションエラー処理
}

Validateメソッドを呼び出すと、Personオブジェクトのバリデーションが行われるんです

バリデーションエラーがあれば、errにエラー内容が格納される、って感じっすね

バリデーションルールの定義例と、それによって防げるエラー

以下は、よくあるバリデーションルールの定義例と、それによって防げるエラーの例です。

  • 文字列フィールドの長さを制限する

      string name = 1 [(validate.rules).string.min_len = 1, (validate.rules).string.max_len = 100];
    

    これにより、空文字列や長すぎる文字列によるエラーを防げます。

  • 整数フィールドの範囲を制限する

      int32 age = 1 [(validate.rules).int32.gte = 0, (validate.rules).int32.lte = 150];
    

    これにより、負の数や非現実的な値によるエラーを防げます。

  • 繰り返しフィールドの要素数を制限する

      repeated string tags = 1 [(validate.rules).repeated.min_items = 1, (validate.rules).repeated.max_items = 5];
    

    これにより、空のリストや大量の要素によるエラーを防げます。

  • メッセージフィールドを必須にする

      Address address = 1 [(validate.rules).message.required = true];
    

    これにより、重要なフィールドの欠落によるエラーを防げます。

まとめ

  • PGVを使えば、.protoファイルにバリデーションルールを定義するだけで、各言語のバリデーションコードを自動生成できちゃう
  • PGVを使うことで、アプリケーションコードからバリデーションロジックを分離でき、コードの可読性とメンテナンス性が向上する
  • Protocol Buffersを使ってるプロジェクトには、PGVが最適かも

※ 公式では新しいプロジェクトは後継のprotovalidateを使ってね!とありました。https://github.com/bufbuild/protovalidate

ってことで、PGVについてざっくりまとめてみました!私のように、APIスキーマ定義にprotobufを使ってる勢には、かなり便利なツールだと思います。

参考資料