0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Go】validatorのタグでカスタムメッセージを設定する

Posted at

はじめに

バリデーションライブラリである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のバリデーション機能を拡張する形で動作しています。

カスタムバリデーターの作成

cmvalidatorgo-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では、フィールドごとにカスタムメッセージを設定できますが、特定のバリデーションタグ(例えばrequiredemailなど)に対して共通のメッセージを指定する機能はありません。現状では、フィールドごとにカスタムメッセージを設定する必要があるため、同じバリデーションルールに対しても各フィールドに個別にメッセージを設定しなければなりません。

まとめ

cmvalidatorを使うことで、構造体のバリデーションエラーが起きたフィールドに対して、より詳細なカスタムメッセージを簡単に設定することができます。興味のある方はぜひ試してみてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?