Goでバリデーションの仕組みを作る
Go
structを以下の様に設定
type Credentials struct { Email string `json:"email" validate:"required,email" ja:"メールアドレス"` Password string `json:"password" validate:"required,min=6" ja:"パスワード"` }
validation.go
type AuthValidator struct {
validate *validator.Validate
Translator ut.Translator
}
func NewAuthValidator() *AuthValidator {
validate := validator.New()
translator := request.RegisterCommonCustomErrorMessages(validate, []reflect.Type{
reflect.TypeOf(Credentials{}),
reflect.TypeOf(PostSetupRequest{}),
})
return &AuthValidator{
validate: validate,
Translator: translator,
}
}
func (v *AuthValidator) ValidateSetup(input Credentials) error {
return v.validate.Struct(input)
}
func (v *AuthValidator) ValidateCredentials(input PostSetupRequest) error {
return v.validate.Struct(input)
}
バリデーションの共通化及び日本語化
package request
import (
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
"github.com/go-playground/locales/ja"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
)
// メッセージを共通化するための関数
func RegisterCommonCustomErrorMessages(validate *validator.Validate, structTypes []reflect.Type) ut.Translator {
uni := ut.New(ja.New(), ja.New())
translator, _ := uni.GetTranslator("ja")
translations := map[string]struct {
Translation string
}{
"required": {Translation: "{0} は必須です。"},
"min": {Translation: "{0} は {1} 文字以上で入力してください。"},
"max": {Translation: "{0} は {1} 文字以下で入力してください。"},
"email": {Translation: "{0} は正しいメールアドレス形式で入力してください。"},
}
for tag, translation := range translations {
tag := tag
validate.RegisterTranslation(tag, translator, func(ut ut.Translator) error {
return ut.Add(tag, translation.Translation, true)
}, func(ut ut.Translator, fe validator.FieldError) string {
// 複数の型に対応するためにループを追加
for _, structType := range structTypes {
customFieldName := getCustomFieldName(fe, structType)
if customFieldName != fe.Field() {
namespace := fe.StructNamespace()
// 例 "Credentials.Email" -> ["Credentials", "Email"]
// 例 "[0].Email" -> ["[0]", "Email"]
fieldPath := strings.Split(namespace, ".")
// 例 "Credentials"
// 例 "[0]"
parentOrIndex := fieldPath[len(fieldPath)-2]
// "[]" にマッチするかどうか
matched, _ := regexp.MatchString(`^\[.*\]$`, parentOrIndex)
// requestが配列の場合、カスタムフィールド名を取得する
if matched {
indexStr := strings.Trim(parentOrIndex, "[]")
index, _ := strconv.Atoi(indexStr)
index++
customFieldName = fmt.Sprintf("%d番目の %s", index, customFieldName)
}
t, _ := ut.T(fe.Tag(), customFieldName, fe.Param())
return t
}
}
// カスタムフィールド名が見つからない場合、デフォルトのエラーメッセージを返す
return fe.Error()
})
}
return translator
}
func getCustomFieldName(fe validator.FieldError, structType reflect.Type) string {
fieldName := fe.Field()
if structType.Kind() == reflect.Ptr {
structType = structType.Elem()
}
field, found := structType.FieldByName(fieldName)
if found {
if customName, ok := field.Tag.Lookup("ja"); ok {
return customName
}
}
return fieldName
}
利用方法
var input request.PostSetupRequest if err := c.BindJSON(&input); err != nil { helper.HandleBadRequest(c, errors.ErrInvalidRequest) return } // バリデーションエラーがある場合は、エラーレスポンスを返す if err := h.Validator.ValidateSetup(&input); err != nil { helper.HandleValidationErrors(c, err, h.Validator.Translator) return }