In the daily development of Golang, it is sometimes necessary to validate each field of a struct to determine whether the value of the structure meets the conditions.
Consider the following profile structure:
type profile struct {
// Info is pointer filed
Info *basicInfo
Companies []company
}
type basicInfo struct {
// 1 <= len <= 20
Name string
// 18 <= age <= 80
Age int
// 1<= len <= 64
Email string
}
type company struct {
// frontend,backend
Position string
// frontend: html,css,javascript
// backend: C,Cpp,Java,Golang
// SkillStack 'length is between [1,3]
Skills []string
}
func getPassedProfile(a) profile {
companies := []company{
{
Position: "frontend",
Skills: []string{"html"."css"},
},
{
Position: "backend",
Skills: []string{"C"."Golang"},
},
}
info := basicInfo{Name: "liang", Age: 24, Email: "[email protected]"}
return profile{
Info: &info,
Companies: companies,
}
}
Copy the code
For values of type profile, there are the following restrictions:
Info
fieldInfo
Don’t is nilName
Is limited to [1,20]Age
The value range of is [18,80].Email
The length is limited to [1,64] and conforms to the mailbox format
Companies
fieldPosition
The value can be frontend or Backend- if
Position
It’s frontend, so the value of the element can only be HTML, CSS,javascript. - if
Position
Backend can be C,Cpp,Java, or Golang.
Skills
Is limited to [1,3]
Check structure parameters using the if/else, gin’s validator, and Checker methods.
Using an if/else
Use if/else to check if the struct argument is valid.
func isValidProfile(pro profile) bool {
if pro.Info == nil {
return false
}
if len(pro.Info.Name) > 20 {
return false
}
if pro.Info.Age < 18 && pro.Info.Age > 80 {
return false
}
if len(pro.Info.Email) > 64 {
return false
}
re := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](? : [a - zA - Z0-9 -], 21 {0} [a zA - Z0-9])? (? :\\.[a-zA-Z0-9](? : [a - zA - Z0-9 -], 21 {0} [a zA - Z0-9])?) * $")
if! re.MatchString(pro.Info.Email){return false
}
for _, comp := range pro.Companies {
if len(comp.Skills) > 3 {
return false
}
ifcomp.Position ! ="frontend"&& comp.Position ! ="backend" {
return false
}
if comp.Position == "frontend" {
for _, skill := range comp.Skills {
ifskill ! ="html"&& skill ! ="css"&& skill ! ="javascript" {
return false}}}else if comp.Position == "backend" {
for _, skill := range comp.Skills {
ifskill ! ="C"&& skill ! ="Cpp"&& skill ! ="Java"&& skill ! ="Golang" {
return false}}}}return true
}
Copy the code
As you can see, for the above validation rules, you may need to write large if/else statements. When the statements are too long, they are not readable and are strongly coupled to the structure.
Use go.pkg validatior
Go.pkg validator, which validates structures by adding tags to their fields.
The profile structure is modified to:
type profile struct {
Info *basicInfo
Companies []company `validate:"dive,min=1,max=3"`
}
type basicInfo struct {
Name string `validate:"min=1,max=20"`
Age int `validate:"min=18,max=80"`
Email string `validate:"min=1,max=64,email"`
}
type company struct {
// frontend,backend
Position string
// frontend: html,css,javascript
// backend: C,Cpp,Java,Golang
Skills []string `validate:"min=1,max=3"`
}
Copy the code
Check function changed to:
import "gopkg.in/go-playground/validator.v10"
func TestValidator(t *testing.T) {
pro := getPassedProfile()
validate := validator.New()
err := validate.Struct(pro)
iferr ! =nil {
t.Errorf("%s", err.Error())
return
}
for _, comp := range pro.Companies {
ifcomp.Position ! ="frontend"&& comp.Position ! ="backend" {
t.Error("failed")}if comp.Position == "frontend" {
for _, skill := range comp.Skills {
ifskill ! ="html"&& skill ! ="css"&& skill ! ="javascript" {
t.Error("failed")}}}else if comp.Position == "backend" {
for _, skill := range comp.Skills {
ifskill ! ="C"&& skill ! ="Cpp"&& skill ! ="Java"&& skill ! ="Golang" {
t.Error("failed")
}
}
}
}
t.Log("passed")}Copy the code
Gopkg. in/go-playground/validator.v10 reduces some code, but the validation logic needs to be written on the tag of the structure, which increases code coupling. In addition, Validators do not support validation of enumerations.
Use the checker
The checker, made up of rules and checkers, adds rules externally to each field of a structure, reducing code coupling, and provides rules for composition, enumeration, and so on, making it easy to combine different rules freely.
func getProfileChecker(a) checker.Checker {
profileChecker := checker.NewChecker()
infoNameRule := checker.NewLengthRule("Info.Name".1.20)
profileChecker.Add(infoNameRule, "invalid info name")
infoAgeRule := checker.NewRangeRuleInt("Info.Age".18.80)
profileChecker.Add(infoAgeRule, "invalid info age")
infoEmailRule := checker.NewAndRule([]checker.Rule{
checker.NewLengthRule("Info.Email".1.64),
checker.NewEmailRule("Info.Email"),
})
profileChecker.Add(infoEmailRule, "invalid info email")
companyLenRule := checker.NewLengthRule("Companies".1.3)
profileChecker.Add(companyLenRule, "invalid companies len")
frontendRule := checker.NewAndRule([]checker.Rule{
checker.NewEqRuleString("Position"."frontend"),
checker.NewSliceRule("Skills",
checker.NewEnumRuleString(""And []string{"html"."css"."javascript"}),
),
})
backendRule := checker.NewAndRule([]checker.Rule{
checker.NewEqRuleString("Position"."backend"),
checker.NewSliceRule("Skills",
checker.NewEnumRuleString(""And []string{"C"."CPP"."Java"."Golang"}),
),
})
companiesSliceRule := checker.NewSliceRule("Companies",
checker.NewAndRule([]checker.Rule{
checker.NewLengthRule("Skills".1.3),
checker.NewOrRule([]checker.Rule{
frontendRule, backendRule,
}),
}))
profileChecker.Add(companiesSliceRule, "invalid skill item")
return profileChecker
}
func TestProfileCheckerPassed(t *testing.T) {
profile := getPassedProfile()
profileChecker := getProfileChecker()
isValid, prompt, errMsg := profileChecker.Check(profile)
if! isValid { t.Logf("prompt:%s", prompt)
t.Logf("errMsg:%s", errMsg)
return
}
t.Log("pass check")
Copy the code
The TestProfileCheckerPassed function can be verified without adding additional code through the checker free collocation, reducing code coupling. The verification logic is all in the checker, so the verification logic is clearer.
Reference documentation
- checker
My official account: THE place to share lyP
My Zhihu column: zhuanlan.zhihu.com/c_127546654…
My blog: www.liangyaopei.com
Github Page: liangyaopei.github.io/