[sduoj] Interface verification with validator

2021SC@SDUSC

introduction

In the sduoj project, the server needs to receive requests from the front end and perform different business processing according to different requests. Without a normalized constraint, the server may receive unreasonable requests. In order to avoid the confusion of business logic caused by parameter errors, we need an interface verification mechanism to return these unreasonable requests.

The interface input parameter verification rules are written on the field label of the corresponding verification structure. In CreateTagRequest, we see that it requires three request body parameters, namely Name, , CreatedByand State. In the field label, formthe parameter name is stored in , and the bindingrule constraint is stored.

type CreateTagRequest struct {
    
    
	Name      string `form:"name" binding:"required,min=3,max=100"`
	CreatedBy string `form:"created_by" binding:"required,min=3,max=100"`
	State     uint8  `form:"state,default=1" binding:"oneof=0 1"`
}

The following are common label meanings.

Label meaning
require Required
gt more than the
gte greater or equal to
lt less than
lte less than or equal to
min minimum
max maximum value
oneof one of the parameters in the set
len The length requirement is the same as that given by len

Source code analysis

BindAndValidThe method is mainly responsible for error handling, and its parameter binding and verification work is placed c.ShouldBindin it. In error handling, the method obtains the corresponding value from the context, transperforms certain language conversion work on the error information, and stores the error information ValidErrorsin .

func BindAndValid(c *gin.Context, v interface{
    
    }) (bool, ValidErrors) {
    
    
	var errs ValidErrors
	err := c.ShouldBind(v)
	if err != nil {
    
    
		v := c.Value("trans")
		trans, _ := v.(ut.Translator)
		verrs, ok := err.(val.ValidationErrors)
		if !ok {
    
    
			return false, errs
		}

		for key, value := range verrs.Translate(trans) {
    
    
			errs = append(errs, &ValidError{
    
    
				Key:     key,
				Message: value,
			})
		}

		return false, errs
	}

	return true, nil
}

validErrorsYes []*validErroralias, here we implement the *ValidErrormethods Error, and the errorinterface has only Errormethods, that is, *ValidErrorimplements the errorinterface.

type ValidError struct {
    
    
	Key     string
	Message string
}

type ValidErrors []*ValidError

func (v *ValidError) Error() string {
    
    
	return v.Message
}

ShouldBindContextThe parameter binding engine will be automatically selected according to the type. For example, if our Contexttype is application/json, we will choose the JSON binding engine.

func (c *Context) ShouldBind(obj interface{
    
    }) error {
    
    
	b := binding.Default(c.Request.Method, c.ContentType())
	return c.ShouldBindWith(obj, b)
}

ShouldBindWithA specific binding engine will be used to bind the passed struct pointer.

func (c *Context) ShouldBindWith(obj interface{
    
    }, b binding.Binding) error {
    
    
	return b.Bind(c.Request, obj)
}

ValueThe method returns a keyvalue associated with the context. It first checks keywhether the value is 0, and if so, c.Requestreturns it; if not, it asserts that it keyis a string type and returns the relevant value, and if it keyis not a string type, it returns nil.

func (c *Context) Value(key interface{
    
    }) interface{
    
    } {
    
    
	if key == 0 {
    
    
		return c.Request
	}
	if keyAsString, ok := key.(string); ok {
    
    
		val, _ := c.Get(keyAsString)
		return val
	}
	return nil
}

GetThe method returns the given keymapped value, which involves a lock and unlock operation, which ensures that the operation of obtaining this value is atomic.

func (c *Context) Get(key string) (value interface{
    
    }, exists bool) {
    
    
	c.mu.RLock()
	value, exists = c.Keys[key]
	c.mu.RUnlock()
	return
}

The validatordefault error message in English is English, but our error message does not have to be in English, it can be changed to Simplified Chinese here. Here is a custom middleware, according to the localelanguage selected, and then transstored in the context.

func Translations() gin.HandlerFunc {
    
    
	return func(c *gin.Context) {
    
    
		uni := ut.New(en.New(), zh.New(), zh_Hant_TW.New())
		locale := c.GetHeader("locale")
		trans, _ := uni.GetTranslator(locale)
		v, ok := binding.Validator.Engine().(*validator.Validate)
		if ok {
    
    
			switch locale {
    
    
			case "zh":
				_ = zh_translations.RegisterDefaultTranslations(v, trans)
			case "en":
				_ = en_translations.RegisterDefaultTranslations(v, trans)
			default:
				_ = zh_translations.RegisterDefaultTranslations(v, trans)
			}
			c.Set("trans", trans)
		}

		c.Next()
	}
}

Guess you like

Origin blog.csdn.net/weixin_45922876/article/details/120970207