ユーザーアイコン

mizuko

3か月前

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 }