package validator import ( "context" "errors" "fmt" "reflect" "strings" "sync" "time" ut "github.com/go-playground/universal-translator" ) const ( defaultTagName = "validate" utf8HexComma = "0x2C" utf8Pipe = "0x7C" tagSeparator = "," orSeparator = "|" tagKeySeparator = "=" structOnlyTag = "structonly" noStructLevelTag = "nostructlevel" omitempty = "omitempty" isdefault = "isdefault" skipValidationTag = "-" diveTag = "dive" keysTag = "keys" endKeysTag = "endkeys" requiredTag = "required" namespaceSeparator = "." leftBracket = "[" rightBracket = "]" restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}" restrictedAliasErr = "Alias '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation" restrictedTagErr = "Tag '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation" ) var ( timeType = reflect.TypeOf(time.Time{}) defaultCField = &cField{namesEqual: true} ) // FilterFunc is the type used to filter fields using // StructFiltered(...) function. // returning true results in the field being filtered/skiped from // validation type FilterFunc func(ns []byte) bool // CustomTypeFunc allows for overriding or adding custom field type handler functions // field = field value of the type to return a value to be validated // example Valuer from sql drive see https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29 type CustomTypeFunc func(field reflect.Value) interface{} // TagNameFunc allows for adding of a custom tag name parser type TagNameFunc func(field reflect.StructField) string // Validate contains the validator settings and cache type Validate struct { tagName string pool *sync.Pool hasCustomFuncs bool hasTagNameFunc bool tagNameFunc TagNameFunc structLevelFuncs map[reflect.Type]StructLevelFuncCtx customFuncs map[reflect.Type]CustomTypeFunc aliases map[string]string validations map[string]FuncCtx transTagFunc map[ut.Translator]map[string]TranslationFunc // map[]map[]TranslationFunc tagCache *tagCache structCache *structCache } // New returns a new instacne of 'validate' with sane defaults. func New() *Validate { tc := new(tagCache) tc.m.Store(make(map[string]*cTag)) sc := new(structCache) sc.m.Store(make(map[reflect.Type]*cStruct)) v := &Validate{ tagName: defaultTagName, aliases: make(map[string]string, len(bakedInAliases)), validations: make(map[string]FuncCtx, len(bakedInValidators)), tagCache: tc, structCache: sc, } // must copy alias validators for separate validations to be used in each validator instance for k, val := range bakedInAliases { v.RegisterAlias(k, val) } // must copy validators for separate validations to be used in each instance for k, val := range bakedInValidators { // no need to error check here, baked in will always be valid v.registerValidation(k, wrapFunc(val), true) } v.pool = &sync.Pool{ New: func() interface{} { return &validate{ v: v, ns: make([]byte, 0, 64), actualNs: make([]byte, 0, 64), misc: make([]byte, 32), } }, } return v } // SetTagName allows for changing of the default tag name of 'validate' func (v *Validate) SetTagName(name string) { v.tagName = name } // RegisterTagNameFunc registers a function to get another name from the // StructField eg. the JSON name func (v *Validate) RegisterTagNameFunc(fn TagNameFunc) { v.tagNameFunc = fn v.hasTagNameFunc = true } // RegisterValidation adds a validation with the given tag // // NOTES: // - if the key already exists, the previous validation function will be replaced. // - this method is not thread-safe it is intended that these all be registered prior to any validation func (v *Validate) RegisterValidation(tag string, fn Func) error { return v.RegisterValidationCtx(tag, wrapFunc(fn)) } // RegisterValidationCtx does the same as RegisterValidation on accepts a FuncCtx validation // allowing context.Context validation support. func (v *Validate) RegisterValidationCtx(tag string, fn FuncCtx) error { return v.registerValidation(tag, fn, false) } func (v *Validate) registerValidation(tag string, fn FuncCtx, bakedIn bool) error { if len(tag) == 0 { return errors.New("Function Key cannot be empty") } if fn == nil { return errors.New("Function cannot be empty") } _, ok := restrictedTags[tag] if !bakedIn && (ok || strings.ContainsAny(tag, restrictedTagChars)) { panic(fmt.Sprintf(restrictedTagErr, tag)) } v.validations[tag] = fn return nil } // RegisterAlias registers a mapping of a single validation tag that // defines a common or complex set of validation(s) to simplify adding validation // to structs. // // NOTE: this function is not thread-safe it is intended that these all be registered prior to any validation func (v *Validate) RegisterAlias(alias, tags string) { _, ok := restrictedTags[alias] if ok || strings.ContainsAny(alias, restrictedTagChars) { panic(fmt.Sprintf(restrictedAliasErr, alias)) } v.aliases[alias] = tags } // RegisterStructValidation registers a StructLevelFunc against a number of types. // // NOTE: // - this method is not thread-safe it is intended that these all be registered prior to any validation func (v *Validate) RegisterStructValidation(fn StructLevelFunc, types ...interface{}) { v.RegisterStructValidationCtx(wrapStructLevelFunc(fn), types...) } // RegisterStructValidationCtx registers a StructLevelFuncCtx against a number of types and allows passing // of contextual validation information via context.Context. // // NOTE: // - this method is not thread-safe it is intended that these all be registered prior to any validation func (v *Validate) RegisterStructValidationCtx(fn StructLevelFuncCtx, types ...interface{}) { if v.structLevelFuncs == nil { v.structLevelFuncs = make(map[reflect.Type]StructLevelFuncCtx) } for _, t := range types { v.structLevelFuncs[reflect.TypeOf(t)] = fn } } // RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types // // NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) { if v.customFuncs == nil { v.customFuncs = make(map[reflect.Type]CustomTypeFunc) } for _, t := range types { v.customFuncs[reflect.TypeOf(t)] = fn } v.hasCustomFuncs = true } // RegisterTranslation registers translations against the provided tag. func (v *Validate) RegisterTranslation(tag string, trans ut.Translator, registerFn RegisterTranslationsFunc, translationFn TranslationFunc) (err error) { if v.transTagFunc == nil { v.transTagFunc = make(map[ut.Translator]map[string]TranslationFunc) } if err = registerFn(trans); err != nil { return } m, ok := v.transTagFunc[trans] if !ok { m = make(map[string]TranslationFunc) v.transTagFunc[trans] = m } m[tag] = translationFn return } // Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified. // // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. func (v *Validate) Struct(s interface{}) error { return v.StructCtx(context.Background(), s) } // StructCtx validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified // and also allows passing of context.Context for contextual validation information. // // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. func (v *Validate) StructCtx(ctx context.Context, s interface{}) (err error) { val := reflect.ValueOf(s) top := val if val.Kind() == reflect.Ptr && !val.IsNil() { val = val.Elem() } if val.Kind() != reflect.Struct || val.Type() == timeType { return &InvalidValidationError{Type: reflect.TypeOf(s)} } // good to validate vd := v.pool.Get().(*validate) vd.top = top vd.isPartial = false // vd.hasExcludes = false // only need to reset in StructPartial and StructExcept vd.validateStruct(ctx, top, val, val.Type(), vd.ns[0:0], vd.actualNs[0:0], nil) if len(vd.errs) > 0 { err = vd.errs vd.errs = nil } v.pool.Put(vd) return } // StructFiltered validates a structs exposed fields, that pass the FilterFunc check and automatically validates // nested structs, unless otherwise specified. // // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. func (v *Validate) StructFiltered(s interface{}, fn FilterFunc) error { return v.StructFilteredCtx(context.Background(), s, fn) } // StructFilteredCtx validates a structs exposed fields, that pass the FilterFunc check and automatically validates // nested structs, unless otherwise specified and also allows passing of contextual validation information via // context.Context // // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. func (v *Validate) StructFilteredCtx(ctx context.Context, s interface{}, fn FilterFunc) (err error) { val := reflect.ValueOf(s) top := val if val.Kind() == reflect.Ptr && !val.IsNil() { val = val.Elem() } if val.Kind() != reflect.Struct || val.Type() == timeType { return &InvalidValidationError{Type: reflect.TypeOf(s)} } // good to validate vd := v.pool.Get().(*validate) vd.top = top vd.isPartial = true vd.ffn = fn // vd.hasExcludes = false // only need to reset in StructPartial and StructExcept vd.validateStruct(context.Background(), top, val, val.Type(), vd.ns[0:0], vd.actualNs[0:0], nil) if len(vd.errs) > 0 { err = vd.errs vd.errs = nil } v.pool.Put(vd) return } // StructPartial validates the fields passed in only, ignoring all others. // Fields may be provided in a namespaced fashion relative to the struct provided // eg. NestedStruct.Field or NestedArrayField[0].Struct.Name // // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. func (v *Validate) StructPartial(s interface{}, fields ...string) error { return v.StructPartialCtx(context.Background(), s, fields...) } // StructPartialCtx validates the fields passed in only, ignoring all others and allows passing of contextual // validation validation information via context.Context // Fields may be provided in a namespaced fashion relative to the struct provided // eg. NestedStruct.Field or NestedArrayField[0].Struct.Name // // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. func (v *Validate) StructPartialCtx(ctx context.Context, s interface{}, fields ...string) (err error) { val := reflect.ValueOf(s) top := val if val.Kind() == reflect.Ptr && !val.IsNil() { val = val.Elem() } if val.Kind() != reflect.Struct || val.Type() == timeType { return &InvalidValidationError{Type: reflect.TypeOf(s)} } // good to validate vd := v.pool.Get().(*validate) vd.top = top vd.isPartial = true vd.ffn = nil vd.hasExcludes = false vd.includeExclude = make(map[string]struct{}) typ := val.Type() name := typ.Name() for _, k := range fields { flds := strings.Split(k, namespaceSeparator) if len(flds) > 0 { vd.misc = append(vd.misc[0:0], name...) vd.misc = append(vd.misc, '.') for _, s := range flds { idx := strings.Index(s, leftBracket) if idx != -1 { for idx != -1 { vd.misc = append(vd.misc, s[:idx]...) vd.includeExclude[string(vd.misc)] = struct{}{} idx2 := strings.Index(s, rightBracket) idx2++ vd.misc = append(vd.misc, s[idx:idx2]...) vd.includeExclude[string(vd.misc)] = struct{}{} s = s[idx2:] idx = strings.Index(s, leftBracket) } } else { vd.misc = append(vd.misc, s...) vd.includeExclude[string(vd.misc)] = struct{}{} } vd.misc = append(vd.misc, '.') } } } vd.validateStruct(ctx, top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil) if len(vd.errs) > 0 { err = vd.errs vd.errs = nil } v.pool.Put(vd) return } // StructExcept validates all fields except the ones passed in. // Fields may be provided in a namespaced fashion relative to the struct provided // i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name // // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. func (v *Validate) StructExcept(s interface{}, fields ...string) error { return v.StructExceptCtx(context.Background(), s, fields...) } // StructExceptCtx validates all fields except the ones passed in and allows passing of contextual // validation validation information via context.Context // Fields may be provided in a namespaced fashion relative to the struct provided // i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name // // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. func (v *Validate) StructExceptCtx(ctx context.Context, s interface{}, fields ...string) (err error) { val := reflect.ValueOf(s) top := val if val.Kind() == reflect.Ptr && !val.IsNil() { val = val.Elem() } if val.Kind() != reflect.Struct || val.Type() == timeType { return &InvalidValidationError{Type: reflect.TypeOf(s)} } // good to validate vd := v.pool.Get().(*validate) vd.top = top vd.isPartial = true vd.ffn = nil vd.hasExcludes = true vd.includeExclude = make(map[string]struct{}) typ := val.Type() name := typ.Name() for _, key := range fields { vd.misc = vd.misc[0:0] if len(name) > 0 { vd.misc = append(vd.misc, name...) vd.misc = append(vd.misc, '.') } vd.misc = append(vd.misc, key...) vd.includeExclude[string(vd.misc)] = struct{}{} } vd.validateStruct(ctx, top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil) if len(vd.errs) > 0 { err = vd.errs vd.errs = nil } v.pool.Put(vd) return } // Var validates a single variable using tag style validation. // eg. // var i int // validate.Var(i, "gt=1,lt=10") // // WARNING: a struct can be passed for validation eg. time.Time is a struct or // if you have a custom type and have registered a custom type handler, so must // allow it; however unforseen validations will occur if trying to validate a // struct that is meant to be passed to 'validate.Struct' // // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. // validate Array, Slice and maps fields which may contain more than one error func (v *Validate) Var(field interface{}, tag string) error { return v.VarCtx(context.Background(), field, tag) } // VarCtx validates a single variable using tag style validation and allows passing of contextual // validation validation information via context.Context. // eg. // var i int // validate.Var(i, "gt=1,lt=10") // // WARNING: a struct can be passed for validation eg. time.Time is a struct or // if you have a custom type and have registered a custom type handler, so must // allow it; however unforseen validations will occur if trying to validate a // struct that is meant to be passed to 'validate.Struct' // // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. // validate Array, Slice and maps fields which may contain more than one error func (v *Validate) VarCtx(ctx context.Context, field interface{}, tag string) (err error) { if len(tag) == 0 || tag == skipValidationTag { return nil } ctag := v.fetchCacheTag(tag) val := reflect.ValueOf(field) vd := v.pool.Get().(*validate) vd.top = val vd.isPartial = false vd.traverseField(ctx, val, val, vd.ns[0:0], vd.actualNs[0:0], defaultCField, ctag) if len(vd.errs) > 0 { err = vd.errs vd.errs = nil } v.pool.Put(vd) return } // VarWithValue validates a single variable, against another variable/field's value using tag style validation // eg. // s1 := "abcd" // s2 := "abcd" // validate.VarWithValue(s1, s2, "eqcsfield") // returns true // // WARNING: a struct can be passed for validation eg. time.Time is a struct or // if you have a custom type and have registered a custom type handler, so must // allow it; however unforseen validations will occur if trying to validate a // struct that is meant to be passed to 'validate.Struct' // // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. // validate Array, Slice and maps fields which may contain more than one error func (v *Validate) VarWithValue(field interface{}, other interface{}, tag string) error { return v.VarWithValueCtx(context.Background(), field, other, tag) } // VarWithValueCtx validates a single variable, against another variable/field's value using tag style validation and // allows passing of contextual validation validation information via context.Context. // eg. // s1 := "abcd" // s2 := "abcd" // validate.VarWithValue(s1, s2, "eqcsfield") // returns true // // WARNING: a struct can be passed for validation eg. time.Time is a struct or // if you have a custom type and have registered a custom type handler, so must // allow it; however unforseen validations will occur if trying to validate a // struct that is meant to be passed to 'validate.Struct' // // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. // validate Array, Slice and maps fields which may contain more than one error func (v *Validate) VarWithValueCtx(ctx context.Context, field interface{}, other interface{}, tag string) (err error) { if len(tag) == 0 || tag == skipValidationTag { return nil } ctag := v.fetchCacheTag(tag) otherVal := reflect.ValueOf(other) vd := v.pool.Get().(*validate) vd.top = otherVal vd.isPartial = false vd.traverseField(ctx, otherVal, reflect.ValueOf(field), vd.ns[0:0], vd.actualNs[0:0], defaultCField, ctag) if len(vd.errs) > 0 { err = vd.errs vd.errs = nil } v.pool.Put(vd) return }