ユーザーアイコン

mizuko

5か月前

0
0

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 }