feat: 数据验证功能

main
熊二 1 year ago
parent b93b1d8ec1
commit 45bc099bb6
  1. 246
      pkg/v/error.go
  2. 52
      pkg/v/matcher.go
  3. 144
      pkg/v/translations.go
  4. 112
      pkg/v/v.go
  5. 696
      pkg/v/valuer.go

@ -0,0 +1,246 @@
package v
import (
"fmt"
"strings"
)
var emptyErrors []*Error
func init() {
emptyErrors = make([]*Error, 0)
}
// Error 验证错误结构体
type Error struct {
error error
code string
format string
params map[string]any
field string
label string
value any
}
// ErrorOption 错误配置函数签名
type ErrorOption func(*Error)
func merge(options []ErrorOption, presets ...ErrorOption) []ErrorOption {
return append(presets, options...)
}
// NewError 创建错误实例
func NewError(code string, options ...ErrorOption) *Error {
e := Error{code: code}
for _, option := range options {
option(&e)
}
return &e
}
// ErrorFormat 设置错误格式化字符串
func ErrorFormat(format string) ErrorOption {
return func(e *Error) {
e.format = format
}
}
// ErrorParam 设置验证参数
func ErrorParam(key string, value any) ErrorOption {
return func(e *Error) {
if e.params == nil {
e.params = make(map[string]any)
}
e.params[key] = value
}
}
// ErrorCode 设置错误代码
func ErrorCode(code string) ErrorOption {
return func(e *Error) {
e.code = code
}
}
// Code 返回错误代码
func (e *Error) Code() string {
return e.code
}
// Format 返回错误格式化模板
func (e *Error) Format() string {
return e.format
}
// Params 返回错误格式化蚕食
func (e *Error) Params() map[string]any {
p := map[string]any{}
if e.params != nil {
for k, v := range e.params {
p[k] = v
}
}
return p
}
// Field 返回字段名
func (e *Error) Field() string {
return e.field
}
// Label 返回错误标签
func (e *Error) Label() string {
return e.label
}
// Value 返回用于验证的值
func (e *Error) Value() any {
return e.value
}
// String 实现 fmt.Stringer 接口,返回格式化后的字符串
func (e *Error) String() string {
message := e.format
params := e.Params()
params["label"] = e.label
params["value"] = e.value
//params["field"] = e.field
// 定义了消息或翻译函数
if t, found := translations[e.code]; found {
if message == "" {
message = t.message
}
if t.trans != nil {
return t.trans(message, params)
}
}
// 设置了默认翻译函数
if defaultTranslator != nil {
return defaultTranslator(message, params)
}
for key, value := range params {
message = strings.ReplaceAll(message, "{"+key+"}", fmt.Sprintf("%v", value))
}
return message
}
// Error 实现内置错误接口(优先使用内部错误)
func (e *Error) Error() string {
if e.error != nil {
return e.error.Error()
}
return e.String()
}
// Errors 错误集
type Errors struct {
errors []*Error
}
// IsEmpty 是否存在错误
func (e *Errors) IsEmpty() bool {
if e == nil || e.errors == nil {
return true
}
return len(e.errors) == 0
}
// Add 添加一个错误
func (e *Errors) Add(err error) {
if err == nil {
return
}
if e.errors == nil {
e.errors = make([]*Error, 0)
}
if ex, ok := err.(*Errors); ok {
if !ex.IsEmpty() {
e.errors = append(e.errors, ex.errors...)
}
} else if ex, ok := err.(*Error); ok && ex != nil {
e.errors = append(e.errors, ex)
} else {
e.errors = append(e.errors, &Error{error: err})
}
}
// First 返回第一个错误实例,如果不存在则返回 nil
func (e *Errors) First() *Error {
if e.IsEmpty() {
return nil
}
return e.errors[0]
}
// Get 获取指定标签的错误列表,如果不存在将返回 nil
func (e *Errors) Get(field string) []*Error {
if e.IsEmpty() {
return nil
}
errs := make([]*Error, 0)
for _, err := range e.errors {
if err.field == field {
errs = append(errs, err)
}
}
if len(errs) > 0 {
return errs
}
return nil
}
func (e *Errors) All() []*Error {
if e == nil {
return emptyErrors
}
return e.errors
}
// ToMap 根据错误标签分组
func (e *Errors) ToMap() map[string][]*Error {
if e == nil || e.IsEmpty() {
return nil
}
errs := map[string][]*Error{}
for _, err := range e.errors {
if _, ok := errs[err.field]; !ok {
errs[err.field] = []*Error{}
}
errs[err.field] = append(errs[err.field], err)
}
return errs
}
func (e *Errors) String() string {
errsMap := e.ToMap()
if errsMap == nil {
return ""
}
var errors []string
for _, errs := range errsMap {
buf := strings.Builder{}
for i, err := range errs {
if i > 0 {
buf.WriteString("\n")
}
buf.WriteString(err.String())
}
errors = append(errors, buf.String())
}
return strings.Join(errors, "\n")
}
func (e *Errors) Error() string {
return e.String()
}
func isBuiltinError(err error) bool {
if _, ok := err.(*Error); ok {
return true
}
if _, ok := err.(*Errors); ok {
return true
}
return false
}

@ -0,0 +1,52 @@
package v
type Matcher struct {
field string
label string
value any
branches []branch
fallback func(valuer *Valuer) error
compare func(a, b any) bool
}
type branch struct {
value any
handle func(valuer *Valuer) error
}
func Match(value any, field, label string) *Matcher {
return &Matcher{
field: field,
label: label,
value: value,
branches: []branch{},
fallback: nil,
compare: func(a, b any) bool { return a == b },
}
}
func (m *Matcher) Branch(value any, handle func(valuer *Valuer) error) *Matcher {
m.branches = append(m.branches, branch{value: value, handle: handle})
return m
}
func (m *Matcher) Fallback(handle func(valuer *Valuer) error) *Matcher {
m.fallback = handle
return m
}
func (m *Matcher) Validate() error {
for _, b := range m.branches {
if m.compare(m.value, b.value) {
valuer := Value(m.value, m.field, m.label)
return b.handle(valuer)
}
}
if m.fallback != nil {
valuer := Value(m.value, m.field, m.label)
return m.fallback(valuer)
}
return nil
}

@ -0,0 +1,144 @@
package v
import (
"fmt"
"reflect"
)
// Translator 翻译函数签名
type Translator func(message string, params map[string]any) string
var (
// 默认翻译函数
defaultTranslator Translator
// 预置的翻译信息
translations = map[string]struct {
message string
trans Translator
}{
"required": {message: "{label}为必填字段"},
"required_if": {message: "{label}为必填字段"},
"typeof": {trans: typeof},
"is_email": {message: "{label}不是有效的电子邮箱地址"},
"is_e164": {message: "{label}不是有效的 e.164 手机号码"},
"is_phone_number": {message: "{label}不是有效的手机号码"},
"is_url": {message: "{label}不是有效的链接"},
"is_url_encoded": {message: "{label}不是有效的链接"},
"is_base64_url": {message: "{label}不是有效的BASE64链接"},
"is_semver": {message: "{label}不是有效的语义化版本号"},
"is_jwt": {message: "{label}不是有效的权限令牌"},
"is_uuid": {message: "{label}不是有效的UUID字符串"},
"is_uuid3": {message: "{label}不是有效的V3版UUID字符串"},
"is_uuid4": {message: "{label}不是有效的V4版UUID字符串"},
"is_uuid5": {message: "{label}不是有效的V5版UUID字符串"},
"is_ulid": {message: "{label}不是有效的ULID字符串"},
"is_md4": {message: ""},
"is_md5": {message: ""},
"is_sha256": {message: ""},
"is_sha384": {message: ""},
"is_sha512": {message: ""},
"is_ascii": {message: "{label}只能包含ASCII字符"},
"is_alpha": {message: "{label}只能包含字母"},
"is_alphanumeric": {message: "{label}只能包含字母和数字"},
"is_alpha_unicode": {message: "{label}只能包含字母和Unicode字符"},
"is_alphanumeric_unicode": {message: "{label}只能包含字母数字和Unicode字符"},
"is_numeric": {message: "{label}必须是一个有效的数值"},
"is_number": {message: "{label}必须是一个有效的数字"},
"is_bool": {message: "{label}必须是一个有效的布尔值"},
"is_hexadecimal": {message: "{label}必须是一个有效的十六进制"},
"is_hexcolor": {message: "{label}必须是一个有效的十六进制颜色"},
"is_rgb": {message: "{label}必须是一个有效的RGB颜色"},
"is_rgba": {message: "{label}必须是一个有效的RGBA颜色"},
"is_hsl": {message: "{label}必须是一个有效的RGB颜色"},
"is_hsla": {message: "{label}必须是一个有效的HSLA颜色"},
"is_color": {message: "{label}必须是一个有效的颜色"},
"is_latitude": {message: "{label}必须包含有效的纬度坐标"},
"is_longitude": {message: "{label}必须包含有效的经度坐标"},
"is_json": {message: "{label}必须是一个JSON字符串"},
"is_base64": {message: "{label}必须是一个有效的Base64字符串"},
"is_html": {message: "{label}必须是一个有效的网页内容"},
"is_html_encoded": {message: "{label}必须是一个被转义的网页内容"},
"is_datetime": {message: "{label}的格式必须是{layout}"},
"is_timezone": {message: "{label}必须是一个有效的时区"},
"is_ipv4": {message: "{label}必须是一个有效的IPv4地址"},
"is_ipv6": {message: "{label}必须是一个有效的IPv6地址"},
"is_ip": {message: "{label}必须是一个有效的IP地址"},
"is_mac": {message: "{label}必须是一个有效的MAC地址"},
"is_file": {message: "{label}必须是一个有效的文件"},
"is_dir": {message: "{label}必须是一个有效的目录"},
"is_lower": {message: "{label}必须是小写字母"},
"is_upper": {message: "{label}必须是大写字母"},
"contains": {message: "{label}必须包含文本'{substr}'"},
"contains_any": {message: "{label}必须包含至少一个以下字符'{chars}'"},
"contains_rune": {message: "{label}必须包含字符'{rune}'"},
"excludes": {message: "{label}不能包含文本'{substr}'"},
"excludes_all": {message: "{label}不能包含以下任何字符'{chars}'"},
"excludes_rune": {message: "{label}不能包含'{rune}'"},
"ends_with": {message: "{label}必须以文本'{suffix}'结尾"},
"ends_not_with": {message: "{label}不能以文本'{suffix}'结尾"},
"starts_with": {message: "{label}必须以文本'{prefix}'开头"},
"starts_not_with": {message: "{label}不能以文本'{prefix}'开头"},
"one_of": {message: "{label}必须是[{items}]中的一个"},
"not_empty": {message: "{label}不能为空"},
"length": {message: "{label}长度必须是{length}"},
"min_length": {message: "{label}最小长度为{min}"},
"max_length": {message: "max_length"},
"length_between": {message: "{label}长度必须大于或等于{min}且小于或等于{max}"},
"greater_than": {message: "{label}必须大于{min}"},
"greater_equal_than": {message: "{label}必须大于或等于{min}"},
"equal": {message: "{label}必须等于{another}"},
"not_equal": {message: "{label}不能等于{another}"},
"less_equal_than": {message: "{label}必须小于或等于{max}"},
"less_than": {message: "{label}必须小于{max}"},
"between": {message: "{label}必须大于或等于{min}且小于或等于{max}"},
"not_between": {message: "{label}必须小于{min}或大于{max}"},
"some": {message: "{label}至少有一个子项通过验证"},
"every": {message: "{label}的所有子项必须通过验证"},
"entity_exists": {message: "{label}不存在"},
"entity_not_exists": {message: "{label}已经存在"},
"index_by": {message: "参数不完整"},
}
types = map[reflect.Kind]string{
reflect.Bool: "布尔值",
reflect.Int: "整数",
reflect.Int8: "整数",
reflect.Int16: "整数",
reflect.Int32: "整数",
reflect.Int64: "整数",
reflect.Uint: "正整数",
reflect.Uint8: "正整数",
reflect.Uint16: "正整数",
reflect.Uint32: "正整数",
reflect.Uint64: "正整数",
reflect.Uintptr: "正整数",
reflect.Float32: "浮点数",
reflect.Float64: "浮点数",
reflect.Complex64: "复数",
reflect.Complex128: "复数",
reflect.Array: "数组",
reflect.Map: "字典(Mapper)",
reflect.Slice: "切片(Slice)",
reflect.String: "字符串",
reflect.Struct: "结构体",
}
)
func typeof(_ string, params map[string]any) string {
kind := params["kind"].(reflect.Kind)
if msg, ok := types[kind]; ok {
return fmt.Sprintf("%s不是有效的%s", params["label"], msg)
} else {
return fmt.Sprintf("%s格式验证失败", params["label"])
}
}
// SetDefaultTranslator 设置默认翻译函数
func SetDefaultTranslator(translator Translator) {
defaultTranslator = translator
}
// DefaultTranslator 返回默认翻译函数
func DefaultTranslator() Translator {
return defaultTranslator
}

@ -0,0 +1,112 @@
package v
import (
"errors"
"sorbet/pkg/is"
"strings"
)
// Validatable 验证功能接口
type Validatable interface {
Validate() error
}
// Checker 功能验证函数签名
type Checker func() error
// Validate 实现 Validatable 接口
func (c Checker) Validate() error {
return c()
}
// Wrap 将值和值的验证函数包装成验证器
func Wrap(value any, check func(any) error) Checker {
return func() error {
return check(value)
}
}
// Every 每一项都要验证通过
func Every(validators ...Validatable) Checker {
return func() error {
for _, validator := range validators {
if err := validator.Validate(); err != nil {
return err
}
}
return nil
}
}
// Some 任意一项验证通过即可
func Some(validators ...Validatable) Checker {
return func() error {
errs := &Errors{}
var hasOk bool
for _, validator := range validators {
err := validator.Validate()
if err != nil {
errs.Add(err)
} else {
hasOk = true
}
}
if hasOk || errs.IsEmpty() {
return nil
}
buf := strings.Builder{}
buf.WriteString("一下错误至少满足一项:\n")
for _, line := range strings.Split(errs.Error(), "\n") {
buf.WriteString(" " + line + "\n")
}
return errors.New(strings.TrimSpace(buf.String()))
}
}
// Validate 执行多个验证器
func Validate(validations ...Validatable) error {
var errs Errors
for _, validation := range validations {
if validation == nil {
continue
}
if err := validation.Validate(); err != nil {
errs.Add(err)
}
}
if errs.IsEmpty() {
return nil
}
return &errs
}
// IndexBy 分组验证,只要其中一组验证通过就返回
func IndexBy(index *int, values [][]any, options ...ErrorOption) Checker {
return func() error {
for i, items := range values {
count := 0
for _, item := range items {
if is.Empty(item) {
break
}
count++
}
if count > 0 && count == len(items) {
*index = i
return nil
}
}
return NewError("index_by", options...)
}
}
// Map 通过 map 构建值验证器
func Map(data map[string]any) func(name, label string) *Valuer {
return func(name, label string) *Valuer {
if val, ok := data[name]; ok {
return Value(val, name, label)
} else {
return Value(nil, name, label)
}
}
}

@ -0,0 +1,696 @@
package v
import (
"fmt"
"reflect"
"sorbet/pkg/is"
"strings"
)
// Ruler 规则验证函数签名
type Ruler func(any) error
// Valuer 基本值验证器
type Valuer struct {
field string // 字段名称,如:username
label string // 数据标签,对应字段名,如:用户名
value any // 参与验证的值
requires []Checker // 空值验证器列表
rules []Ruler // 参与验证的规则列表
}
// Value 创建一条验证器
func Value(value any, field, label string) *Valuer {
return &Valuer{
field: field,
label: label,
value: value,
requires: []Checker{},
rules: []Ruler{},
}
}
// Validate 实现验证器接口
func (v *Valuer) Validate() error {
if is.Empty(v.value) {
for _, require := range v.requires {
if err := require(); err != nil {
return err
}
}
return nil
}
// simple nil
value := v.value
if value == nil {
return nil
} else if reflect.TypeOf(value).Kind() == reflect.Ptr {
rv := reflect.ValueOf(value)
if rv.IsNil() {
return nil
} else {
value = rv.Elem().Interface()
}
}
// call rules
for _, rule := range v.rules {
if err := rule(value); err != nil {
return err
}
}
return nil
}
func (v *Valuer) mistake(err error, options ...ErrorOption) *Error {
if m, ok := err.(*Error); ok {
return m
}
e := &Error{error: err}
e.field = v.field
e.label = v.label
e.value = v.value
for _, option := range options {
option(e)
}
return e
}
func (v *Valuer) newError(code string, options []ErrorOption) *Error {
e := NewError(code, options...)
e.label = v.label
e.value = v.value
e.field = v.field
return e
}
func (v *Valuer) addRule(rule Ruler) *Valuer {
v.rules = append(v.rules, rule)
return v
}
func (v *Valuer) simple(code string, check func(any) bool, options []ErrorOption) *Valuer {
return v.addRule(func(val any) error {
if check(val) {
return nil
}
return v.newError(code, options)
})
}
func (v *Valuer) string(code string, check func(string) bool, options []ErrorOption) *Valuer {
return v.simple(code, func(a any) bool { return check(toString(a)) }, options)
}
func (v *Valuer) Custom(code string, check func(val any) any, options ...ErrorOption) *Valuer {
v.rules = append(v.rules, func(val any) error {
if res := check(val); res == false {
return v.newError(code, options) // 验证失败
} else if res == true || res == nil {
return nil // 验证成功
} else if err, ok := res.(error); ok {
if isBuiltinError(err) {
return err
}
m := v.newError(code, options)
m.error = err
//if str := err.Error(); m.format != "" && str != "" {
// m.format = fmt.Sprintf("%s(%s)", m.format, str)
//}
return m
} else {
panic("must return a bool, a nil or a error pointer")
}
})
return v
}
// Required 值是否必须(值不为空)
func (v *Valuer) Required(options ...ErrorOption) *Valuer {
v.requires = append(v.requires, func() error {
return v.newError("required", options)
})
return v
}
// RequiredIf 满足条件必须
func (v *Valuer) RequiredIf(condition bool, options ...ErrorOption) *Valuer {
v.requires = append(v.requires, func() error {
if condition {
return v.newError("required_if", options)
}
return nil
})
return v
}
// RequiredWith 依赖其它值判断是否必须
func (v *Valuer) RequiredWith(values []any, options ...ErrorOption) *Valuer {
v.requires = append(v.requires, func() error {
for _, value := range values {
if !is.Empty(value) {
return v.newError("required_with", options)
}
}
return nil
})
return v
}
func (v *Valuer) When(condition bool, then func(*Valuer)) *Valuer {
if condition && then != nil {
v.addRule(func(a any) error {
x := Value(v.value, v.field, v.label)
then(x)
return x.Validate()
})
}
return v
}
func (v *Valuer) Match(handle func(m *Matcher)) *Valuer {
return v.addRule(func(a any) error {
m := Match(v.value, v.field, v.label)
handle(m)
return m.Validate()
})
}
func (v *Valuer) Typeof(kind reflect.Kind, options ...ErrorOption) *Valuer {
return v.addRule(func(val any) error {
if reflect.TypeOf(val).Kind() != kind {
options = merge(options, ErrorParam("kind", kind))
return v.newError("typeof", options)
}
return nil
})
}
func (v *Valuer) IsString(options ...ErrorOption) *Valuer {
options = append(options, ErrorCode("is_string"))
return v.Typeof(reflect.String, options...)
}
func (v *Valuer) IsEmail(options ...ErrorOption) *Valuer {
return v.string("is_email", is.Email, options)
}
func (v *Valuer) IsE164(options ...ErrorOption) *Valuer {
return v.string("is_e164", is.E164, options)
}
func (v *Valuer) IsPhoneNumber(options ...ErrorOption) *Valuer {
return v.string("is_phone_number", is.PhoneNumber, options)
}
func (v *Valuer) IsURL(options ...ErrorOption) *Valuer {
return v.string("is_url", is.URL, options)
}
func (v *Valuer) IsURLEncoded(options ...ErrorOption) *Valuer {
return v.string("is_url_encoded", is.URLEncoded, options)
}
func (v *Valuer) IsBase64URL(options ...ErrorOption) *Valuer {
return v.string("is_base64_url", is.Base64URL, options)
}
func (v *Valuer) IsSemver(options ...ErrorOption) *Valuer {
return v.string("is_semver", is.Semver, options)
}
func (v *Valuer) IsJwt(options ...ErrorOption) *Valuer {
return v.string("is_jwt", is.JWT, options)
}
func (v *Valuer) IsUUID(options ...ErrorOption) *Valuer {
return v.string("is_uuid", is.UUID, options)
}
func (v *Valuer) IsUUID5(options ...ErrorOption) *Valuer {
return v.string("is_uuid5", is.UUID5, options)
}
func (v *Valuer) IsUUID4(options ...ErrorOption) *Valuer {
return v.string("is_uuid4", is.UUID4, options)
}
func (v *Valuer) IsUUID3(options ...ErrorOption) *Valuer {
return v.string("is_uuid3", is.UUID3, options)
}
func (v *Valuer) IsULID(options ...ErrorOption) *Valuer {
return v.string("is_ulid", is.ULID, options)
}
func (v *Valuer) IsMD4(options ...ErrorOption) *Valuer {
return v.string("is_md4", is.MD4, options)
}
func (v *Valuer) IsMD5(options ...ErrorOption) *Valuer {
return v.string("is_md5", is.MD5, options)
}
func (v *Valuer) IsSHA256(options ...ErrorOption) *Valuer {
return v.string("is_sha256", is.SHA256, options)
}
func (v *Valuer) IsSHA384(options ...ErrorOption) *Valuer {
return v.string("is_sha384", is.SHA384, options)
}
func (v *Valuer) IsSHA512(options ...ErrorOption) *Valuer {
return v.string("is_sha512", is.SHA512, options)
}
func (v *Valuer) IsAscii(options ...ErrorOption) *Valuer {
return v.string("is_ascii", is.ASCII, options)
}
func (v *Valuer) IsAlpha(options ...ErrorOption) *Valuer {
return v.string("is_alpha", is.Alpha, options)
}
func (v *Valuer) IsAlphanumeric(options ...ErrorOption) *Valuer {
return v.string("is_alphanumeric", is.Alphanumeric, options)
}
func (v *Valuer) IsAlphaUnicode(options ...ErrorOption) *Valuer {
return v.string("is_alpha_unicode", is.AlphaUnicode, options)
}
func (v *Valuer) IsAlphanumericUnicode(options ...ErrorOption) *Valuer {
return v.string("is_alphanumeric_unicode", is.AlphanumericUnicode, options)
}
func (v *Valuer) IsNumeric(options ...ErrorOption) *Valuer {
return v.simple("is_numeric", is.Numeric[any], options)
}
func (v *Valuer) IsNumber(options ...ErrorOption) *Valuer {
return v.simple("is_number", is.Number[any], options)
}
func (v *Valuer) IsBool(options ...ErrorOption) *Valuer {
return v.simple("is_bool", is.Boolean[any], options)
}
func (v *Valuer) IsHexadecimal(options ...ErrorOption) *Valuer {
return v.string("is_hexadecimal", is.Hexadecimal, options)
}
func (v *Valuer) IsHexColor(options ...ErrorOption) *Valuer {
return v.string("is_hexcolor", is.HEXColor, options)
}
func (v *Valuer) IsRgb(options ...ErrorOption) *Valuer {
return v.string("is_rgb", is.RGB, options)
}
func (v *Valuer) IsRgba(options ...ErrorOption) *Valuer {
return v.string("is_rgba", is.RGBA, options)
}
func (v *Valuer) IsHsl(options ...ErrorOption) *Valuer {
return v.string("is_hsl", is.HSL, options)
}
func (v *Valuer) IsHsla(options ...ErrorOption) *Valuer {
return v.string("is_hsla", is.HSLA, options)
}
func (v *Valuer) IsColor(options ...ErrorOption) *Valuer {
return v.string("is_color", is.Color, options)
}
func (v *Valuer) IsLatitude(options ...ErrorOption) *Valuer {
return v.simple("is_latitude", is.Latitude[any], options)
}
func (v *Valuer) IsLongitude(options ...ErrorOption) *Valuer {
return v.simple("is_longitude", is.Longitude[any], options)
}
func (v *Valuer) IsJson(options ...ErrorOption) *Valuer {
return v.simple("is_json", is.JSON[any], options)
}
func (v *Valuer) IsBase64(options ...ErrorOption) *Valuer {
return v.string("is_base64", is.Base64, options)
}
func (v *Valuer) IsHTML(options ...ErrorOption) *Valuer {
return v.string("is_html", is.HTML, options)
}
func (v *Valuer) IsHTMLEncoded(options ...ErrorOption) *Valuer {
return v.string("is_html_encoded", is.HTMLEncoded, options)
}
func (v *Valuer) IsDatetime(layout string, options ...ErrorOption) *Valuer {
return v.simple(
"is_datetime",
func(a any) bool { return is.Datetime(toString(a), layout) },
append([]ErrorOption{ErrorParam("layout", layout)}, options...),
)
}
func (v *Valuer) IsTimezone(options ...ErrorOption) *Valuer {
return v.string("is_timezone", is.Timezone, options)
}
func (v *Valuer) IsIPv4(options ...ErrorOption) *Valuer {
return v.string("is_ipv4", is.IPv4, options)
}
func (v *Valuer) IsIPv6(options ...ErrorOption) *Valuer {
return v.string("is_ipv6", is.IPv6, options)
}
func (v *Valuer) IsIP(options ...ErrorOption) *Valuer {
return v.string("is_ip", is.IP, options)
}
func (v *Valuer) IsMAC(options ...ErrorOption) *Valuer {
return v.string("is_mac", is.MAC, options)
}
func (v *Valuer) IsFile(options ...ErrorOption) *Valuer {
return v.simple("is_file", is.File, options)
}
func (v *Valuer) IsDir(options ...ErrorOption) *Valuer {
return v.simple("is_file", is.Dir, options)
}
func (v *Valuer) IsLower(options ...ErrorOption) *Valuer {
return v.string("is_lower", is.Lowercase, options)
}
func (v *Valuer) IsUpper(options ...ErrorOption) *Valuer {
return v.string("is_upper", is.Uppercase, options)
}
func (v *Valuer) Contains(substr string, options ...ErrorOption) *Valuer {
return v.simple(
"contains",
func(a any) bool { return strings.Contains(toString(a), substr) },
merge(options, ErrorParam("substr", substr)),
)
}
func (v *Valuer) ContainsAny(chars string, options ...ErrorOption) *Valuer {
return v.simple(
"contains_any",
func(a any) bool { return strings.ContainsAny(toString(a), chars) },
merge(options, ErrorParam("chars", chars)),
)
}
func (v *Valuer) ContainsRune(rune rune, options ...ErrorOption) *Valuer {
return v.simple(
"contains_rune",
func(a any) bool { return strings.ContainsRune(toString(a), rune) },
merge(options, ErrorParam("rune", rune)),
)
}
func (v *Valuer) Excludes(substr string, options ...ErrorOption) *Valuer {
return v.simple(
"excludes",
func(val any) bool { return !strings.Contains(toString(val), substr) },
merge(options, ErrorParam("substr", substr)),
)
}
func (v *Valuer) ExcludesAll(chars string, options ...ErrorOption) *Valuer {
return v.simple(
"excludes_all",
func(val any) bool { return !strings.ContainsAny(toString(val), chars) },
merge(options, ErrorParam("chars", chars)),
)
}
func (v *Valuer) ExcludesRune(rune rune, options ...ErrorOption) *Valuer {
return v.simple(
"excludes_rune",
func(val any) bool { return !strings.ContainsRune(toString(val), rune) },
merge(options, ErrorParam("rune", rune)),
)
}
func (v *Valuer) EndsWith(suffix string, options ...ErrorOption) *Valuer {
return v.simple(
"ends_with",
func(a any) bool { return strings.HasSuffix(toString(a), suffix) },
merge(options, ErrorParam("suffix", suffix)),
)
}
func (v *Valuer) EndsNotWith(suffix string, options ...ErrorOption) *Valuer {
return v.simple(
"ends_not_with",
func(val any) bool { return !strings.HasSuffix(toString(val), suffix) },
merge(options, ErrorParam("suffix", suffix)),
)
}
func (v *Valuer) StartsWith(prefix string, options ...ErrorOption) *Valuer {
return v.simple(
"starts_with",
func(a any) bool { return strings.HasPrefix(toString(a), prefix) },
merge(options, ErrorParam("prefix", prefix)),
)
}
func (v *Valuer) StartsNotWith(prefix string, options ...ErrorOption) *Valuer {
return v.simple(
"starts_not_with",
func(val any) bool { return !strings.HasPrefix(toString(val), prefix) },
merge(options, ErrorParam("prefix", prefix)),
)
}
func (v *Valuer) OneOf(items []any, options ...ErrorOption) *Valuer {
return v.simple(
"one_of",
func(value any) bool { return is.OneOf(value, items) },
merge(options, ErrorParam("items", items)),
)
}
func (v *Valuer) NotEmpty(options ...ErrorOption) *Valuer {
return v.simple("not_empty", is.NotEmpty[any], options)
}
func (v *Valuer) Length(n int, options ...ErrorOption) *Valuer {
return v.simple(
"length",
func(a any) bool { return is.Length(a, n, "=") },
merge(options, ErrorParam("length", n)),
)
}
func (v *Valuer) MinLength(min int, options ...ErrorOption) *Valuer {
return v.simple(
"min_length",
func(a any) bool { return is.Length(a, min, ">=") },
merge(options, ErrorParam("min", min)),
)
}
func (v *Valuer) MaxLength(max int, options ...ErrorOption) *Valuer {
return v.simple(
"max_length",
func(a any) bool { return is.Length(a, max, "<=") },
merge(options, ErrorParam("max", max)),
)
}
func (v *Valuer) LengthBetween(min, max int, options ...ErrorOption) *Valuer {
return v.simple(
"length_between",
func(a any) bool { return is.LengthBetween(a, min, max) },
merge(options, ErrorParam("min", min), ErrorParam("max", max)),
)
}
func (v *Valuer) GreaterThan(min any, options ...ErrorOption) *Valuer {
return v.simple(
"greater_than",
func(a any) bool { return is.GreaterThan(a, min) },
merge(options, ErrorParam("min", min)),
)
}
func (v *Valuer) GreaterEqualThan(n any, options ...ErrorOption) *Valuer {
return v.simple(
"greater_equal_than",
func(a any) bool { return is.GreaterEqualThan(a, n) },
merge(options, ErrorParam("min", n)),
)
}
func (v *Valuer) Equal(another any, options ...ErrorOption) *Valuer {
return v.simple(
"equal",
func(a any) bool { return is.Equal(a, another) },
merge(options, ErrorParam("another", another)),
)
}
func (v *Valuer) NotEqual(another any, options ...ErrorOption) *Valuer {
return v.simple(
"not_equal",
func(a any) bool { return is.NotEqual(a, another) },
merge(options, ErrorParam("another", another)),
)
}
func (v *Valuer) LessEqualThan(max any, options ...ErrorOption) *Valuer {
return v.simple(
"less_equal_than",
func(a any) bool { return is.LessEqualThan(a, max) },
merge(options, ErrorParam("max", max)),
)
}
func (v *Valuer) LessThan(max any, options ...ErrorOption) *Valuer {
return v.simple(
"less_than",
func(a any) bool { return is.LessThan(a, max) },
merge(options, ErrorParam("max", max)),
)
}
func (v *Valuer) Between(min, max any, options ...ErrorOption) *Valuer {
return v.simple(
"between",
func(a any) bool { return is.Between(a, min, max) },
merge(options, ErrorParam("min", min), ErrorParam("max", max)),
)
}
func (v *Valuer) NotBetween(min, max any, options ...ErrorOption) *Valuer {
return v.simple(
"not_between",
func(a any) bool { return is.NotBetween(a, min, max) },
merge(options, ErrorParam("min", min), ErrorParam("max", max)),
)
}
type Item struct {
Key any
Index int
Value any
}
func (v *Valuer) itemize(handle func(item *Item) any, every bool, options []ErrorOption) *Valuer {
check := func(item *Item) (bool, error) {
res := handle(item)
if x, ok := res.(Validatable); ok {
if err := x.Validate(); res != nil {
return false, err
} else {
return !every, nil
}
}
if res == true || res == nil {
if !every {
// some 成功
return true, nil
}
return false, nil
}
if res == false && every {
if every {
// every 失败
return false, v.newError("every", options)
}
return false, nil
}
if err, ok := res.(error); ok {
// 自定义错误
return false, v.mistake(err, options...)
}
panic(fmt.Errorf("expect a bool, a Validatable or a error, got %+v", res))
}
v.addRule(func(a any) error {
rv := reflect.Indirect(reflect.ValueOf(a))
rt := rv.Type()
switch k := rt.Kind(); k {
case reflect.Array, reflect.Slice:
for i := 0; i < rv.Len(); i++ {
skip, err := check(&Item{
Key: nil,
Index: i,
Value: rv.Index(i).Interface(),
})
if err != nil {
return v.mistake(err)
}
if skip {
break
}
}
case reflect.Map:
iter := rv.MapRange()
for iter.Next() {
skip, err := check(&Item{
Key: iter.Key().String(),
Value: iter.Value().Interface(),
})
if err != nil {
return v.mistake(err)
}
if skip {
break
}
}
case reflect.Struct:
for i := 0; i < rt.NumField(); i++ {
field := rt.Field(i)
skip, err := check(&Item{
Key: field.Name,
Value: rv.FieldByName(field.Name).Interface(),
})
if err != nil {
return v.mistake(err)
}
if skip {
break
}
}
default:
panic("invalid value")
}
if every {
return nil
}
return v.newError("some", options)
})
return v
}
func (v *Valuer) Every(handle func(item *Item) any, options ...ErrorOption) *Valuer {
return v.itemize(handle, true, options)
}
func (v *Valuer) Some(handle func(item *Item) any, options ...ErrorOption) *Valuer {
return v.itemize(handle, false, options)
}
func toString(val any) string {
if str, ok := val.(string); ok {
return str
}
return fmt.Sprintf("%v", val)
}
Loading…
Cancel
Save