Goでバリデーションの仕組みを作る
Go
バックエンド
知識
structを以下の様に設定
go
type Credentials struct {
Email string `json:"email" validate:"required,email" ja:"メールアドレス"`
Password string `json:"password" validate:"required,min=6" ja:"パスワード"`
}validation.go
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)
}バリデーションの共通化及び日本語化
go
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
}利用方法
go
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
}