Compare commits
7 Commits
973d88e77c
...
2aadccd5b8
Author | SHA1 | Date |
---|---|---|
熊二 | 2aadccd5b8 | 1 year ago |
熊二 | 69032be6b2 | 1 year ago |
熊二 | 45bc099bb6 | 1 year ago |
熊二 | b93b1d8ec1 | 1 year ago |
熊二 | 32a7f7d6fc | 1 year ago |
熊二 | bb44a78885 | 1 year ago |
熊二 | 34d8a15f06 | 1 year ago |
@ -1,27 +0,0 @@ |
|||||||
package repositories |
|
||||||
|
|
||||||
import ( |
|
||||||
"sorbet/pkg/ioc" |
|
||||||
) |
|
||||||
|
|
||||||
func Init() { |
|
||||||
ioc.MustFactory(NewCompanyRepository) |
|
||||||
ioc.MustFactory(NewCompanyDepartmentRepository) |
|
||||||
ioc.MustFactory(NewCompanyStaffRepository) |
|
||||||
ioc.MustFactory(NewConfigRepository) |
|
||||||
ioc.MustFactory(NewConfigGroupRepository) |
|
||||||
ioc.MustFactory(NewFeatureRepository) |
|
||||||
ioc.MustFactory(NewFeatureCategoryRepository) |
|
||||||
ioc.MustFactory(NewFeatureConfigRepository) |
|
||||||
ioc.MustFactory(NewFeatureContentRepository) |
|
||||||
ioc.MustFactory(NewFeatureContentChapterRepository) |
|
||||||
ioc.MustFactory(NewFeatureContentDetailRepository) |
|
||||||
ioc.MustFactory(NewResourceRepository) |
|
||||||
ioc.MustFactory(NewResourceCategoryRepository) |
|
||||||
ioc.MustFactory(NewSystemLogRepository) |
|
||||||
ioc.MustFactory(NewSystemMenuRepository) |
|
||||||
ioc.MustFactory(NewSystemPermissionRepository) |
|
||||||
ioc.MustFactory(NewSystemRoleRepository) |
|
||||||
ioc.MustFactory(NewSystemRolePowerRepository) |
|
||||||
ioc.MustFactory(NewSystemUserRepository) |
|
||||||
} |
|
@ -1,16 +0,0 @@ |
|||||||
package app |
|
||||||
|
|
||||||
import "github.com/labstack/echo/v4" |
|
||||||
|
|
||||||
type Applet interface { |
|
||||||
// Init 初始化服务
|
|
||||||
Init(ctx *Context) error |
|
||||||
// Start 启动服务
|
|
||||||
Start() error |
|
||||||
// Stop 停止服务
|
|
||||||
Stop() error |
|
||||||
} |
|
||||||
|
|
||||||
type Routable interface { |
|
||||||
InitRoutes(r *echo.Group) |
|
||||||
} |
|
@ -0,0 +1,20 @@ |
|||||||
|
package app |
||||||
|
|
||||||
|
import "github.com/labstack/echo/v4" |
||||||
|
|
||||||
|
type Service interface { |
||||||
|
// Name 服务名称
|
||||||
|
Name() string |
||||||
|
// Priority 优先级,用于启动和销毁的执行顺序
|
||||||
|
Priority() int |
||||||
|
// Init 初始化服务
|
||||||
|
Init(ctx *Context) error |
||||||
|
// Bootstrap 启动服务
|
||||||
|
Bootstrap() error |
||||||
|
// Destroy 销毁服务
|
||||||
|
Destroy() error |
||||||
|
} |
||||||
|
|
||||||
|
type Routable interface { |
||||||
|
InitRoutes(r *echo.Group) |
||||||
|
} |
@ -1,228 +0,0 @@ |
|||||||
package ioc |
|
||||||
|
|
||||||
import ( |
|
||||||
"errors" |
|
||||||
"fmt" |
|
||||||
"reflect" |
|
||||||
) |
|
||||||
|
|
||||||
type binding struct { |
|
||||||
name string |
|
||||||
typ reflect.Type |
|
||||||
resolver any |
|
||||||
shared bool |
|
||||||
} |
|
||||||
|
|
||||||
func (b *binding) make(c *Container) (reflect.Value, error) { |
|
||||||
if v, exists := c.instances[b.typ][b.name]; exists { |
|
||||||
return v, nil |
|
||||||
} |
|
||||||
val, err := c.Invoke(b.resolver) |
|
||||||
if err != nil { |
|
||||||
return reflect.Value{}, err |
|
||||||
} |
|
||||||
rv := val[0] |
|
||||||
if len(val) == 2 { |
|
||||||
err = val[1].Interface().(error) |
|
||||||
if err != nil { |
|
||||||
return reflect.Value{}, err |
|
||||||
} |
|
||||||
} |
|
||||||
if b.shared { |
|
||||||
if _, exists := c.instances[b.typ]; !exists { |
|
||||||
c.instances[b.typ] = make(map[string]reflect.Value) |
|
||||||
} |
|
||||||
c.instances[b.typ][b.name] = rv |
|
||||||
} |
|
||||||
return rv, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (b *binding) mustMake(c *Container) reflect.Value { |
|
||||||
val, err := b.make(c) |
|
||||||
if err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
return val |
|
||||||
} |
|
||||||
|
|
||||||
type Container struct { |
|
||||||
// 注册的工厂函数
|
|
||||||
factories map[reflect.Type]map[string]*binding |
|
||||||
// 注册的共享实例
|
|
||||||
instances map[reflect.Type]map[string]reflect.Value |
|
||||||
parent *Container |
|
||||||
} |
|
||||||
|
|
||||||
func New() *Container { |
|
||||||
return &Container{ |
|
||||||
factories: make(map[reflect.Type]map[string]*binding), |
|
||||||
instances: make(map[reflect.Type]map[string]reflect.Value), |
|
||||||
parent: nil, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Fork 分支
|
|
||||||
func (c *Container) Fork() *Container { |
|
||||||
ioc := New() |
|
||||||
ioc.parent = c |
|
||||||
return ioc |
|
||||||
} |
|
||||||
|
|
||||||
// Bind 绑定值到容器,有效类型:
|
|
||||||
// - 接口的具体实现值
|
|
||||||
// - 结构体的实例
|
|
||||||
// - 类型的值(尽量不要使用原始类型,而应该使用元素类型的变体)
|
|
||||||
func (c *Container) Bind(instance any) { |
|
||||||
c.NamedBind("", instance) |
|
||||||
} |
|
||||||
|
|
||||||
// NamedBind 绑定具名值到容器
|
|
||||||
func (c *Container) NamedBind(name string, instance any) { |
|
||||||
//typ := InterfaceOf(instance)
|
|
||||||
typ := reflect.TypeOf(instance) |
|
||||||
if _, ok := c.instances[typ]; !ok { |
|
||||||
c.instances[typ] = make(map[string]reflect.Value) |
|
||||||
} |
|
||||||
c.instances[typ][name] = reflect.ValueOf(instance) |
|
||||||
} |
|
||||||
|
|
||||||
// Factory 绑定工厂函数
|
|
||||||
func (c *Container) Factory(factory any, shared ...bool) error { |
|
||||||
return c.NamedFactory("", factory, shared...) |
|
||||||
} |
|
||||||
|
|
||||||
// NamedFactory 绑定具名工厂函数
|
|
||||||
func (c *Container) NamedFactory(name string, factory any, shared ...bool) error { |
|
||||||
reflectedFactory := reflect.TypeOf(factory) |
|
||||||
if reflectedFactory.Kind() != reflect.Func { |
|
||||||
return errors.New("container: the factory must be a function") |
|
||||||
} |
|
||||||
if returnCount := reflectedFactory.NumOut(); returnCount == 0 || returnCount > 2 { |
|
||||||
return errors.New("container: factory function signature is invalid - it must return abstract, or abstract and error") |
|
||||||
} |
|
||||||
// TODO(hupeh): 验证第二个参数必须是 error 接口
|
|
||||||
concreteType := reflectedFactory.Out(0) |
|
||||||
for i := 0; i < reflectedFactory.NumIn(); i++ { |
|
||||||
// 循环依赖
|
|
||||||
if reflectedFactory.In(i) == concreteType { |
|
||||||
return fmt.Errorf("container: factory function signature is invalid - depends on abstract it returns") |
|
||||||
} |
|
||||||
} |
|
||||||
if _, exists := c.factories[concreteType]; !exists { |
|
||||||
c.factories[concreteType] = make(map[string]*binding) |
|
||||||
} |
|
||||||
bd := &binding{ |
|
||||||
name: name, |
|
||||||
typ: concreteType, |
|
||||||
resolver: factory, |
|
||||||
shared: false, |
|
||||||
} |
|
||||||
for _, b := range shared { |
|
||||||
bd.shared = b |
|
||||||
} |
|
||||||
c.factories[concreteType][name] = bd |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Resolve 完成的注入
|
|
||||||
func (c *Container) Resolve(i any) error { |
|
||||||
v := reflect.ValueOf(i) |
|
||||||
for v.Kind() == reflect.Ptr { |
|
||||||
v = v.Elem() |
|
||||||
} |
|
||||||
if v.Kind() != reflect.Struct { |
|
||||||
return errors.New("must given a struct") |
|
||||||
} |
|
||||||
t := v.Type() |
|
||||||
for i := 0; i < v.NumField(); i++ { |
|
||||||
f := v.Field(i) |
|
||||||
structField := t.Field(i) |
|
||||||
inject, willInject := structField.Tag.Lookup("inject") |
|
||||||
if !f.CanSet() { |
|
||||||
if willInject { |
|
||||||
return fmt.Errorf("container: cannot make %v field", t.Field(i).Name) |
|
||||||
} |
|
||||||
continue |
|
||||||
} |
|
||||||
ft := f.Type() |
|
||||||
fv := c.NamedGet(inject, ft) |
|
||||||
if !fv.IsValid() { |
|
||||||
return fmt.Errorf("value not found for type %v", ft) |
|
||||||
} |
|
||||||
f.Set(fv) |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Get 获取指定类型的值
|
|
||||||
func (c *Container) Get(t reflect.Type) reflect.Value { |
|
||||||
return c.NamedGet("", t) |
|
||||||
} |
|
||||||
|
|
||||||
// NamedGet 通过注入的名称获取指定类型的值
|
|
||||||
func (c *Container) NamedGet(name string, t reflect.Type) reflect.Value { |
|
||||||
val, exists := c.instances[t][name] |
|
||||||
|
|
||||||
if exists && val.IsValid() { |
|
||||||
return val |
|
||||||
} |
|
||||||
|
|
||||||
if factory, exists := c.factories[t][name]; exists { |
|
||||||
val = factory.mustMake(c) |
|
||||||
} |
|
||||||
|
|
||||||
if val.IsValid() || t.Kind() != reflect.Interface { |
|
||||||
goto RESULT |
|
||||||
} |
|
||||||
|
|
||||||
// 使用共享值里面该接口的实现者
|
|
||||||
for k, v := range c.instances { |
|
||||||
if k.Implements(t) { |
|
||||||
for _, value := range v { |
|
||||||
if value.IsValid() { |
|
||||||
val = value |
|
||||||
goto RESULT |
|
||||||
} |
|
||||||
} |
|
||||||
break |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// 使用工厂函数里面该接口的实现者
|
|
||||||
for k, v := range c.factories { |
|
||||||
if k.Implements(t) { |
|
||||||
for _, bd := range v { |
|
||||||
if x := bd.mustMake(c); x.IsValid() { |
|
||||||
val = x |
|
||||||
goto RESULT |
|
||||||
} |
|
||||||
} |
|
||||||
break |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
RESULT: |
|
||||||
if !val.IsValid() && c.parent != nil { |
|
||||||
val = c.parent.NamedGet(name, t) |
|
||||||
} |
|
||||||
return val |
|
||||||
} |
|
||||||
|
|
||||||
// Invoke 执行函数
|
|
||||||
func (c *Container) Invoke(f any) ([]reflect.Value, error) { |
|
||||||
t := reflect.TypeOf(f) |
|
||||||
if t.Kind() != reflect.Func { |
|
||||||
return nil, errors.New("container: invalid function") |
|
||||||
} |
|
||||||
|
|
||||||
var in = make([]reflect.Value, t.NumIn()) //Panic if t is not kind of Func
|
|
||||||
for i := 0; i < t.NumIn(); i++ { |
|
||||||
argType := t.In(i) |
|
||||||
val := c.Get(argType) |
|
||||||
if !val.IsValid() { |
|
||||||
return nil, fmt.Errorf("value not found for type %v", argType) |
|
||||||
} |
|
||||||
in[i] = val |
|
||||||
} |
|
||||||
return reflect.ValueOf(f).Call(in), nil |
|
||||||
} |
|
@ -1,96 +0,0 @@ |
|||||||
package ioc |
|
||||||
|
|
||||||
import ( |
|
||||||
"errors" |
|
||||||
"reflect" |
|
||||||
) |
|
||||||
|
|
||||||
var ( |
|
||||||
ErrValueNotFound = errors.New("ioc: value not found") |
|
||||||
) |
|
||||||
|
|
||||||
var global = New() |
|
||||||
|
|
||||||
// Fork 分支
|
|
||||||
func Fork() *Container { |
|
||||||
return global.Fork() |
|
||||||
} |
|
||||||
|
|
||||||
// Bind 绑定值到容器,有效类型:
|
|
||||||
//
|
|
||||||
// - 接口的具体实现值
|
|
||||||
// - 结构体的实例
|
|
||||||
// - 类型的值(尽量不要使用原始类型,而应该使用元素类型的变体)
|
|
||||||
func Bind(instance any) { |
|
||||||
global.Bind(instance) |
|
||||||
} |
|
||||||
|
|
||||||
// NamedBind 绑定具名值到容器
|
|
||||||
func NamedBind(name string, instance any) { |
|
||||||
global.NamedBind(name, instance) |
|
||||||
} |
|
||||||
|
|
||||||
// Factory 绑定工厂函数
|
|
||||||
func Factory(factory any, shared ...bool) error { |
|
||||||
return global.Factory(factory, shared...) |
|
||||||
} |
|
||||||
|
|
||||||
func MustFactory(factory any, shared ...bool) { |
|
||||||
err := Factory(factory, shared...) |
|
||||||
if err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// NamedFactory 绑定具名工厂函数
|
|
||||||
func NamedFactory(name string, factory any, shared ...bool) error { |
|
||||||
return global.NamedFactory(name, factory, shared...) |
|
||||||
} |
|
||||||
|
|
||||||
func MustNamedFactory(name string, factory any, shared ...bool) { |
|
||||||
err := NamedFactory(name, factory, shared...) |
|
||||||
if err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Resolve 完成的注入
|
|
||||||
func Resolve(i any) error { |
|
||||||
return global.Resolve(i) |
|
||||||
} |
|
||||||
|
|
||||||
// Get 获取指定类型的值
|
|
||||||
func Get[T any]() (*T, error) { |
|
||||||
return NamedGet[T]("") |
|
||||||
} |
|
||||||
|
|
||||||
func MustGet[T any]() *T { |
|
||||||
return MustNamedGet[T]("") |
|
||||||
} |
|
||||||
|
|
||||||
// NamedGet 通过注入的名称获取指定类型的值
|
|
||||||
func NamedGet[T any](name string) (*T, error) { |
|
||||||
var abs T |
|
||||||
t := reflect.TypeOf(&abs) |
|
||||||
v := global.NamedGet(name, t) |
|
||||||
if !v.IsValid() { |
|
||||||
return nil, ErrValueNotFound |
|
||||||
} |
|
||||||
if x, ok := v.Interface().(*T); ok { |
|
||||||
return x, nil |
|
||||||
} |
|
||||||
return nil, ErrValueNotFound |
|
||||||
} |
|
||||||
|
|
||||||
func MustNamedGet[T any](name string) *T { |
|
||||||
v, err := NamedGet[T](name) |
|
||||||
if err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
return v |
|
||||||
} |
|
||||||
|
|
||||||
// Invoke 执行函数
|
|
||||||
func Invoke(f any) ([]reflect.Value, error) { |
|
||||||
return global.Invoke(f) |
|
||||||
} |
|
@ -1,19 +0,0 @@ |
|||||||
package ioc |
|
||||||
|
|
||||||
import "reflect" |
|
||||||
|
|
||||||
// InterfaceOf dereferences a pointer to an Interface type.
|
|
||||||
// It panics if value is not a pointer to an interface.
|
|
||||||
func InterfaceOf(value interface{}) reflect.Type { |
|
||||||
t := reflect.TypeOf(value) |
|
||||||
|
|
||||||
for t.Kind() == reflect.Ptr { |
|
||||||
t = t.Elem() |
|
||||||
} |
|
||||||
|
|
||||||
if t.Kind() != reflect.Interface { |
|
||||||
panic("the value is not a pointer to an interface. (*MyInterface)(nil)") |
|
||||||
} |
|
||||||
|
|
||||||
return t |
|
||||||
} |
|
@ -0,0 +1,607 @@ |
|||||||
|
package is |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"net" |
||||||
|
"net/url" |
||||||
|
"os" |
||||||
|
"reflect" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
// Email 验证给出的字符串是不是有效的邮箱地址
|
||||||
|
func Email(str string) bool { |
||||||
|
return emailRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// E164 判断给出的字符串是否符合 e.164 规范的手机号码
|
||||||
|
func E164(str string) bool { |
||||||
|
return e164Regex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// PhoneNumber 判断给出的字符串是否符合中国大陆规范的手机号码
|
||||||
|
func PhoneNumber(str string) bool { |
||||||
|
return phoneNumberRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// Semver 判断给出的字符串是否符合语义化版本号规范
|
||||||
|
func Semver(str string) bool { |
||||||
|
return semverRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// Base64 判断给出的字符串是否为base64数据
|
||||||
|
func Base64(str string) bool { |
||||||
|
return base64Regex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// URL 判断给出的字符串是否为有效的URL
|
||||||
|
func URL(s string) bool { |
||||||
|
var i int |
||||||
|
// checks needed as of Go 1.6 because of change https://github.com/golang/go/commit/617c93ce740c3c3cc28cdd1a0d712be183d0b328#diff-6c2d018290e298803c0c9419d8739885L195
|
||||||
|
// emulate browser and strip the '#' suffix prior to validation. see issue-#237
|
||||||
|
if i = strings.Index(s, "#"); i > -1 { |
||||||
|
s = s[:i] |
||||||
|
} |
||||||
|
if len(s) == 0 { |
||||||
|
return false |
||||||
|
} |
||||||
|
u, err := url.ParseRequestURI(s) |
||||||
|
if err != nil || u.Scheme == "" { |
||||||
|
return false |
||||||
|
} |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// Base64URL 判断给出的字符串是否为有效且安全的 base64URL
|
||||||
|
func Base64URL(str string) bool { |
||||||
|
return base64URLRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// JWT is the validation function for validating if the current field's value is a valid JWT string.
|
||||||
|
func JWT(str string) bool { |
||||||
|
return jWTRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// UUID5 is the validation function for validating if the field's value is a valid v5 UUID.
|
||||||
|
func UUID5(str string) bool { |
||||||
|
return uUID5Regex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// UUID4 is the validation function for validating if the field's value is a valid v4 UUID.
|
||||||
|
func UUID4(str string) bool { |
||||||
|
return uUID4Regex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// UUID3 is the validation function for validating if the field's value is a valid v3 UUID.
|
||||||
|
func UUID3(str string) bool { |
||||||
|
return uUID3Regex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// UUID is the validation function for validating if the field's value is a valid UUID of any version.
|
||||||
|
func UUID(str string) bool { |
||||||
|
return uUIDRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// ULID is the validation function for validating if the field's value is a valid ULID.
|
||||||
|
func ULID(str string) bool { |
||||||
|
return uLIDRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// MD4 is the validation function for validating if the field's value is a valid MD4.
|
||||||
|
func MD4(str string) bool { |
||||||
|
return md4Regex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// MD5 is the validation function for validating if the field's value is a valid MD5.
|
||||||
|
func MD5(str string) bool { |
||||||
|
return md5Regex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// SHA256 is the validation function for validating if the field's value is a valid SHA256.
|
||||||
|
func SHA256(str string) bool { |
||||||
|
return sha256Regex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// SHA384 is the validation function for validating if the field's value is a valid SHA384.
|
||||||
|
func SHA384(str string) bool { |
||||||
|
return sha384Regex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// SHA512 is the validation function for validating if the field's value is a valid SHA512.
|
||||||
|
func SHA512(str string) bool { |
||||||
|
return sha512Regex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// ASCII is the validation function for validating if the field's value is a valid ASCII character.
|
||||||
|
func ASCII(str string) bool { |
||||||
|
return aSCIIRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// Alpha is the validation function for validating if the current field's value is a valid alpha value.
|
||||||
|
func Alpha(str string) bool { |
||||||
|
return alphaRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// Alphanumeric is the validation function for validating if the current field's value is a valid alphanumeric value.
|
||||||
|
func Alphanumeric(str string) bool { |
||||||
|
return alphaNumericRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// AlphaUnicode is the validation function for validating if the current field's value is a valid alpha unicode value.
|
||||||
|
func AlphaUnicode(str string) bool { |
||||||
|
return alphaUnicodeRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// AlphanumericUnicode is the validation function for validating if the current field's value is a valid alphanumeric unicode value.
|
||||||
|
func AlphanumericUnicode(str string) bool { |
||||||
|
return alphaUnicodeNumericRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// Numeric is the validation function for validating if the current field's value is a valid numeric value.
|
||||||
|
func Numeric[T any](t T) bool { |
||||||
|
ctx := reflect.ValueOf(t) |
||||||
|
switch ctx.Kind() { |
||||||
|
case 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: |
||||||
|
return true |
||||||
|
default: |
||||||
|
return numericRegex.MatchString(ctx.String()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Number is the validation function for validating if the current field's value is a valid number.
|
||||||
|
func Number[T any](t T) bool { |
||||||
|
rv := reflect.ValueOf(t) |
||||||
|
switch rv.Kind() { |
||||||
|
case 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: |
||||||
|
return true |
||||||
|
default: |
||||||
|
return numberRegex.MatchString(rv.String()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Boolean is the validation function for validating if the current field's value can be safely converted to a boolean.
|
||||||
|
func Boolean[T any](t T) bool { |
||||||
|
ref := reflect.ValueOf(t) |
||||||
|
switch ref.Kind() { |
||||||
|
case reflect.String: |
||||||
|
switch ref.String() { |
||||||
|
case "1", "yes", "YES", "Yes", "on", "ON", "On", "true", "TRUE", "True", |
||||||
|
"0", "no", "NO", "No", "", "off", "OFF", "Off", "false", "FALSE", "False": |
||||||
|
return true |
||||||
|
default: |
||||||
|
return false |
||||||
|
} |
||||||
|
case reflect.Int, reflect.Int32, reflect.Int64: |
||||||
|
n := ref.Int() |
||||||
|
return n == 0 || n == 1 |
||||||
|
case reflect.Uint, reflect.Uint32, reflect.Uint64: |
||||||
|
n := ref.Uint() |
||||||
|
return n == 0 || n == 1 |
||||||
|
case reflect.Bool: |
||||||
|
return ref.Bool() |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// Default is the opposite of required aka HasValue
|
||||||
|
func Default(val any) bool { |
||||||
|
return !HasValue(val) |
||||||
|
} |
||||||
|
|
||||||
|
// HasValue is the validation function for validating if the current field's value is not the default static value.
|
||||||
|
func HasValue(val any) bool { |
||||||
|
rv := reflect.ValueOf(val) |
||||||
|
switch rv.Kind() { |
||||||
|
case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: |
||||||
|
return !rv.IsNil() |
||||||
|
default: |
||||||
|
return rv.IsValid() && rv.Interface() != reflect.Zero(rv.Type()).Interface() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Hexadecimal is the validation function for validating if the current field's value is a valid hexadecimal.
|
||||||
|
func Hexadecimal(str string) bool { |
||||||
|
return hexadecimalRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// HEXColor is the validation function for validating if the current field's value is a valid HEX color.
|
||||||
|
func HEXColor(str string) bool { |
||||||
|
return hexColorRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// RGB is the validation function for validating if the current field's value is a valid RGB color.
|
||||||
|
func RGB(str string) bool { |
||||||
|
return rgbRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// RGBA is the validation function for validating if the current field's value is a valid RGBA color.
|
||||||
|
func RGBA(str string) bool { |
||||||
|
return rgbaRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// HSL is the validation function for validating if the current field's value is a valid HSL color.
|
||||||
|
func HSL(str string) bool { |
||||||
|
return hslRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// HSLA is the validation function for validating if the current field's value is a valid HSLA color.
|
||||||
|
func HSLA(str string) bool { |
||||||
|
return hslaRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
func Color(str string) bool { |
||||||
|
return HEXColor(str) || HSLA(str) || HSL(str) || RGB(str) || RGBA(str) |
||||||
|
} |
||||||
|
|
||||||
|
// Latitude is the validation function for validating if the field's value is a valid latitude coordinate.
|
||||||
|
func Latitude[T any](t T) bool { |
||||||
|
ref := reflect.ValueOf(t) |
||||||
|
var v string |
||||||
|
switch ref.Kind() { |
||||||
|
case reflect.String: |
||||||
|
v = ref.String() |
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
||||||
|
v = strconv.FormatInt(ref.Int(), 10) |
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
||||||
|
v = strconv.FormatUint(ref.Uint(), 10) |
||||||
|
case reflect.Float32: |
||||||
|
v = strconv.FormatFloat(ref.Float(), 'f', -1, 32) |
||||||
|
case reflect.Float64: |
||||||
|
v = strconv.FormatFloat(ref.Float(), 'f', -1, 64) |
||||||
|
default: |
||||||
|
//fmt.Errorf("bad ref type %T", ref.Interface())
|
||||||
|
return false |
||||||
|
} |
||||||
|
return latitudeRegex.MatchString(v) |
||||||
|
} |
||||||
|
|
||||||
|
// Longitude is the validation function for validating if the field's value is a valid longitude coordinate.
|
||||||
|
func Longitude[T any](t T) bool { |
||||||
|
ref := reflect.ValueOf(t) |
||||||
|
var v string |
||||||
|
switch ref.Kind() { |
||||||
|
case reflect.String: |
||||||
|
v = ref.String() |
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
||||||
|
v = strconv.FormatInt(ref.Int(), 10) |
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
||||||
|
v = strconv.FormatUint(ref.Uint(), 10) |
||||||
|
case reflect.Float32: |
||||||
|
v = strconv.FormatFloat(ref.Float(), 'f', -1, 32) |
||||||
|
case reflect.Float64: |
||||||
|
v = strconv.FormatFloat(ref.Float(), 'f', -1, 64) |
||||||
|
default: |
||||||
|
//fmt.Errorf("bad field type %T", ref.Interface())
|
||||||
|
return false |
||||||
|
} |
||||||
|
return longitudeRegex.MatchString(v) |
||||||
|
} |
||||||
|
|
||||||
|
// JSON is the validation function for validating if the current field's value is a valid json string.
|
||||||
|
func JSON[T any](t T) bool { |
||||||
|
rv := reflect.ValueOf(t) |
||||||
|
if rv.Type() == nilType { |
||||||
|
return json.Valid(rv.Bytes()) |
||||||
|
} |
||||||
|
if rv.Kind() == reflect.String { |
||||||
|
return json.Valid([]byte(rv.String())) |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func Datetime(str, layout string) bool { |
||||||
|
_, err := time.Parse(layout, str) |
||||||
|
return err == nil |
||||||
|
} |
||||||
|
|
||||||
|
// Timezone is the validation function for validating if the current field's value is a valid time zone string.
|
||||||
|
func Timezone(str string) bool { |
||||||
|
// empty value is converted to UTC by time.LoadLocation but disallow it as it is not a valid time zone name
|
||||||
|
if str == "" { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// Local value is converted to the current system time zone by time.LoadLocation but disallow it as it is not a valid time zone name
|
||||||
|
if strings.ToLower(str) == "local" { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
_, err := time.LoadLocation(str) |
||||||
|
return err == nil |
||||||
|
} |
||||||
|
|
||||||
|
// IPv4 is the validation function for validating if a value is a valid v4 IP address.
|
||||||
|
func IPv4(str string) bool { |
||||||
|
ip := net.ParseIP(str) |
||||||
|
return ip != nil && ip.To4() != nil |
||||||
|
} |
||||||
|
|
||||||
|
// IPv6 is the validation function for validating if the field's value is a valid v6 IP address.
|
||||||
|
func IPv6(str string) bool { |
||||||
|
ip := net.ParseIP(str) |
||||||
|
return ip != nil && ip.To4() == nil |
||||||
|
} |
||||||
|
|
||||||
|
// IP is the validation function for validating if the field's value is a valid v4 or v6 IP address.
|
||||||
|
func IP(str string) bool { |
||||||
|
ip := net.ParseIP(str) |
||||||
|
return ip != nil |
||||||
|
} |
||||||
|
|
||||||
|
// MAC is the validation function for validating if the field's value is a valid MAC address.
|
||||||
|
func MAC(str string) bool { |
||||||
|
_, err := net.ParseMAC(str) |
||||||
|
return err == nil |
||||||
|
} |
||||||
|
|
||||||
|
// Lowercase is the validation function for validating if the current field's value is a lowercase string.
|
||||||
|
func Lowercase(str string) bool { |
||||||
|
if str == "" { |
||||||
|
return false |
||||||
|
} |
||||||
|
return str == strings.ToLower(str) |
||||||
|
} |
||||||
|
|
||||||
|
// Uppercase is the validation function for validating if the current field's value is an uppercase string.
|
||||||
|
func Uppercase(str string) bool { |
||||||
|
if str == "" { |
||||||
|
return false |
||||||
|
} |
||||||
|
return str == strings.ToUpper(str) |
||||||
|
} |
||||||
|
|
||||||
|
// Empty checks if a value is empty or not.
|
||||||
|
// A value is considered empty if
|
||||||
|
// - integer, float: zero
|
||||||
|
// - bool: false
|
||||||
|
// - string, array: len() == 0
|
||||||
|
// - slice, map: nil or len() == 0
|
||||||
|
// - interface, pointer: nil or the referenced value is empty
|
||||||
|
func Empty[T any](t T) bool { |
||||||
|
rv := reflect.ValueOf(t) |
||||||
|
switch rv.Kind() { |
||||||
|
case reflect.String, reflect.Array, reflect.Map, reflect.Slice: |
||||||
|
return rv.Len() == 0 |
||||||
|
case reflect.Bool: |
||||||
|
return !rv.Bool() |
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
||||||
|
return rv.Int() == 0 |
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
||||||
|
return rv.Uint() == 0 |
||||||
|
case reflect.Float32, reflect.Float64: |
||||||
|
return rv.Float() == 0 |
||||||
|
case reflect.Invalid: |
||||||
|
return true |
||||||
|
case reflect.Interface, reflect.Ptr: |
||||||
|
if rv.IsNil() { |
||||||
|
return true |
||||||
|
} |
||||||
|
return Empty(rv.Elem().Interface()) |
||||||
|
case reflect.Struct: |
||||||
|
v, ok := rv.Interface().(time.Time) |
||||||
|
if ok && v.IsZero() { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func NotEmpty[T any](t T) bool { |
||||||
|
return !Empty(t) |
||||||
|
} |
||||||
|
|
||||||
|
func URLEncoded(str string) bool { |
||||||
|
return uRLEncodedRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
func HTMLEncoded(str string) bool { |
||||||
|
return hTMLEncodedRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
func HTML(str string) bool { |
||||||
|
return hTMLRegex.MatchString(str) |
||||||
|
} |
||||||
|
|
||||||
|
// File is the validation function for validating if the current field's value is a valid file path.
|
||||||
|
func File(val any) bool { |
||||||
|
field := reflect.ValueOf(val) |
||||||
|
switch field.Kind() { |
||||||
|
case reflect.String: |
||||||
|
fileInfo, err := os.Stat(field.String()) |
||||||
|
if err != nil { |
||||||
|
return false |
||||||
|
} |
||||||
|
return !fileInfo.IsDir() |
||||||
|
} |
||||||
|
//fmt.Errorf("bad value type %T", field.Interface())
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// Dir is the validation function for validating if the current field's value is a valid directory.
|
||||||
|
func Dir(val any) bool { |
||||||
|
field := reflect.ValueOf(val) |
||||||
|
if field.Kind() == reflect.String { |
||||||
|
fileInfo, err := os.Stat(field.String()) |
||||||
|
if err != nil { |
||||||
|
return false |
||||||
|
} |
||||||
|
return fileInfo.IsDir() |
||||||
|
} |
||||||
|
//fmt.Errorf("bad field type %T", field.Interface())
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func OneOf(val any, vals []any) bool { |
||||||
|
//rv := reflect.ValueOf(val)
|
||||||
|
//
|
||||||
|
//var v string
|
||||||
|
//switch rv.Kind() {
|
||||||
|
//case reflect.String:
|
||||||
|
// v = rv.String()
|
||||||
|
//case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
// v = strconv.FormatInt(rv.Int(), 10)
|
||||||
|
//case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
// v = strconv.FormatUint(rv.Uint(), 10)
|
||||||
|
//default:
|
||||||
|
// //fmt.Errorf("Bad rv type %T", rv.Interface())
|
||||||
|
// return false
|
||||||
|
//}
|
||||||
|
//for i := 0; i < len(vals); i++ {
|
||||||
|
// if vals[i] == v {
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
if len(vals) == 0 { |
||||||
|
return false |
||||||
|
} |
||||||
|
for _, a := range vals { |
||||||
|
if Equal(val, a) { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func Length(val any, length int, op string) bool { |
||||||
|
if n := calcLength(val); n == -1 { |
||||||
|
return false |
||||||
|
} else { |
||||||
|
return Compare(n, length, op) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func LengthBetween(val any, min, max int) bool { |
||||||
|
if !Compare(min, max, "<") { |
||||||
|
panic(ErrBadType) |
||||||
|
} else if n, err := getLength(val, false); err != nil { |
||||||
|
return false |
||||||
|
} else { |
||||||
|
return Compare(n, min, ">=") && Compare(n, max, "<=") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Compare intX,floatX value by given op. returns `srcVal op(=,!=,<,<=,>,>=) dstVal`
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
//
|
||||||
|
// compare(2, 3, ">") // false
|
||||||
|
// compare(2, 1.3, ">") // true
|
||||||
|
// compare(2.2, 1.3, ">") // true
|
||||||
|
// compare(2.1, 2, ">") // true
|
||||||
|
func Compare(srcVal, dstVal any, op string) bool { |
||||||
|
srv := reflect.ValueOf(srcVal) |
||||||
|
|
||||||
|
switch srv.Kind() { |
||||||
|
case reflect.Struct: |
||||||
|
if srv.Type().ConvertibleTo(timeType) { |
||||||
|
drv := reflect.ValueOf(dstVal) |
||||||
|
if drv.Type().ConvertibleTo(timeType) { |
||||||
|
at := srv.Convert(timeType).Interface().(time.Time) |
||||||
|
bt := drv.Convert(timeType).Interface().(time.Time) |
||||||
|
return compTime(at, bt, op) |
||||||
|
} |
||||||
|
} |
||||||
|
case reflect.Bool: |
||||||
|
drv := reflect.ValueOf(dstVal) |
||||||
|
switch drv.Kind() { |
||||||
|
case reflect.Bool: |
||||||
|
return compBool(srv.Bool(), drv.Bool(), op) |
||||||
|
case reflect.String: |
||||||
|
if bl, err := strconv.ParseBool(drv.String()); err == nil { |
||||||
|
return compBool(srv.Bool(), bl, op) |
||||||
|
} |
||||||
|
} |
||||||
|
default: |
||||||
|
if srcStr, ok := srcVal.(string); ok { |
||||||
|
if dstStr, ok2 := dstVal.(string); ok2 { |
||||||
|
return compString(srcStr, dstStr, op) |
||||||
|
} |
||||||
|
break |
||||||
|
} |
||||||
|
|
||||||
|
// float
|
||||||
|
if srcFlt, ok := srcVal.(float64); ok { |
||||||
|
if dstFlt, err := toFloat(dstVal); err == nil { |
||||||
|
return compNum(srcFlt, dstFlt, op) |
||||||
|
} |
||||||
|
break |
||||||
|
} |
||||||
|
|
||||||
|
if srcFlt, ok := srcVal.(float32); ok { |
||||||
|
if dstFlt, err := toFloat(dstVal); err == nil { |
||||||
|
return compNum(float64(srcFlt), dstFlt, op) |
||||||
|
} |
||||||
|
break |
||||||
|
} |
||||||
|
|
||||||
|
// as int64
|
||||||
|
if srcInt, err := toInt64(srcVal); err != nil { |
||||||
|
break |
||||||
|
} else if dstInt, ex := toInt64(dstVal); ex != nil { |
||||||
|
break |
||||||
|
} else { |
||||||
|
return compNum(srcInt, dstInt, op) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
switch op { |
||||||
|
case "=": |
||||||
|
return srcVal == dstVal |
||||||
|
case "!=": |
||||||
|
return srcVal != dstVal |
||||||
|
default: |
||||||
|
//ErrBadType
|
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// GreaterThan is the validation function for validating if the current field's value is greater than the param's value.
|
||||||
|
func GreaterThan(a, b any) bool { |
||||||
|
return Compare(a, b, ">") |
||||||
|
} |
||||||
|
|
||||||
|
// GreaterEqualThan is the validation function for validating if the current field's value is greater than or equal to the param's value.
|
||||||
|
func GreaterEqualThan(a, b any) bool { |
||||||
|
return Compare(a, b, ">=") |
||||||
|
} |
||||||
|
|
||||||
|
// LessThan is the validation function for validating if the current field's value is less than the param's value.
|
||||||
|
func LessThan(a, b any) bool { |
||||||
|
return Compare(a, b, "<") |
||||||
|
} |
||||||
|
|
||||||
|
// LessEqualThan is the validation function for validating if the current field's value is less than or equal to the param's value.
|
||||||
|
func LessEqualThan(a, b any) bool { |
||||||
|
return Compare(a, b, "<=") |
||||||
|
} |
||||||
|
|
||||||
|
// Equal is the validation function for validating if the current field's value is equal to the param's value.
|
||||||
|
func Equal(a, b any) bool { |
||||||
|
return Compare(a, b, "=") |
||||||
|
} |
||||||
|
|
||||||
|
func NotEqual(a, b any) bool { |
||||||
|
return Compare(a, b, "!=") |
||||||
|
} |
||||||
|
|
||||||
|
func Between(val, min, max any) bool { |
||||||
|
if !Compare(min, max, ">") { |
||||||
|
panic(ErrBadRange) |
||||||
|
} |
||||||
|
|
||||||
|
return Compare(val, min, ">=") && Compare(val, max, "<=") |
||||||
|
} |
||||||
|
|
||||||
|
func NotBetween(val, min, max any) bool { |
||||||
|
if !Compare(min, max, ">") { |
||||||
|
panic(ErrBadRange) |
||||||
|
} |
||||||
|
|
||||||
|
return Compare(val, min, "<") || Compare(val, max, ">") |
||||||
|
} |
@ -0,0 +1,79 @@ |
|||||||
|
package is |
||||||
|
|
||||||
|
import "regexp" |
||||||
|
|
||||||
|
const ( |
||||||
|
alphaRegexString = "^[a-zA-Z]+$" |
||||||
|
alphaNumericRegexString = "^[a-zA-Z0-9]+$" |
||||||
|
alphaUnicodeRegexString = "^[\\p{L}]+$" |
||||||
|
alphaUnicodeNumericRegexString = "^[\\p{L}\\p{N}]+$" |
||||||
|
numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$" |
||||||
|
numberRegexString = "^[0-9]+$" |
||||||
|
hexadecimalRegexString = "^(0[xX])?[0-9a-fA-F]+$" |
||||||
|
hexColorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$" |
||||||
|
rgbRegexString = "^rgb\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*\\)$" |
||||||
|
rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" |
||||||
|
hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$" |
||||||
|
hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" |
||||||
|
emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" |
||||||
|
e164RegexString = "^\\+[1-9]?[0-9]{7,14}$" |
||||||
|
phoneNumberRegexString = "^(\\+?86)?1[0-9]{10}$" |
||||||
|
base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$" |
||||||
|
base64URLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=|[A-Za-z0-9-_]{4})$" |
||||||
|
uUID3RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$" |
||||||
|
uUID4RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" |
||||||
|
uUID5RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" |
||||||
|
uUIDRegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" |
||||||
|
uLIDRegexString = "^[A-HJKMNP-TV-Z0-9]{26}$" |
||||||
|
md4RegexString = "^[0-9a-f]{32}$" |
||||||
|
md5RegexString = "^[0-9a-f]{32}$" |
||||||
|
sha256RegexString = "^[0-9a-f]{64}$" |
||||||
|
sha384RegexString = "^[0-9a-f]{96}$" |
||||||
|
sha512RegexString = "^[0-9a-f]{128}$" |
||||||
|
aSCIIRegexString = "^[\x00-\x7F]*$" |
||||||
|
latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$" |
||||||
|
longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" |
||||||
|
uRLEncodedRegexString = `^(?:[^%]|%[0-9A-Fa-f]{2})*$` |
||||||
|
hTMLEncodedRegexString = `&#[x]?([0-9a-fA-F]{2})|(>)|(<)|(")|(&)+[;]?` |
||||||
|
hTMLRegexString = `<[/]?([a-zA-Z]+).*?>` |
||||||
|
jWTRegexString = "^[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]*$" |
||||||
|
semverRegexString = `^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$` // numbered capture groups https://semver.org/
|
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
alphaRegex = regexp.MustCompile(alphaRegexString) |
||||||
|
alphaNumericRegex = regexp.MustCompile(alphaNumericRegexString) |
||||||
|
alphaUnicodeRegex = regexp.MustCompile(alphaUnicodeRegexString) |
||||||
|
alphaUnicodeNumericRegex = regexp.MustCompile(alphaUnicodeNumericRegexString) |
||||||
|
numericRegex = regexp.MustCompile(numericRegexString) |
||||||
|
numberRegex = regexp.MustCompile(numberRegexString) |
||||||
|
hexadecimalRegex = regexp.MustCompile(hexadecimalRegexString) |
||||||
|
hexColorRegex = regexp.MustCompile(hexColorRegexString) |
||||||
|
rgbRegex = regexp.MustCompile(rgbRegexString) |
||||||
|
rgbaRegex = regexp.MustCompile(rgbaRegexString) |
||||||
|
hslRegex = regexp.MustCompile(hslRegexString) |
||||||
|
hslaRegex = regexp.MustCompile(hslaRegexString) |
||||||
|
e164Regex = regexp.MustCompile(e164RegexString) |
||||||
|
phoneNumberRegex = regexp.MustCompile(phoneNumberRegexString) |
||||||
|
emailRegex = regexp.MustCompile(emailRegexString) |
||||||
|
base64Regex = regexp.MustCompile(base64RegexString) |
||||||
|
base64URLRegex = regexp.MustCompile(base64URLRegexString) |
||||||
|
uUID3Regex = regexp.MustCompile(uUID3RegexString) |
||||||
|
uUID4Regex = regexp.MustCompile(uUID4RegexString) |
||||||
|
uUID5Regex = regexp.MustCompile(uUID5RegexString) |
||||||
|
uUIDRegex = regexp.MustCompile(uUIDRegexString) |
||||||
|
uLIDRegex = regexp.MustCompile(uLIDRegexString) |
||||||
|
md4Regex = regexp.MustCompile(md4RegexString) |
||||||
|
md5Regex = regexp.MustCompile(md5RegexString) |
||||||
|
sha256Regex = regexp.MustCompile(sha256RegexString) |
||||||
|
sha384Regex = regexp.MustCompile(sha384RegexString) |
||||||
|
sha512Regex = regexp.MustCompile(sha512RegexString) |
||||||
|
aSCIIRegex = regexp.MustCompile(aSCIIRegexString) |
||||||
|
latitudeRegex = regexp.MustCompile(latitudeRegexString) |
||||||
|
longitudeRegex = regexp.MustCompile(longitudeRegexString) |
||||||
|
uRLEncodedRegex = regexp.MustCompile(uRLEncodedRegexString) |
||||||
|
hTMLEncodedRegex = regexp.MustCompile(hTMLEncodedRegexString) |
||||||
|
hTMLRegex = regexp.MustCompile(hTMLRegexString) |
||||||
|
jWTRegex = regexp.MustCompile(jWTRegexString) |
||||||
|
semverRegex = regexp.MustCompile(semverRegexString) |
||||||
|
) |
@ -0,0 +1,211 @@ |
|||||||
|
package is |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"reflect" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
"unicode/utf8" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
ErrBadType = errors.New("bad value type") |
||||||
|
ErrBadRange = errors.New("bad value range") |
||||||
|
|
||||||
|
timeType = reflect.TypeOf(time.Time{}) |
||||||
|
nilType = reflect.TypeOf([]byte(nil)) |
||||||
|
) |
||||||
|
|
||||||
|
// convert value to float64, return error on failed
|
||||||
|
func toFloat(in any) (f64 float64, err error) { |
||||||
|
switch tVal := in.(type) { |
||||||
|
case nil: |
||||||
|
f64 = 0 |
||||||
|
case string: |
||||||
|
f64, err = strconv.ParseFloat(strings.TrimSpace(tVal), 64) |
||||||
|
case int: |
||||||
|
f64 = float64(tVal) |
||||||
|
case int8: |
||||||
|
f64 = float64(tVal) |
||||||
|
case int16: |
||||||
|
f64 = float64(tVal) |
||||||
|
case int32: |
||||||
|
f64 = float64(tVal) |
||||||
|
case int64: |
||||||
|
f64 = float64(tVal) |
||||||
|
case uint: |
||||||
|
f64 = float64(tVal) |
||||||
|
case uint8: |
||||||
|
f64 = float64(tVal) |
||||||
|
case uint16: |
||||||
|
f64 = float64(tVal) |
||||||
|
case uint32: |
||||||
|
f64 = float64(tVal) |
||||||
|
case uint64: |
||||||
|
f64 = float64(tVal) |
||||||
|
case float32: |
||||||
|
f64 = float64(tVal) |
||||||
|
case float64: |
||||||
|
f64 = tVal |
||||||
|
case time.Duration: |
||||||
|
f64 = float64(tVal) |
||||||
|
case json.Number: |
||||||
|
f64, err = tVal.Float64() |
||||||
|
default: |
||||||
|
err = ErrBadType |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// convert string to int64, return error on failed
|
||||||
|
func toInt64(in any) (i64 int64, err error) { |
||||||
|
switch tVal := in.(type) { |
||||||
|
case nil: |
||||||
|
i64 = 0 |
||||||
|
case string: |
||||||
|
i64, err = strconv.ParseInt(strings.TrimSpace(tVal), 10, 0) |
||||||
|
case int: |
||||||
|
i64 = int64(tVal) |
||||||
|
case int8: |
||||||
|
i64 = int64(tVal) |
||||||
|
case int16: |
||||||
|
i64 = int64(tVal) |
||||||
|
case int32: |
||||||
|
i64 = int64(tVal) |
||||||
|
case int64: |
||||||
|
i64 = tVal |
||||||
|
case uint: |
||||||
|
i64 = int64(tVal) |
||||||
|
case uint8: |
||||||
|
i64 = int64(tVal) |
||||||
|
case uint16: |
||||||
|
i64 = int64(tVal) |
||||||
|
case uint32: |
||||||
|
i64 = int64(tVal) |
||||||
|
case uint64: |
||||||
|
i64 = int64(tVal) |
||||||
|
case float32: |
||||||
|
i64 = int64(tVal) |
||||||
|
case float64: |
||||||
|
i64 = int64(tVal) |
||||||
|
case time.Duration: |
||||||
|
i64 = int64(tVal) |
||||||
|
case json.Number: |
||||||
|
i64, err = tVal.Int64() |
||||||
|
default: |
||||||
|
err = ErrBadType |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// compString compare string, returns the first op second
|
||||||
|
func compString(first, second, op string) bool { |
||||||
|
rs := strings.Compare(first, second) |
||||||
|
if rs < 0 { |
||||||
|
return op == "<" || op == "<=" |
||||||
|
} else if rs > 0 { |
||||||
|
return op == ">" || op == ">=" |
||||||
|
} else { |
||||||
|
return op == ">=" || op == "<=" || op == "=" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func compTime(first, dstTime time.Time, op string) (ok bool) { |
||||||
|
switch op { |
||||||
|
case "<": |
||||||
|
return first.Before(dstTime) |
||||||
|
case "<=": |
||||||
|
return first.Before(dstTime) || first.Equal(dstTime) |
||||||
|
case ">": |
||||||
|
return first.After(dstTime) |
||||||
|
case ">=": |
||||||
|
return first.After(dstTime) || first.Equal(dstTime) |
||||||
|
case "=": |
||||||
|
return first.Equal(dstTime) |
||||||
|
case "!=": |
||||||
|
return !first.Equal(dstTime) |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func compNum[T int64 | float64 | uint64](first, second T, op string) bool { |
||||||
|
switch op { |
||||||
|
case "<": |
||||||
|
return first < second |
||||||
|
case "<=": |
||||||
|
return first <= second |
||||||
|
case ">": |
||||||
|
return first > second |
||||||
|
case ">=": |
||||||
|
return first >= second |
||||||
|
case "=": |
||||||
|
return first == second |
||||||
|
case "!=": |
||||||
|
return first != second |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func compBool(first, second bool, op string) bool { |
||||||
|
return compNum(boo2int(first), boo2int(second), op) |
||||||
|
} |
||||||
|
|
||||||
|
func boo2int(a bool) int64 { |
||||||
|
if a { |
||||||
|
return 1 |
||||||
|
} else { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// get reflect value length
|
||||||
|
func calcLength(val any) int { |
||||||
|
v := reflect.Indirect(reflect.ValueOf(val)) |
||||||
|
|
||||||
|
// (u)int use width.
|
||||||
|
switch v.Kind() { |
||||||
|
case reflect.String: |
||||||
|
return len([]rune(v.String())) |
||||||
|
case reflect.Map, reflect.Array, reflect.Chan, reflect.Slice: |
||||||
|
return v.Len() |
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
||||||
|
return len(strconv.FormatInt(int64(v.Uint()), 10)) |
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
||||||
|
return len(strconv.FormatInt(v.Int(), 10)) |
||||||
|
case reflect.Float32, reflect.Float64: |
||||||
|
return len(fmt.Sprint(v.Interface())) |
||||||
|
} |
||||||
|
|
||||||
|
// cannot get length
|
||||||
|
return -1 |
||||||
|
} |
||||||
|
|
||||||
|
func getLength(a any, rune bool) (int, error) { |
||||||
|
field := reflect.ValueOf(a) |
||||||
|
|
||||||
|
//if !field.IsValid() || field.IsNil() {
|
||||||
|
// return 0, nil
|
||||||
|
//}
|
||||||
|
|
||||||
|
switch field.Kind() { |
||||||
|
case reflect.String: |
||||||
|
if rune { |
||||||
|
return utf8.RuneCountInString(field.String()), nil |
||||||
|
} |
||||||
|
return field.Len(), nil |
||||||
|
|
||||||
|
case reflect.Slice, reflect.Map, reflect.Array: |
||||||
|
return field.Len(), nil |
||||||
|
|
||||||
|
case reflect.Ptr: |
||||||
|
if field.Type().Elem().Kind() == reflect.Array { |
||||||
|
// 类型声明中的长度
|
||||||
|
return field.Type().Elem().Len(), nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return 0, ErrBadType |
||||||
|
} |
@ -1,171 +0,0 @@ |
|||||||
package rsp |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"sort" |
|
||||||
"strconv" |
|
||||||
"strings" |
|
||||||
) |
|
||||||
|
|
||||||
var errInvalidTypeSubtype = "accept: Invalid type '%s'." |
|
||||||
|
|
||||||
// headerAccept represents a parsed headerAccept(-Charset|-Encoding|-Language) header.
|
|
||||||
type headerAccept struct { |
|
||||||
Type, Subtype string |
|
||||||
Q float64 |
|
||||||
Extensions map[string]string |
|
||||||
} |
|
||||||
|
|
||||||
// AcceptSlice is a slice of headerAccept.
|
|
||||||
type acceptSlice []headerAccept |
|
||||||
|
|
||||||
func Accepts(header, expect string) bool { |
|
||||||
_, typeSubtype, err := parseMediaRange(expect) |
|
||||||
if err != nil { |
|
||||||
return false |
|
||||||
} |
|
||||||
return accepts(parse(header), typeSubtype[0], typeSubtype[1]) |
|
||||||
} |
|
||||||
|
|
||||||
func accepts(slice acceptSlice, typ, sub string) bool { |
|
||||||
for _, a := range slice { |
|
||||||
if a.Type != typ { |
|
||||||
continue |
|
||||||
} |
|
||||||
if a.Subtype == "*" || a.Subtype == sub { |
|
||||||
return true |
|
||||||
} |
|
||||||
} |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
// parses a HTTP headerAccept(-Charset|-Encoding|-Language) header and returns
|
|
||||||
// AcceptSlice, sorted in decreasing order of preference. If the header lists
|
|
||||||
// multiple types that have the same level of preference (same specificity of
|
|
||||||
// type and subtype, same qvalue, and same number of extensions), the type
|
|
||||||
// that was listed in the header first comes first in the returned value.
|
|
||||||
//
|
|
||||||
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14 for more information.
|
|
||||||
func parse(header string) acceptSlice { |
|
||||||
mediaRanges := strings.Split(header, ",") |
|
||||||
accepted := make(acceptSlice, 0, len(mediaRanges)) |
|
||||||
for _, mediaRange := range mediaRanges { |
|
||||||
rangeParams, typeSubtype, err := parseMediaRange(mediaRange) |
|
||||||
if err != nil { |
|
||||||
continue |
|
||||||
} |
|
||||||
|
|
||||||
accept := headerAccept{ |
|
||||||
Type: typeSubtype[0], |
|
||||||
Subtype: typeSubtype[1], |
|
||||||
Q: 1.0, |
|
||||||
Extensions: make(map[string]string), |
|
||||||
} |
|
||||||
|
|
||||||
// If there is only one rangeParams, we can stop here.
|
|
||||||
if len(rangeParams) == 1 { |
|
||||||
accepted = append(accepted, accept) |
|
||||||
continue |
|
||||||
} |
|
||||||
|
|
||||||
// Validate the rangeParams.
|
|
||||||
validParams := true |
|
||||||
for _, v := range rangeParams[1:] { |
|
||||||
nameVal := strings.SplitN(v, "=", 2) |
|
||||||
if len(nameVal) != 2 { |
|
||||||
validParams = false |
|
||||||
break |
|
||||||
} |
|
||||||
nameVal[1] = strings.TrimSpace(nameVal[1]) |
|
||||||
if name := strings.TrimSpace(nameVal[0]); name == "q" { |
|
||||||
qval, err := strconv.ParseFloat(nameVal[1], 64) |
|
||||||
if err != nil || qval < 0 { |
|
||||||
validParams = false |
|
||||||
break |
|
||||||
} |
|
||||||
if qval > 1.0 { |
|
||||||
qval = 1.0 |
|
||||||
} |
|
||||||
accept.Q = qval |
|
||||||
} else { |
|
||||||
accept.Extensions[name] = nameVal[1] |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if validParams { |
|
||||||
accepted = append(accepted, accept) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
sort.Sort(accepted) |
|
||||||
return accepted |
|
||||||
} |
|
||||||
|
|
||||||
// Len implements the Len() method of the Sort interface.
|
|
||||||
func (a acceptSlice) Len() int { |
|
||||||
return len(a) |
|
||||||
} |
|
||||||
|
|
||||||
// Less implements the Less() method of the Sort interface. Elements are
|
|
||||||
// sorted in order of decreasing preference.
|
|
||||||
func (a acceptSlice) Less(i, j int) bool { |
|
||||||
// Higher qvalues come first.
|
|
||||||
if a[i].Q > a[j].Q { |
|
||||||
return true |
|
||||||
} else if a[i].Q < a[j].Q { |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
// Specific types come before wildcard types.
|
|
||||||
if a[i].Type != "*" && a[j].Type == "*" { |
|
||||||
return true |
|
||||||
} else if a[i].Type == "*" && a[j].Type != "*" { |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
// Specific subtypes come before wildcard subtypes.
|
|
||||||
if a[i].Subtype != "*" && a[j].Subtype == "*" { |
|
||||||
return true |
|
||||||
} else if a[i].Subtype == "*" && a[j].Subtype != "*" { |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
// A lot of extensions comes before not a lot of extensions.
|
|
||||||
if len(a[i].Extensions) > len(a[j].Extensions) { |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
// Swap implements the Swap() method of the Sort interface.
|
|
||||||
func (a acceptSlice) Swap(i, j int) { |
|
||||||
a[i], a[j] = a[j], a[i] |
|
||||||
} |
|
||||||
|
|
||||||
// parseMediaRange parses the provided media range, and on success returns the
|
|
||||||
// parsed range params and type/subtype pair.
|
|
||||||
func parseMediaRange(mediaRange string) (rangeParams, typeSubtype []string, err error) { |
|
||||||
rangeParams = strings.Split(mediaRange, ";") |
|
||||||
typeSubtype = strings.Split(rangeParams[0], "/") |
|
||||||
|
|
||||||
// typeSubtype should have a length of exactly two.
|
|
||||||
if len(typeSubtype) > 2 { |
|
||||||
err = fmt.Errorf(errInvalidTypeSubtype, rangeParams[0]) |
|
||||||
return |
|
||||||
} else { |
|
||||||
typeSubtype = append(typeSubtype, "*") |
|
||||||
} |
|
||||||
|
|
||||||
// Sanitize typeSubtype.
|
|
||||||
typeSubtype[0] = strings.TrimSpace(typeSubtype[0]) |
|
||||||
typeSubtype[1] = strings.TrimSpace(typeSubtype[1]) |
|
||||||
if typeSubtype[0] == "" { |
|
||||||
typeSubtype[0] = "*" |
|
||||||
} |
|
||||||
if typeSubtype[1] == "" { |
|
||||||
typeSubtype[1] = "*" |
|
||||||
} |
|
||||||
|
|
||||||
return |
|
||||||
} |
|
@ -0,0 +1,14 @@ |
|||||||
|
package rsp |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/labstack/echo/v4" |
||||||
|
) |
||||||
|
|
||||||
|
// HTTPErrorHandler 实现 echo.HTTPErrorHandler 函数
|
||||||
|
func HTTPErrorHandler(err error, c echo.Context) { |
||||||
|
if c.Response().Committed { |
||||||
|
c.Logger().Error(err) |
||||||
|
} else if err = Fail(c, err); err != nil { |
||||||
|
c.Logger().Error(err) |
||||||
|
} |
||||||
|
} |
@ -1,83 +0,0 @@ |
|||||||
package rsp |
|
||||||
|
|
||||||
import ( |
|
||||||
"github.com/labstack/echo/v4" |
|
||||||
"net/http" |
|
||||||
) |
|
||||||
|
|
||||||
// GuardFunc 参数守卫函数前面
|
|
||||||
type GuardFunc[T any] func(c echo.Context, req *T) error |
|
||||||
|
|
||||||
// ServeFunc 参数处理函数签名
|
|
||||||
type ServeFunc[T any, R any] func(c echo.Context, req *T) (*R, error) |
|
||||||
|
|
||||||
// ServeWithDataFunc 参数处理函数签名,支持自定义数据
|
|
||||||
type ServeWithDataFunc[T any, R any, O any] func(c echo.Context, req *T, opt O) (*R, error) |
|
||||||
|
|
||||||
// RespondFunc 数据响应函数前面
|
|
||||||
type RespondFunc[R any] func(c echo.Context, res *R) error |
|
||||||
|
|
||||||
// Handle 通用 CRUD 函数构造器,具体参数与函数 HandleWithData 保持一致
|
|
||||||
func Handle[T any, R any](guard GuardFunc[T], serve ServeFunc[T, R], respond ...RespondFunc[R]) echo.HandlerFunc { |
|
||||||
return HandleWithData[T, R, any](guard, func(c echo.Context, req *T, opt any) (*R, error) { |
|
||||||
return serve(c, req) |
|
||||||
}, nil, respond...) |
|
||||||
} |
|
||||||
|
|
||||||
// HandleWithData 通用 CRUD 函数构造器,可以预置数据
|
|
||||||
//
|
|
||||||
// 参数 guard 可以为空值,表示无参数守卫;
|
|
||||||
// 参数 serve 必传;
|
|
||||||
// 参数 data 为自定义数据,该值最好不可被修改;
|
|
||||||
// 参数 respond 为自定义响应函数,若未指定,内部将使用 Ok 或 Created 来响应结果。
|
|
||||||
func HandleWithData[T any, R any, D any](guard GuardFunc[T], serve ServeWithDataFunc[T, R, D], data D, respond ...RespondFunc[R]) echo.HandlerFunc { |
|
||||||
if serve == nil { |
|
||||||
panic("miss ServeFunc") |
|
||||||
} |
|
||||||
return func(c echo.Context) error { |
|
||||||
var req T |
|
||||||
if err := c.Bind(&req); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
if err := c.Validate(&req); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
if guard != nil { |
|
||||||
err := guard(c, &req) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
res, err := serve(c, &req, data) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
for _, send := range respond { |
|
||||||
if send != nil { |
|
||||||
return send(c, res) |
|
||||||
} |
|
||||||
} |
|
||||||
// 我们认为凡是 PUT 请求,都是创建数据
|
|
||||||
// 所以这里使用 Created 函数来响应数据。
|
|
||||||
if c.Request().Method == http.MethodPut { |
|
||||||
return Created(c, res) |
|
||||||
} |
|
||||||
return Ok(c, res) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func Bind[T any](c echo.Context, guards ...GuardFunc[T]) (*T, error) { |
|
||||||
var req T |
|
||||||
if err := c.Bind(&req); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
if err := c.Validate(&req); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
for _, guard := range guards { |
|
||||||
if err := guard(c, &req); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
} |
|
||||||
return &req, nil |
|
||||||
} |
|
@ -0,0 +1,277 @@ |
|||||||
|
package rsp |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"github.com/labstack/echo/v4" |
||||||
|
"sort" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
type cache struct { |
||||||
|
hint int |
||||||
|
slice AcceptSlice |
||||||
|
} |
||||||
|
|
||||||
|
// Negotiator An HTTP content negotiator
|
||||||
|
//
|
||||||
|
// Accept: <MIME_type>/<MIME_subtype>
|
||||||
|
// Accept: <MIME_type>/*
|
||||||
|
// Accept: */*
|
||||||
|
// Accept: text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8
|
||||||
|
type Negotiator struct { |
||||||
|
capacity int // for cache
|
||||||
|
caches map[string]*cache |
||||||
|
} |
||||||
|
|
||||||
|
func NewNegotiator(capacity int) *Negotiator { |
||||||
|
if capacity <= 0 { |
||||||
|
capacity = 10 |
||||||
|
} |
||||||
|
return &Negotiator{ |
||||||
|
capacity: capacity, |
||||||
|
caches: make(map[string]*cache), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (n *Negotiator) Slice(header string) AcceptSlice { |
||||||
|
if c, ok := n.caches[header]; ok { |
||||||
|
c.hint++ |
||||||
|
return c.slice |
||||||
|
} |
||||||
|
if len(n.caches) >= n.capacity { |
||||||
|
var s string |
||||||
|
var hint int |
||||||
|
for i, x := range n.caches { |
||||||
|
if hint == 0 || hint < x.hint { |
||||||
|
hint = x.hint |
||||||
|
s = i |
||||||
|
} |
||||||
|
} |
||||||
|
delete(n.caches, s) |
||||||
|
} |
||||||
|
slice := newSlice(header) |
||||||
|
n.caches[header] = &cache{1, slice} |
||||||
|
return slice |
||||||
|
} |
||||||
|
|
||||||
|
func (n *Negotiator) Is(header string, expects ...string) bool { |
||||||
|
return n.Slice(header).Is(expects...) |
||||||
|
} |
||||||
|
|
||||||
|
func (n *Negotiator) Type(header string, expects ...string) string { |
||||||
|
return n.Slice(header).Type(expects...) |
||||||
|
} |
||||||
|
|
||||||
|
// Accept represents a parsed `Accept` header.
|
||||||
|
type Accept struct { |
||||||
|
Type, Subtype string |
||||||
|
Q float64 |
||||||
|
mime string |
||||||
|
} |
||||||
|
|
||||||
|
func (a *Accept) Mime() string { |
||||||
|
if a.mime == "" { |
||||||
|
a.mime = a.Type + "/" + a.Subtype |
||||||
|
} |
||||||
|
return a.mime |
||||||
|
} |
||||||
|
|
||||||
|
// AcceptSlice is a slice of accept.
|
||||||
|
type AcceptSlice []Accept |
||||||
|
|
||||||
|
// newSlice parses an HTTP Accept header and returns AcceptSlice, sorted in
|
||||||
|
// decreasing order of preference. If the header lists multiple types that
|
||||||
|
// have the same level of preference (same specificity of a type and subtype,
|
||||||
|
// same qvalue, and same number of extensions), the type that was listed
|
||||||
|
// in the header first comes in the returned value.
|
||||||
|
//
|
||||||
|
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14 for more information.
|
||||||
|
func newSlice(header string) AcceptSlice { |
||||||
|
mediaRanges := strings.Split(header, ",") |
||||||
|
accepted := make(AcceptSlice, 0, len(mediaRanges)) |
||||||
|
for _, mediaRange := range mediaRanges { |
||||||
|
rangeParams, typeSubtype, err := parseMediaRange(mediaRange) |
||||||
|
if err != nil { |
||||||
|
continue |
||||||
|
} |
||||||
|
item := Accept{ |
||||||
|
Type: typeSubtype[0], |
||||||
|
Subtype: typeSubtype[1], |
||||||
|
Q: 1.0, |
||||||
|
} |
||||||
|
// If there is only one rangeParams, we can stop here.
|
||||||
|
if len(rangeParams) == 1 { |
||||||
|
accepted = append(accepted, item) |
||||||
|
continue |
||||||
|
} |
||||||
|
// Validate the rangeParams.
|
||||||
|
validParams := true |
||||||
|
for _, v := range rangeParams[1:] { |
||||||
|
nameVal := strings.SplitN(v, "=", 2) |
||||||
|
if len(nameVal) != 2 { |
||||||
|
validParams = false |
||||||
|
break |
||||||
|
} |
||||||
|
nameVal[1] = strings.TrimSpace(nameVal[1]) |
||||||
|
if name := strings.TrimSpace(nameVal[0]); name == "q" { |
||||||
|
qval, err := strconv.ParseFloat(nameVal[1], 64) |
||||||
|
if err != nil || qval < 0 { |
||||||
|
validParams = false |
||||||
|
break |
||||||
|
} |
||||||
|
if qval > 1.0 { |
||||||
|
qval = 1.0 |
||||||
|
} |
||||||
|
item.Q = qval |
||||||
|
//break // 不跳过,检查 validParams
|
||||||
|
} |
||||||
|
} |
||||||
|
if validParams { |
||||||
|
accepted = append(accepted, item) |
||||||
|
} |
||||||
|
} |
||||||
|
sort.Sort(accepted) |
||||||
|
return accepted |
||||||
|
} |
||||||
|
|
||||||
|
// Len implements the Len() method of the Sort interface.
|
||||||
|
func (a AcceptSlice) Len() int { |
||||||
|
return len(a) |
||||||
|
} |
||||||
|
|
||||||
|
// Less implements the Less() method of the Sort interface. Elements are
|
||||||
|
// sorted in order of decreasing preference.
|
||||||
|
func (a AcceptSlice) Less(i, j int) bool { |
||||||
|
// Higher qvalues come first.
|
||||||
|
if a[i].Q > a[j].Q { |
||||||
|
return true |
||||||
|
} else if a[i].Q < a[j].Q { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// Specific types come before wildcard types.
|
||||||
|
if a[i].Type != "*" && a[j].Type == "*" { |
||||||
|
return true |
||||||
|
} else if a[i].Type == "*" && a[j].Type != "*" { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// Specific subtypes come before wildcard subtypes.
|
||||||
|
if a[i].Subtype != "*" && a[j].Subtype == "*" { |
||||||
|
return true |
||||||
|
} else if a[i].Subtype == "*" && a[j].Subtype != "*" { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// Swap implements the Swap() method of the Sort interface.
|
||||||
|
func (a AcceptSlice) Swap(i, j int) { |
||||||
|
a[i], a[j] = a[j], a[i] |
||||||
|
} |
||||||
|
|
||||||
|
func (a AcceptSlice) Is(expect ...string) bool { |
||||||
|
for _, e := range expect { |
||||||
|
for _, s := range a { |
||||||
|
if e == s.Mime() { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func (a AcceptSlice) Type(expects ...string) string { |
||||||
|
if len(expects) == 0 { |
||||||
|
return "" |
||||||
|
} |
||||||
|
var fuzzies [][2]string |
||||||
|
for _, expect := range expects { |
||||||
|
switch expect { |
||||||
|
case "html": |
||||||
|
if a.Is(echo.MIMETextHTML) { |
||||||
|
return expect |
||||||
|
} |
||||||
|
fuzzies = append(fuzzies, [2]string{expect, "text/*"}) |
||||||
|
case "json": |
||||||
|
if a.Is(echo.MIMEApplicationJSON) { |
||||||
|
return expect |
||||||
|
} |
||||||
|
fuzzies = append(fuzzies, [2]string{expect, "text/*"}) |
||||||
|
case "jsonp": |
||||||
|
if a.Is(echo.MIMEApplicationJavaScript) { |
||||||
|
return expect |
||||||
|
} |
||||||
|
case "xml": |
||||||
|
if a.Is(echo.MIMEApplicationXML, echo.MIMETextXML) { |
||||||
|
return expect |
||||||
|
} |
||||||
|
fuzzies = append(fuzzies, [2]string{expect, "text/*"}) |
||||||
|
case "form": |
||||||
|
if a.Is(echo.MIMEMultipartForm, echo.MIMEApplicationForm) { |
||||||
|
return expect |
||||||
|
} |
||||||
|
case "protobuf": |
||||||
|
if a.Is(echo.MIMEApplicationProtobuf) { |
||||||
|
return expect |
||||||
|
} |
||||||
|
case "msgpack": |
||||||
|
if a.Is(echo.MIMEApplicationMsgpack) { |
||||||
|
return expect |
||||||
|
} |
||||||
|
case "text", "string": |
||||||
|
if a.Is(echo.MIMETextPlain) { |
||||||
|
return expect |
||||||
|
} |
||||||
|
default: |
||||||
|
_, typeSubtype, err := parseMediaRange(expect) |
||||||
|
if err != nil { |
||||||
|
continue |
||||||
|
} |
||||||
|
if a.Is(typeSubtype[0] + "/" + typeSubtype[1]) { |
||||||
|
return expect |
||||||
|
} |
||||||
|
//if typeSubtype[0] == "text" {
|
||||||
|
// fuzzies = append(fuzzies, [2]string{expect, "text/*"})
|
||||||
|
//}
|
||||||
|
fuzzies = append(fuzzies, [2]string{expect, typeSubtype[0] + "/*"}) |
||||||
|
} |
||||||
|
} |
||||||
|
if fuzzies != nil { |
||||||
|
for _, f := range fuzzies { |
||||||
|
if a.Is(f[1]) { |
||||||
|
return f[0] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if a.Is("*/*") { |
||||||
|
return expects[0] |
||||||
|
} |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
// parseMediaRange parses the provided media range, and on success returns the
|
||||||
|
// parsed range params and type/subtype pair.
|
||||||
|
func parseMediaRange(mediaRange string) (rangeParams, typeSubtype []string, err error) { |
||||||
|
rangeParams = strings.Split(mediaRange, ";") |
||||||
|
typeSubtype = strings.Split(rangeParams[0], "/") |
||||||
|
// typeSubtype should have a length of exactly two.
|
||||||
|
if len(typeSubtype) > 2 { |
||||||
|
err = fmt.Errorf("go-slim.dev/slim: invalid accept type '%s'", rangeParams[0]) |
||||||
|
return |
||||||
|
} else { |
||||||
|
typeSubtype = append(typeSubtype, "*") |
||||||
|
} |
||||||
|
// Sanitize typeSubtype.
|
||||||
|
typeSubtype[0] = strings.TrimSpace(typeSubtype[0]) |
||||||
|
typeSubtype[1] = strings.TrimSpace(typeSubtype[1]) |
||||||
|
if typeSubtype[0] == "" { |
||||||
|
typeSubtype[0] = "*" |
||||||
|
} |
||||||
|
if typeSubtype[1] == "" { |
||||||
|
typeSubtype[1] = "*" |
||||||
|
} |
||||||
|
return |
||||||
|
} |
@ -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