Skip to content

クロスフィールド & ビジネスルール

タグ層は単一フィールドの形を扱います。2 つ以上のフィールド に 依存する処理(パスワード確認、条件付き必須、計算された境界値……) は Validate() error フックを使います。

仕様

構造体の値(あるいはそのポインタ)が次を実装している場合:

go
Validate() error

…govalid はタグルールが終わった後、Check 呼び出しごとにこれを 実行します。非 nil のエラーは結果スライスに *ErrContext として 追加され、あなたが返したメッセージがラップされます。

シグネチャは 正確に func() error でなければなりません:

メソッド形呼ばれる?
func (T) Validate() errorはい(値レシーバー)
func (*T) Validate() error呼び出し側が *T を渡したとき
func (T) Validate(extra string) errorいいえ——アリティ違い
func (T) Validate() stringいいえ——戻り値違い

パスワード確認

go
type Form struct {
    Password       string `valid:"required;minlen:8" label:"密码"`
    RepeatPassword string `valid:"required"          label:"重复密码"`
}

func (f *Form) Validate() error {
    if f.Password != f.RepeatPassword {
        return errors.New("两次输入的密码不一致")
    }
    return nil
}

組み込みの equal チェッカーを使った等価な書き方:

go
type Form struct {
    Password       string `valid:"required;minlen:8" label:"密码"`
    RepeatPassword string `valid:"required;equal:Password" label:"重复密码"`
}

Validate 形式の方が柔軟です——条件と外部参照を混ぜられます。

条件付き必須

go
type Address struct {
    Country string `valid:"required;list:CN,US"`
    State   string // Country == "US" の時のみ必須
}

func (a Address) Validate() error {
    if a.Country == "US" && a.State == "" {
        return errors.New("state is required for US addresses")
    }
    return nil
}

外部参照

Validate はただの Go コードなので、データベース、キャッシュ、その 他のサービスを呼び出せます。ただし Check を認可境界にしないで ください——キャンセルやタイムアウトの概念がありません。

go
type CreateUser struct {
    Username string `valid:"required;username"`
    Email    string `valid:"required;email"`
}

func (u *CreateUser) Validate() error {
    if exists, _ := userRepo.UsernameExists(u.Username); exists {
        return fmt.Errorf("用户名 %q 已被占用", u.Username)
    }
    return nil
}

TIP

可能な限り Validate を決定的・副作用なしに保ちましょう。バリデー ションは 1 リクエスト中に複数回呼ばれることが多いため(プレビュー、 保存、再描画)、思わぬ副作用はデバッグを難しくします。

より豊かなエラーを返す

Validate は任意の error を返せます。govalid はユーザー向け メッセージを組み立てる際 Error() で文字列化するので、Error() が ローカライズされた文字列を返すカスタムエラー型はそのまま動きます:

go
type LocalizedErr struct{ zh, en string }

func (e *LocalizedErr) Error() string { return e.zh }

func (f *Form) Validate() error {
    if !ok {
        return &LocalizedErr{zh: "操作失败", en: "operation failed"}
    }
    return nil
}

元のエラー型にアクセスしたい場合、errs[i].Unwrap() 経由で errors.As を使うのが筋ですが——*ErrContext は現在 Unwrap を 公開していないので、当面はエラーデータを Error() に持たせるのが 最もシンプルです。

MIT ライセンスのもとで公開されています。