Skip to content

Commit

Permalink
Feat: support validate struct without struct tag
Browse files Browse the repository at this point in the history
  • Loading branch information
leoliang committed Apr 20, 2022
1 parent 58d5778 commit 1d01768
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 0 deletions.
91 changes: 91 additions & 0 deletions _examples/struct-map-rules-validation/main.go
@@ -0,0 +1,91 @@
package main

import (
"fmt"
"github.com/go-playground/validator/v10"
)

type Data struct {
Name string
Email string
Details *Details
}

type Details struct {
FamilyMembers FamilyMembers
Salary string
}

type FamilyMembers struct {
FatherName string
MotherName string
}

type Data2 struct {
Name string
Age uint32
}

var validate = validator.New()

func main() {
validateStruct()
// output
// Key: 'Data2.Name' Error:Field validation for 'Name' failed on the 'min' tag
// Key: 'Data2.Age' Error:Field validation for 'Age' failed on the 'max' tag

validateStructNested()
// output
// Key: 'Data.Name' Error:Field validation for 'Name' failed on the 'max' tag
// Key: 'Data.Details.FamilyMembers.FatherName' Error:Field validation for 'FatherName' failed on the 'min' tag
// Key: 'Data.Details.FamilyMembers.MotherName' Error:Field validation for 'MotherName' failed on the 'min' tag
}

func validateStruct() {
data := Data2{
Name: "leo",
Age: 1000,
}

rules := map[string]interface{}{
"Name": "min=4,max=6",
"Age": "min=4,max=6",
}

validate.RegisterStructValidationMapRules(rules, Data2{})

err := validate.Struct(data)
fmt.Println(err)
fmt.Println()
}

func validateStructNested() {
data := Data{
Name: "11sdfddd111",
Email: "zytel3301@mail.com",
Details: &Details{
FamilyMembers: FamilyMembers{
FatherName: "1",
MotherName: "2",
},
Salary: "1000",
},
}

rules := map[string]interface{}{
"Name": "min=4,max=6",
"Email": "required,email",
"Details": map[string]interface{}{
"FamilyMembers": map[string]interface{}{
"FatherName": "required,min=4,max=32",
"MotherName": "required,min=4,max=32",
},
"Salary": "number",
},
}

validate.RegisterStructValidationMapRules(rules, Data{Details: &Details{FamilyMembers: FamilyMembers{}}})
err := validate.Struct(data)

fmt.Println(err)
}
53 changes: 53 additions & 0 deletions cache.go
Expand Up @@ -166,6 +166,59 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
return cs
}

func (v *Validate) storeStructCacheRulesRecursive(val reflect.Value, rules map[string]interface{}) {

typ := val.Type()

if typ.Kind() == reflect.Ptr && !val.IsNil() {
typ = typ.Elem()
val = val.Elem()
}

if typ.Kind() != reflect.Struct {
return
}

var (
fld reflect.StructField
ctag *cTag
customName string
)

cs := &cStruct{name: typ.Name(), fields: make([]*cField, 0), fn: v.structLevelFuncs[typ]}
numFields := typ.NumField()
for i := 0; i < numFields; i++ {
fld = typ.Field(i)
tag := rules[fld.Name]
customName = fld.Name
if v.hasTagNameFunc {
name := v.tagNameFunc(fld)
if len(name) > 0 {
customName = name
}
}

switch tag := tag.(type) {
case string:
if tag == skipValidationTag {
continue
}
ctag, _ = v.parseFieldTagsRecursive(tag, fld.Name, "", false)
case map[string]interface{}:
v.storeStructCacheRulesRecursive(reflect.ValueOf(val.Field(i).Interface()), tag)
}

cs.fields = append(cs.fields, &cField{
idx: i,
name: fld.Name,
altName: customName,
cTags: ctag,
namesEqual: fld.Name == customName,
})
}
v.structCache.Set(typ, cs)
}

func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) {
var t string
noAlias := len(alias) == 0
Expand Down
10 changes: 10 additions & 0 deletions validator_instance.go
Expand Up @@ -271,6 +271,16 @@ func (v *Validate) RegisterStructValidationCtx(fn StructLevelFuncCtx, types ...i
}
}

// RegisterStructValidationMapRules registers a dynamic map rules
func (v *Validate) RegisterStructValidationMapRules(rules map[string]interface{}, types ...interface{}) {
v.structCache.lock.Lock()
defer v.structCache.lock.Unlock()

for _, t := range types {
v.storeStructCacheRulesRecursive(reflect.ValueOf(t), rules)
}
}

// 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
Expand Down

0 comments on commit 1d01768

Please sign in to comment.