Go の Gin でのバリデーション
Gin は go-playground/validator を内蔵しており、構造体タグでバリデーションルールを定義できる。入力検証を宣言的に行えるため、コードがすっきりする。
基本的なバリデーション
構造体フィールドに binding タグを付けるとバリデーションが有効になる。
type CreateUserRequest struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
Age int `json:"age" binding:"required,gte=0,lte=120"`
}
r.POST("/users", func(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// バリデーション通過
c.JSON(201, gin.H{"user": req})
})バリデーションに失敗すると ShouldBindJSON がエラーを返す。
よく使うバリデーションタグ
required
値が必須。ゼロ値(空文字、0、nil など)は許可されない。
email
メールアドレス形式かどうかをチェックする。
gte, lte, gt, lt
数値の範囲を指定。gte=0 は「0以上」、lte=100 は「100以下」。
min, max
文字列の長さや配列の要素数の範囲。min=3 は「3文字以上」。
len
文字列の長さや配列の要素数が正確にその値かどうか。
oneof
指定した値のいずれかであること。oneof=male female other のように使う。
複数のバリデーションルール
カンマで区切って複数のルールを指定できる。
type Product struct {
Name string `json:"name" binding:"required,min=2,max=100"`
Price float64 `json:"price" binding:"required,gt=0"`
Category string `json:"category" binding:"required,oneof=electronics books clothing"`
SKU string `json:"sku" binding:"required,len=10"`
}ネストした構造体のバリデーション
ネストした構造体も自動的にバリデーションされる。
type Address struct {
Street string `json:"street" binding:"required"`
City string `json:"city" binding:"required"`
ZipCode string `json:"zip_code" binding:"required,len=7"`
}
type Order struct {
ProductID int `json:"product_id" binding:"required,gt=0"`
Quantity int `json:"quantity" binding:"required,gte=1,lte=100"`
Shipping Address `json:"shipping" binding:"required"`
}dive タグを使うとスライス内の各要素もバリデーションできる。
type BulkOrder struct {
Items []OrderItem `json:"items" binding:"required,dive"`
}
type OrderItem struct {
ProductID int `json:"product_id" binding:"required,gt=0"`
Quantity int `json:"quantity" binding:"required,gte=1"`
}カスタムバリデーションの作成
独自のバリデーションルールを定義できる。
import "github.com/go-playground/validator/v10"
// カスタムバリデーション関数
func validateUsername(fl validator.FieldLevel) bool {
username := fl.Field().String()
// 英数字とアンダースコアのみ許可
matched, _ := regexp.MatchString(`^[a-zA-Z0-9_]+$`, username)
return matched
}
func main() {
r := gin.Default()
// バリデーターを取得して登録
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("username", validateUsername)
}
// 使用
type User struct {
Username string `json:"username" binding:"required,username"`
}
}エラーメッセージのカスタマイズ
デフォルトのエラーメッセージは技術的で、ユーザーに見せるには不親切だ。
func formatValidationErrors(err error) map[string]string {
errors := make(map[string]string)
if validationErrors, ok := err.(validator.ValidationErrors); ok {
for _, e := range validationErrors {
field := e.Field()
switch e.Tag() {
case "required":
errors[field] = field + " は必須です"
case "email":
errors[field] = "有効なメールアドレスを入力してください"
case "min":
errors[field] = field + " は " + e.Param() + " 文字以上必要です"
case "max":
errors[field] = field + " は " + e.Param() + " 文字以下にしてください"
default:
errors[field] = field + " が無効です"
}
}
}
return errors
}
r.POST("/users", func(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{
"errors": formatValidationErrors(err),
})
return
}
// ...
})条件付きバリデーション
required_if などのタグを使って、他のフィールドの値に応じたバリデーションも可能だ。
type Payment struct {
Method string `json:"method" binding:"required,oneof=card bank"`
CardNumber string `json:"card_number" binding:"required_if=Method card"`
BankCode string `json:"bank_code" binding:"required_if=Method bank"`
}Method が “card” の場合は CardNumber が必須、“bank” の場合は BankCode が必須になる。
omitempty との組み合わせ
更新 API など、一部のフィールドだけ送信される場合は omitempty を使う。
type UpdateUserRequest struct {
Name string `json:"name" binding:"omitempty,min=2,max=100"`
Email string `json:"email" binding:"omitempty,email"`
Age int `json:"age" binding:"omitempty,gte=0,lte=120"`
}omitempty を付けると、値がゼロ値の場合はバリデーションをスキップする。値が存在する場合のみ、その他のルールが適用される。