Gin Framework – Part 11

Introduction

Validator is an open source validator package that can quickly verify whether the input information conforms to custom rules. Currently, it has 7.8k stars on Github.

Installation

go get github.com/go-playground/validator

Import

import "github.com/go-playground/validator"

Example

  • Code
package main

import (
	"fmt"

	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
)

// Define an add user parameter structure
type AddUserPost struct {
	Name  string `json:"name" validate:"required"`        //Required
	Email string `json:"email" validate:"required,email"` // Required Fill in, and the format is email
	Age   uint8  `json:"age" validate:"gte=18,lte=30"`    // age range
}

// Simple example
func main() {
	engine := gin.Default()
	engine.POST("/valid", func(context *gin.Context) {
		var adduserPost AddUserPost
		// Receive parameter
		err := context.ShouldBindJSON(&adduserPost)
		if err != nil {
			context.JSON(500, gin.H{"msg": err})
			return
		}
		fmt.Printf("adduserPost: %+v\n", adduserPost)
		// Validate with Validate
		validate := validator.New()
		err = validate.Struct(adduserPost)
		if err != nil {
			fmt.Println(err)
			context.JSON(500, gin.H{"msg": err.Error()})
			return
		}
		context.JSON(200, gin.H{"msg": "success"})
	})
	_ = engine.Run(":9090")
}
  • Requests
# When the email is invalid
$ curl -X POST http://127.0.0.1:9090/valid -d '{"name":"Marsero","email":"marsero","age":26}'
{"msg":"Key: 'AddUserPost.Email' Error:Field validation for 'Email' failed on the 'email' tag"}%

# When age is not in the specified range
$ curl -X POST http://127.0.0.1:9090/valid -d '{"name":"Marsero","email":"marsero@mars.com","age":17}'
{"msg":"Key: 'AddUserPost.Age' Error:Field validation for 'Age' failed on the 'gte' tag"}%

# When name is not filled in
$ curl -X POST http://127.0.0.1:9090/valid -d '{"name":"","email":"marsero@mars.com","age":20}'
{"msg":"Key: 'AddUserPost.Name' Error:Field validation for 'Name' failed on the 'required' tag"}%

Translate to French

Code

package main

import (
	"github.com/gin-gonic/gin"
	"github.com/go-playground/locales/fr"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	frs "github.com/go-playground/validator/v10/translations/fr"
)

var (
	validate = validator.New()         // instantiate validator
	french   = fr.New()                // get French translator
	uni      = ut.New(french, french)  // set to French translator
	trans, _ = uni.GetTranslator("fr") // Get translation dictionary
)

type User struct {
	Name  string `form:"name" validate:"required,min=3,max=5"`
	Email string `form:"email" validate:"email"`
	Age   int8   `form:"age" validate:"gte=18,lte=20"`
}

func main() {
	engine := gin.Default()
	engine.GET("/language", func(context *gin.Context) {
		var user User
		err := context.ShouldBindQuery(&user)
		if err != nil {
			context.JSON(500, gin.H{"msg": err})
			return
		}
		// register translator
		_ = frs.RegisterDefaultTranslations(validate, trans)
		// use validator to validate
		err = validate.Struct(user)
		if err != nil {
			if errors, ok := err.(validator.ValidationErrors); ok {
				// Translate and return
				context.JSON(500, gin.H{
					"before translation": errors.Error(),
					"after translation ": errors.Translate(trans),
				})
				return
			}
		}
		context.JSON(200, gin.H{"msg": "success"})
	})
	_ = engine.Run(":9090")
}
  • Request
# do not pass parameters
$ curl -X GET http://127.0.0.1:9090/language

# it returns
{
   "after translation ":{
      "User.Age":"Age doit être 18 ou plus",
      "User.Email":"Email doit être une adresse email valide",
      "User.Name":"Name est un champ obligatoire"
   },
   "before translation":"Key: 'User.Name' Error:Field validation for 'Name' failed on the 'required' tag\nKey: 'User.Email' Error:Field validation for 'Email' failed on the 'email' tag\nKey: 'User.Age' Error:Field validation for 'Age' failed on the 'gte' tag"
}

Validation rules

Organize some common rules

Tag Description
required Required
unique Guarantees uniqueness, different types are handled differently;
For map, unique constraints have no repeated value values;
For arrays and slices, there are no repeated element values<br/ >
min The minimum value, different types have different meanings
The number type represents the minimum value.
String type represents the minimum number of characters
max The maximum value, different types have different meanings
The number type represents the maximum value.
String type represents the maximum number of characters
gte greater than or equal to parameter value
gt greater than parameter value
lt less than parameter value
lte less than d equals parameter value
email Use email to restrict fields to be email
oneof Must be one of the specified values, such as: oneof=male or female, only male or female

For more rules, you can check the documentation: https://github.com/go-playground/validator

Custom Rules

Code

package main

import (
	"fmt"

	"github.com/go-playground/validator/v10"
)

// verify pre
type CustomParam struct {
	Pre string `validate:"pre=go_"`
}

func main() {
	// instantiate validator
	validate = validator.New()
	// register custom
	tag_ = validate.RegisterValidation("pre", ValidatePre)
	cusParam := CustomParam{
		Pre: "php_",
	}
	err := validate.Struct(cusParam)
	fmt.Println(err)
}

// custom validation rule
func ValidatePre(fl validator.FieldLevel) bool {
	return fl.Field().String() == "go_"
}
  • Request return
Key: 'CustomParam.Pre' Error:Field validation for 'Pre' failed on the 'pre' tag

Use in Gin

Code

package main

import (
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator/v10"
)

// define structure
type User struct {
	Name      string    `form:"name" binding:"required,min=3,max=5" `
	BirthDate time.Time `form:"date" binding:"required,birth" time_format:"2006-01-02"`
}

// Run the program
func main() {
	engine := gin.Default()
	// Register custom validation tags: birth
	if validate, ok := binding.Validator.Engine().(*validator.Validate); ok {
		validate.RegisterValidation(" birth", checkBirthDate)
	}
	// Receive request
	engine.GET("/valid", func(context *gin.Context) {
		var user User
		// integration validation
		err := context.ShouldBindQuery(&user)
		if err != nil {
			context.JSON(http.StatusBadRequest, gin.H{" error": err.Error()})
			return
		}
		context.JSON(http.StatusOK, gin.H{"msg": "success"})
	})
	_ = engine.Run(":9090")
}

// check birthday
func checkBirthDate(fl validator.FieldLevel) bool {
	t, ok := fl.Field().Interface().(time.Time)
	if ok {
		// current time should be greater than birthday time
		if time.Now().After(t) {
			return true
		}
	}
	return false
}
  • Request
# When the name is wrong
$ curl -X GET http://127.0.0.1:9090/valid?name=Marsero&date=2020-01-01
{"error":"Key: 'User.Name' Error:Field validation for 'Name' failed on the 'min' tag"}

# When the custom birth format is wrong
$ curl -X GET http://127.0.0.1:9090/valid?name=Marsero is three&date=2020
{"error":"parsing time \"2020\" as \"2006-01-02\": cannot parse \"\" as \"-\""}

# all correct
$ curl -X GET http://127.0.0.1:9090/valid?name=Zhang is three&date=2020-10-10
{"msg":"success"}

Error display json tag

....	
 // Register a custom method to get json tag
	validate.RegisterTagNameFunc(func(field reflect.StructField) string {
		splitN := strings.SplitN(field.Tag.Get("json"), ",", 2)
		fmt.Println(splitN)
		n := splitN[0]
		if n == "-" {
			return ""
		}
		return n
	})
	err := validate.Struct(s)
if err != nil {
  ....
}

Commonly used tags instructions

More available here.