Go言語のWebフレームワークであるginでは、HTTPリクエストのパラメータを受け取るための構造体にタグとしてValidatorを記述することが可能だ。このValidatorは、リクエストパラメータの値が特定の条件を満たすことを検査することができる。

例えば、あるフィールドが、必須であったり、特定の形式に一致しなければならなかったりなど、様々な条件を指定することができる。これにより、リクエストパラメータが正しくない場合にはエラーを返すなど、APIの堅牢性を高めることができる。

ginは標準で多数のValidatorを提供しているが、実際に使っていくとそれだけでは要件を満たせない場合がある。そのような場合には、独自のCustom Validatorを作成し、それを適用することができる。これにより、より複雑な条件を満たすようなリクエストパラメータの検証を行うことができるようになる。

この記事では、そのような独自のCustom Validatorの作成方法と、それをginの構造体に適用する方法について解説する。

ginの検証機能について

ginの検証機能は、リクエストパラメータの妥当性を確認するための強力なツールだ。開発者はこの機能を用いて入力データが期待する形式と一致しているかどうかを確認することができる。例えば、特定フィールドが数値である必要がある場合、文字列が送信されていないかを検証するといったことができる。また、必須のパラメータが欠落していないかもチェックすることができる。

これらの検証は、アプリケーションの安全性と信頼性を確保する上で重要だ。ユーザーからの入力は常に予測不可能であるため、これらの検証を行うことで、アプリケーションが予期せぬ動作を起こすことを防ぐことができる。

ginではvalidatorパッケージが標準で採用されている。これにより、様々な種類の検証を容易に行うことができる。例えば、文字列の長さやパターン、数値の範囲など、多くの一般的な検証がサポートされている。また、カスタムvalidatorを作成することも可能であり、これにより特定のビジネスロジックに基づいた複雑な検証を実装することも可能だ。

ginでValidatorを使用する方法

ginでValidatorを指定するには、リクエストパラメータを受け取るための構造体にバインディングタグを追加することで実現できる。例として、idを指定して該当するユーザーを取得するようなAPIを作成することを考えてみよう。

まず、リクエストパラメータを受け取るための構造体を定義する。この構造体のフィールドは、リクエストパラメータと一致させる必要がある。以下の例では、 idフィールドはrequiredタグが付けることで、このフィールドがリクエストに含まれている必要があることを示している。

type GetUserRequest struct {
	id `form:"id" binding:"required"`
}

次に、ginのハンドラ関数内で上記で定義した構造体のインスタンスを作成し、そのインスタンスに対してリクエストからデータをバインドする。ginの Bind関数を使用すると、リクエストデータを構造体にバインドすることができる。このバインド処理は、リクエストデータの各パラメータを構造体の対応するフィールドに適切にマッピングする。さらに、このバインド処理は同時にバリデーションも行う。バリデーションは、バインドされたデータが構造体の定義に基づいた制約を満たしているかを検査する。

func GetUserRequestHandler(ctx* gin.Context) {
	var req GetUserRequest
	if err := ctx.Bind(&req); err != nil {
		ctx.Error(err)
		return
	}
	// ...
}

required 以外で使用できるvalidatorは、GitHubのREADMEに記載されているので確認してみてほしい。

Custom Validatorを作成する方法

次に、Custom validatorを作成する方法を紹介する。例として、リクエストパラメータが郵便番号の形式になっているかどうかを判定するようなvalidatorを作成することを考える。具体的には、次のような構造体を作成してリクエストパラメータを受けた時に、郵便番号が不正な値ならエラーにしてくれるようなvalidatorを作成することにする。

type CreateUserInfoRequest struct {
	// ...
	postalCode `json:"id" binding:"required,postalcode"`
	// ...
}

上記の postalcode のように、任意の名前を記述することで自前のvalidatorを呼べるようにするには、以下のように名前と関数の組を RegisterValidation 関数で登録することで実現できる。第一引数は使用する際の名前で、第二引数が検証時に実行される関数(後述)になる。

if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
	v.RegisterValidation("postalcode", validators.PostalCode)
}

実際にvalidation処理を行う関数を作成していく(上記の validators.PostalCode の部分)。ginのvalidator関数は、引数に validator.FieldLevel を取り、bool値を返すようなものである必要がある。郵便番号を検査する関数は、例えば以下のような実装になる。

package validators

var postalCodeRegex = regexp.MustCompile(`^[0-9]{3}-[0-9]{4}$`)

var PostalCode = func(fl validator.FieldLevel) bool {
	value := fl.Field().String()
	return postalCodeRegex.MatchString(value)
}

validator.FieldLevel は、 Field 関数を呼ぶことでリクエストパラメータとして渡ってきた値を reflect.Value 型の値として取得することができる。上記の例では、いきなりString型にしているが、 reflect.Value 型なので以下のように Kind 関数でKind型を用いて検査をしたりといったこともできる。

value := fl.Field()
if value.Kind() == reflect.Int {
	// ...
} 

参考