Skip to content

ビジネスルール

タグ層は単一フィールドの形を扱います;Validate() メソッドはそれ 以外を全部扱います。本ページは本番コードでよく見るパターンを集めた ものです。

クロスフィールドの相等

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

あるいは等価に Validate で:

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

条件付き必須

go
type Address struct {
    Country string `valid:"required;list:CN,US,JP"`
    State   string
    Province string
}

func (a Address) Validate() error {
    switch a.Country {
    case "US":
        if a.State == "" {
            return errors.New("US 地址必须填写 state")
        }
    case "CN":
        if a.Province == "" {
            return errors.New("中国地址必须填写省份")
        }
    }
    return nil
}

日時の境界

govalid は日付チェッカーを同梱しません。カスタムでラップします:

go
import "time"

govalid.SetMessageTemplates(map[string]string{
    "afterToday": "必须晚于今天",
})

govalid.Checkers["afterToday"] = func(c govalid.CheckerContext) *govalid.ErrContext {
    t, ok := c.FieldValue.(time.Time)
    if !ok {
        return govalid.MakeValueTypeError(c)
    }
    if !t.After(time.Now()) {
        return govalid.NewErrorContext(c)
    }
    return nil
}

type Booking struct {
    StartAt time.Time `valid:"required;afterToday" label:"开始时间"`
}

データベースに対するユニーク性

Validate() はただの Go コード——リポジトリを呼んでください:

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

WARNING

バリデーションは認可ではありません。セキュリティ関連のチェックを データベースに依存するなら、書き込みを行うトランザクション内で そのチェックを行ってください——Validate() は UX 上の親切心であり、 保証ではありません。

エラーを豊かにする

1 つの構造体が多種のドメイン特有の理由で失敗しうる場合、Validate() から型付きセンチネルを返し、後で switch します:

go
var (
    errAdminBlocked = errors.New("admin reserved")
    errEmailReused  = errors.New("email already in use")
)

func (u *CreateUser) Validate() error {
    if u.Username == "admin" {
        return errAdminBlocked
    }
    if reused, _ := userRepo.EmailExists(u.Email); reused {
        return errEmailReused
    }
    return nil
}

errs, _ := govalid.Check(form)
for _, e := range errs {
    switch {
    case strings.Contains(e.Error(), errAdminBlocked.Error()):
        // ...
    }
}

govalid はあなたが返したエラーを文字列化するので、トークンベースの 識別が今のところ最もシンプルなパスです。将来のバージョンで Unwrap が直接公開されるかもしれません。

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