parent
b93b1d8ec1
commit
45bc099bb6
@ -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…
Reference in new issue