クロスフィールド & ビジネスルール
タグ層は単一フィールドの形を扱います。2 つ以上のフィールド に 依存する処理(パスワード確認、条件付き必須、計算された境界値……) は Validate() error フックを使います。
仕様
構造体の値(あるいはそのポインタ)が次を実装している場合:
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 | いいえ——戻り値違い |
パスワード確認
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 チェッカーを使った等価な書き方:
type Form struct {
Password string `valid:"required;minlen:8" label:"密码"`
RepeatPassword string `valid:"required;equal:Password" label:"重复密码"`
}Validate 形式の方が柔軟です——条件と外部参照を混ぜられます。
条件付き必須
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 を認可境界にしないで ください——キャンセルやタイムアウトの概念がありません。
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() が ローカライズされた文字列を返すカスタムエラー型はそのまま動きます:
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() に持たせるのが 最もシンプルです。