emahiro/b.log

日々の勉強の記録とか育児の記録とか。

protovalidate でリクエスト内部の時刻型を検証するときに考えたこと

Overview

protovalidate でリクエスト内に定義した時刻型の検証を書いたときに考えたことを備忘録としてメモっておきます。

1. そのまま prorovalidate を書く

string start_datetime = 3 [(buf.validate.field).string = {pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}[+-]\\d{2}:\\d{2}$"}];

そのままですね。こんな感じで都度検証したい時刻の書式(このサンプルは RFC 3339)を正規表現で書きます。

2. 外部の定義を利用する

google/protobuf/timestamp.proto を利用する

下記のブログを参考にしました。

blog.soushi.me

型定義は以下。

https://github.com/protocolbuffers/protobuf/blob/89c585602af7d28646ce92cd3abba07cfdad7fa6/src/google/protobuf/timestamp.proto#L133-L144:embed:lang=proto

これは Go だと google.golang.org/protobuf/types/known/timestamppb.Timestamp 型になって利用できます。

type Timestamp struct {

    // Represents seconds of UTC time since Unix epoch
    // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
    // 9999-12-31T23:59:59Z inclusive.
    Seconds int64 `protobuf:"varint,1,opt,name=seconds,proto3" json:"seconds,omitempty"`
    // Non-negative fractions of a second at nanosecond resolution. Negative
    // second values with fractions must still have non-negative nanos values
    // that count forward in time. Must be from 0 to 999,999,999
    // inclusive.
    Nanos int32 `protobuf:"varint,2,opt,name=nanos,proto3" json:"nanos,omitempty"`
    // contains filtered or unexported fields
}

ref:https://pkg.go.dev/google.golang.org/protobuf@v1.36.7/types/known/timestamppb#Timestamp

アプリケーションの中で time.Time 型として扱いたいときは、 AsTime メソッドを利用します。

googleapis/googleapis/datetime.proto を利用する

自公の 外部定義としては buf.build/googleapis/googleapis 配下の datetime.proto も利用できます。

これは timestamppb.Timestamp と異なり Datetime 形式で時刻を定義、検証ができます。
実際の定義 は以下。

https://github.com/googleapis/googleapis/blob/master/google/type/datetime.proto#L52-L94:embed:lang=proto

GoDoc は以下。

pkg.go.dev

timestamppb 型に比べるとこちらの方が直感的で使いやすいんじゃないかなと思います。

その他

google/protobuf は外部の定義を確認したいときに確認する

以下に色んな型が定義されているので定期的に参照すると良さそう。

github.com

カスタムエラーメッセージを定義する

時刻型に限った話じゃないのですが、validation のエラーが発生したとき、そのエラーメッセージをそのまま返してしまうと、思わぬアプリケーション内部の情報をクライアントに伝えてしまうことになるので、 proto 内の validation の内部で定義して、それをクライアントに返すようにしたい場合、CEL 記法を使って validation を拡張できます。

  string start_datetime = 1 [(buf.validate.field) = {
    cel: {
      id: "start_datetime.format"
      message: "開始日時はRFC3339形式(例: 2024-03-20T19:00:00+09:00)で入力してください"
      expression: "this.matches('^\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}:\\\\d{2}:\\\\d{2}[+-]\\\\d{2}:\\\\d{2}$')"
    }
  }];

デフォルトでは protovalidation の結果が返却されますが、上記の場合 message に指定したメッセージが返されます。