はじめに
バリデーションライブラリであるgo-playground/validator
は非常に強力ですが、デフォルトのエラーメッセージをフィールドごとにカスタマイズしたい場合があります。そこで、今回はカスタムメッセージをタグで設定できるようにするライブラリcmvalidator
を作成したので紹介します。
デフォルトのバリデーションメッセージの課題
通常、go-playground/validator
を使用して構造体をバリデートする際には、次のようなデフォルトのエラーメッセージが生成されます。
package main
import (
"github.com/go-playground/validator/v10"
)
type User struct {
FirstName string `validate:"required"`
LastName string `validate:"required"`
Age int `validate:"gte=0,lte=130"`
Email string `validate:"required,email"`
}
func main() {
validate := validator.New()
newUser := User{
FirstName: "",
LastName: "",
Age: 200, // invalid age
Email: "invalid-email",
}
err := validate.Struct(newUser)
if err != nil {
for _, err := range err.(validator.ValidationErrors) {
println("Validation error:", err.Error())
}
}
}
このコードでは、以下のようなエラーメッセージが表示されます。
Validation error: Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'required' tag
Validation error: Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'required' tag
Validation error: Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag
Validation error: Key: 'User.Email' Error:Field validation for 'Email' failed on the 'email' tag
これらのメッセージは技術者にとっては十分な情報ですが、エンドユーザーに直接提示する場合、もう少し分かりやすいメッセージが欲しいです。またフィールドごとにカスタマイズしたメッセージが欲しい場合、開発者がFieldErrorインターフェースを使ってエラーメッセージを変換する必要があります。
cmvalidatorを使ったカスタムメッセージの設定
そこで今回作成したcmvalidator
というライブラリを使うことで、構造体のタグにカスタムメッセージを設定でき、ユーザーにとって分かりやすいメッセージを表示することができます。
インストール
まず、cmvalidator
をプロジェクトにインストールします。
go get github.com/yudai2929/cmvalidator
使用方法
次に、以下のようにカスタムメッセージを含む構造体を定義します。
package main
import (
"github.com/yudai2929/cmvalidator"
)
type User struct {
FirstName string `validate:"required" customMessage:"名前を入力してください!"`
LastName string `validate:"required" customMessage:"姓を入力してください!"`
Age int `validate:"gte=0,lte=130" customMessage:"年齢は0から130の間で入力してください!"`
Email string `validate:"required,email" customMessage:"有効なメールアドレスを入力してください!"`
}
次に、cmvalidator
を使ってバリデーションを行います。
package main
import (
"errors"
"github.com/yudai2929/cmvalidator"
)
func main() {
validate := cmvalidator.New()
newUser := User{
FirstName: "",
LastName: "",
Age: 200, // invalid age
Email: "invalid-email",
}
err := validate.Struct(newUser)
if err != nil {
var cmvalidateErrors cmvalidator.CFValidateErrors
if errors.As(err, &cmvalidateErrors) {
for _, cmvalidateError := range cmvalidateErrors {
println("Custom Message:", cmvalidateError.CustomMessage())
}
}
}
}
このコードを実行すると、次のようにカスタムメッセージが出力されます。
Custom Message: 名前を入力してください!
Custom Message: 姓を入力してください!
Custom Message: 年齢は0から130の間で入力してください!
Custom Message: 有効なメールアドレスを入力してください!
内部挙動の解説
cmvalidator
の内部では、go-playground/validator
のバリデーション機能を拡張する形で動作しています。
カスタムバリデーターの作成
cmvalidator
はgo-playground/validator
をラップしたCMValidate
という構造体を提供します。この構造体は、Struct
およびStructCtx
メソッドを持ち、これらを通じてバリデーション処理を行います。
type CMValidate struct {
*validator.Validate
}
func New() *CMValidate {
return &CMValidate{validator.New()}
}
バリデーションエラーの変換
Struct
メソッドやStructCtx
メソッドでは、まず通常のバリデーションを行い、その結果を元にエラーメッセージを生成します。エラーメッセージは、構造体のフィールドタグからcustomMessage
タグを取得し、それを適用する形でカスタマイズされます。
func (cv *CMValidate) Struct(s any) (err error) {
return cv.StructCtx(context.Background(), s)
}
func (cv *CMValidate) StructCtx(ctx context.Context, s any) (err error) {
err = cv.Validate.StructCtx(ctx, s)
if err == nil {
return
}
var validationErrors validator.ValidationErrors
if !errors.As(err, &validationErrors) {
return
}
return cnvToCMValidateErrors(validationErrors, s)
}
func cnvToCMValidateErrors(err validator.ValidationErrors, s any) CFValidateErrors {
cmvalidateErrors := make(CFValidateErrors, 0, len(err))
for _, validationError := range err {
cmvalidateErrors = append(cmvalidateErrors, cnvToCMFieldError(validationError, s))
}
return cmvalidateErrors
}
func cnvToCMFieldError(err validator.FieldError, s any) CMFieldError {
typ := reflect.TypeOf(s)
fieldName := err.StructField()
field, ok := typ.FieldByName(fieldName)
if !ok {
return cmFieldError{
FieldError: err,
customMessage: "",
}
}
customMessage := field.Tag.Get(CustomMessageTag)
return cmFieldError{
FieldError: err,
customMessage: customMessage,
}
}
エラーのラッピング
最後に、カスタムメッセージを含むエラー情報をCMFieldError
としてラップし、これを返却します。このカスタムエラーメッセージは、後でCustomMessage
メソッドを使って簡単に取得することができます。
type CMFieldError interface {
validator.FieldError
CustomMessage() string
}
type cmFieldError struct {
validator.FieldError
customMessage string
}
func (cfe cmFieldError) CustomMessage() string {
return cfe.customMessage
}
cmvalidatorの課題
cmvalidator
では、フィールドごとにカスタムメッセージを設定できますが、特定のバリデーションタグ(例えばrequired
やemail
など)に対して共通のメッセージを指定する機能はありません。現状では、フィールドごとにカスタムメッセージを設定する必要があるため、同じバリデーションルールに対しても各フィールドに個別にメッセージを設定しなければなりません。
まとめ
cmvalidator
を使うことで、構造体のバリデーションエラーが起きたフィールドに対して、より詳細なカスタムメッセージを簡単に設定することができます。興味のある方はぜひ試してみてください。