go项目脚手架
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sorbet/pkg/v/valuer.go

696 lines
17 KiB

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)
}