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

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

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

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

ginのValidator機能について

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

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

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

ginでValidatorを指定する方法

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

まず、リクエストパラメータを受け取るための構造体を定義します。この構造体のフィールドは、リクエストパラメータと一致させる必要があります。

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
	}
	// ...
}

この例では、 idフィールドはrequiredタグが付けられているため、このフィールドがリクエストに含まれていない場合、エラーが返されます。

required 以外に使用できるvalidatorはこちらのドキュメントを確認してください。

Custom Validatorを作成する方法

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

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

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

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 {
	// ...
} 

参考