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