In the development of Golang using Gin framework, the server needs to verify the parameters of HTTP request after binding:

// req. Email must conform to the format of Email
type Req struct {
	Info  typeInfo
	Email string
}

type typeStr string

// req.info.type = "range", typeInfo.type is 2, all elements are in the format of "2006-01-02"
Type = "last",typeInfo.Type is 1 in length, and the Granularity of the elements is a positive integer. The Granularity must be one of day, week, or month
type typeInfo struct {
	Type        typeStr
	Range       []string
	Unit        string
	Granularity string
}

func BindParams(ctx *gin.Context) {
	req := Req{}
	_ = ctx.BindJSON(&req)
	// Check parameters
}

Copy the code

Generally, there are three ways to verify parameters:

  1. Use if/else or switch’s native validation methods.
  2. useginBuilt-in structure tag to verify.
  3. Use checker for declarative parameter verification.

Native validation method

As you can see, the check method of the native if/else switch is cumbersome and not easy to read.

func (r Req) validate(a) bool {
    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{FD F0}-\\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}])))\\.? $"
    
	regexObject := regexp.MustCompile(emailRegexString)
	if! regexObject.MatchString(r.Email) {return false
	}
	switch r.Info.Type {
	case "range":
		if len(r.Info.Range) ! =2 {
			return false
		}
		for _, value := range r.Info.Range {
			if _, err := time.Parse("2006-01-02", value); err ! =nil {
				return false}}case "last":
		if len(r.Info.Range) ! =1 {
			return false
		}
		valInt, err := strconv.Atoi(r.Info.Range[0])
		iferr ! =nil {
			return false
		}
		if valInt <= 0 {
			return false
		}
		ifr.Info.Granularity ! ="hour"&& r.Info.Granularity ! ="day"&& r.Info.Granularity ! ="week"&& r.Info.Granularity ! ="month"&& r.Info.Granularity ! ="year"&& r.Info.Granularity ! ="decade" {
			return false
		}

	default:
		return false
	}
	return true
}
Copy the code

Structure label verification

When defining a structure, label the field:

type Req struct {
	Info  typeInfo
	Email string `binding:"required,email"`
}

type typeStr string


type typeInfo struct {
	Type        typeStr
	Range       []string `binding:"required,min=1,max=2"`
	Granularity string
}

Copy the code

The disadvantages of structure label verification are:

  1. Supported methods are incomplete, for exampleGranularityThe enumeration checksum for does not have a label.
  2. Tags are strongly coupled to structures. If you want to have multiple checksum methods for the same structure, it is not currently supported. For example,RangeIn the field

The length of Type=range is 2, the length of Type=last is 1. 3. Difficult to read and prone to error. Complex verification rules are written on labels, which is not good for code readability. Also, if an int field is bound to a verification label of character type, panic occurs.

Checker

Checker reduces coupling by defining validation rules outside the structure. In addition, there are a number of rules to facilitate verification.

An important method in checker is fetchFiled, which is used to get the value of a field in a structure.

func fetchField(param interface{}, filedExpr string) (interface{}, reflect.Kind) {
	pValue := reflect.ValueOf(param)
	if filedExpr == "" {
		return param, pValue.Kind()
	}

	exprs := strings.Split(filedExpr, ".")
	for i := 0; i < len(exprs); i++ {
		expr := exprs[i]
		if pValue.Kind() == reflect.Ptr {
			pValue = pValue.Elem()
		}
		if! pValue.IsValid() || pValue.Kind() ! = reflect.Struct {return nil, reflect.Invalid
		}
		pValue = pValue.FieldByName(expr)
	}

	// last field is pointer
	if pValue.Kind() == reflect.Ptr {
		if pValue.IsNil() {
			return nil, reflect.Ptr
		}
		pValue = pValue.Elem()
	}

	if! pValue.IsValid() {return nil, reflect.Invalid
	}
	return pValue.Interface(), pValue.Kind()
}
Copy the code

FetchField parameter fieldExpr has three cases:

  • fieldExprIs an empty string, in which case the value is directly validated.
  • fieldExprIs a single field. In this case, the value of the field is first obtained and then verified.
  • fieldExprFor the point (.). Split the field according to the first.Value of hierarchy, and then verify

If the field is a pointer, the value of the pointer is used to verify the value. If the pointer is null, it is regarded as not passing the verification.

The above verification rules are rewritten using checker:

rule := And(
		Email("Email"),
		Field("Info",
			Or(
				And(
					EqStr("Type"."range"),
					Length("Range".2.2),
					Array("Range", isDatetime(""."2006-01-02")),
				),
				And(
					EqStr("Type"."last"),
					InStr("Granularity"."day"."week"."month"),
					Number("Unit"),
				),
			),
		),
    )
itemChecker := NewChecker()
// Check parameters
itemChecker.Add(rule, "wrong item")
Copy the code

Compared with the other two methods, the definition of Rule is more concise, clear, powerful and less coupled.

Bonus

The definition of fieldExpr in Checker makes it possible to verify the value and length of the list in a way that tags can’t.

type list struct {
	Name *string
	Next *list
}

// check that the length of the second node Name is [1,20].
nameRule := Length("Next.Next.Name".1.20)
Copy the code

Reference Documents:

  1. checker