mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-17 22:32:26 +08:00
Implement registering validator tags for custom map and slice types
This commit is contained in:
parent
04ccf172a6
commit
f26790bbda
41
README.md
41
README.md
@ -43,6 +43,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
|||||||
- [Controlling Log output coloring](#controlling-log-output-coloring)
|
- [Controlling Log output coloring](#controlling-log-output-coloring)
|
||||||
- [Model binding and validation](#model-binding-and-validation)
|
- [Model binding and validation](#model-binding-and-validation)
|
||||||
- [Custom Validators](#custom-validators)
|
- [Custom Validators](#custom-validators)
|
||||||
|
- [Custom Map and Slice Validator Tags](#custom-map-and-slice-validator-tags)
|
||||||
- [Only Bind Query String](#only-bind-query-string)
|
- [Only Bind Query String](#only-bind-query-string)
|
||||||
- [Bind Query String or Post Data](#bind-query-string-or-post-data)
|
- [Bind Query String or Post Data](#bind-query-string-or-post-data)
|
||||||
- [Bind Uri](#bind-uri)
|
- [Bind Uri](#bind-uri)
|
||||||
@ -838,6 +839,46 @@ $ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10"
|
|||||||
[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way.
|
[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way.
|
||||||
See the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations) to learn more.
|
See the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations) to learn more.
|
||||||
|
|
||||||
|
### Custom Map and Slice Validator Tags
|
||||||
|
|
||||||
|
It is possible to register validator tags for custom map and slice types.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Person struct {
|
||||||
|
FirstName string `json:"firstName" binding:"required,lte=64"`
|
||||||
|
LastName string `json:"lastName" binding:"required,lte=64"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Managers map[string]Person
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
route := gin.Default()
|
||||||
|
|
||||||
|
binding.RegisterValidatorTag("dive,keys,oneof=accounting finance operations,endkeys", Managers{})
|
||||||
|
|
||||||
|
route.POST("/managers", configureManagers)
|
||||||
|
route.Run(":8085")
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureManagers(c *gin.Context) {
|
||||||
|
var m Managers
|
||||||
|
if err := c.ShouldBindJSON(&m); err == nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": "Manager configuration is valid!"})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Only Bind Query String
|
### Only Bind Query String
|
||||||
|
|
||||||
`ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017).
|
`ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017).
|
||||||
|
@ -12,6 +12,33 @@ import (
|
|||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var validatorTags = make(map[reflect.Type]string)
|
||||||
|
|
||||||
|
// RegisterValidatorTag registers a validator tag against a number of types.
|
||||||
|
// This allows defining validation for custom slice, array, and map types. For example:
|
||||||
|
// type CustomMap map[int]string
|
||||||
|
// ...
|
||||||
|
// binding.RegisterValidatorTag("gt=0", CustomMap{})
|
||||||
|
//
|
||||||
|
// Do not use the "dive" tag (unless in conjunction with "keys"/"endkeys").
|
||||||
|
// Slice/array/map elements are validated independently.
|
||||||
|
//
|
||||||
|
// This function will not have any effect is binding.Validator has been replaced.
|
||||||
|
//
|
||||||
|
// NOTE: This function is not thread-safe. It is intended that these all be registered prior to any validation.
|
||||||
|
func RegisterValidatorTag(tag string, types ...interface{}) {
|
||||||
|
for _, typ := range types {
|
||||||
|
t := reflect.TypeOf(typ)
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
if t.Kind() != reflect.Slice && t.Kind() != reflect.Array && t.Kind() != reflect.Map {
|
||||||
|
panic("validator tags can be registered only for slices, arrays, and maps")
|
||||||
|
}
|
||||||
|
validatorTags[t] = tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type defaultValidator struct {
|
type defaultValidator struct {
|
||||||
once sync.Once
|
once sync.Once
|
||||||
validate *validator.Validate
|
validate *validator.Validate
|
||||||
@ -81,6 +108,13 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
|||||||
return v.validateStruct(obj)
|
return v.validateStruct(obj)
|
||||||
case reflect.Slice, reflect.Array:
|
case reflect.Slice, reflect.Array:
|
||||||
var errs validator.ValidationErrors
|
var errs validator.ValidationErrors
|
||||||
|
|
||||||
|
if tag, ok := validatorTags[value.Type()]; ok {
|
||||||
|
if err := v.validateVar(obj, tag); err != nil {
|
||||||
|
errs = append(errs, err.(validator.ValidationErrors)...) // nolint: errorlint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
count := value.Len()
|
count := value.Len()
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
if err := v.ValidateStruct(value.Index(i).Interface()); err != nil {
|
if err := v.ValidateStruct(value.Index(i).Interface()); err != nil {
|
||||||
@ -89,12 +123,20 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
var errs validator.ValidationErrors
|
var errs validator.ValidationErrors
|
||||||
|
|
||||||
|
if tag, ok := validatorTags[value.Type()]; ok {
|
||||||
|
if err := v.validateVar(obj, tag); err != nil {
|
||||||
|
errs = append(errs, err.(validator.ValidationErrors)...) // nolint: errorlint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, key := range value.MapKeys() {
|
for _, key := range value.MapKeys() {
|
||||||
if err := v.ValidateStruct(value.MapIndex(key).Interface()); err != nil {
|
if err := v.ValidateStruct(value.MapIndex(key).Interface()); err != nil {
|
||||||
for _, fieldError := range err.(validator.ValidationErrors) { // nolint: errorlint
|
for _, fieldError := range err.(validator.ValidationErrors) { // nolint: errorlint
|
||||||
@ -117,6 +159,12 @@ func (v *defaultValidator) validateStruct(obj interface{}) error {
|
|||||||
return v.validate.Struct(obj)
|
return v.validate.Struct(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateStruct receives slice, array, and map types
|
||||||
|
func (v *defaultValidator) validateVar(obj interface{}, tag string) error {
|
||||||
|
v.lazyinit()
|
||||||
|
return v.validate.Var(obj, tag)
|
||||||
|
}
|
||||||
|
|
||||||
// Engine returns the underlying validator engine which powers the default
|
// Engine returns the underlying validator engine which powers the default
|
||||||
// Validator instance. This is useful if you want to register custom validations
|
// Validator instance. This is useful if you want to register custom validations
|
||||||
// or struct level validations. See validator GoDoc for more info -
|
// or struct level validations. See validator GoDoc for more info -
|
||||||
|
@ -96,3 +96,116 @@ func TestDefaultValidator(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRegisterValidatorTag(t *testing.T) {
|
||||||
|
type CustomSlice []struct {
|
||||||
|
A string
|
||||||
|
}
|
||||||
|
type CustomArray [10]struct {
|
||||||
|
A string
|
||||||
|
}
|
||||||
|
type CustomMap map[string]struct {
|
||||||
|
A string
|
||||||
|
}
|
||||||
|
type CustomStruct struct {
|
||||||
|
A string
|
||||||
|
}
|
||||||
|
type CustomInt int
|
||||||
|
|
||||||
|
// only slice, array, and map types are accepted
|
||||||
|
RegisterValidatorTag("gt=0", CustomSlice{})
|
||||||
|
RegisterValidatorTag("gt=0", &CustomSlice{})
|
||||||
|
RegisterValidatorTag("gt=0", CustomArray{})
|
||||||
|
RegisterValidatorTag("gt=0", &CustomArray{})
|
||||||
|
RegisterValidatorTag("gt=0", CustomMap{})
|
||||||
|
RegisterValidatorTag("gt=0", &CustomMap{})
|
||||||
|
assert.Panics(t, func() { RegisterValidatorTag("gt=0", CustomStruct{}) })
|
||||||
|
assert.Panics(t, func() { RegisterValidatorTag("gt=0", &CustomStruct{}) })
|
||||||
|
assert.Panics(t, func() { var i CustomInt; RegisterValidatorTag("gt=0", i) })
|
||||||
|
assert.Panics(t, func() { var i CustomInt; RegisterValidatorTag("gt=0", &i) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidatorTagsSlice(t *testing.T) {
|
||||||
|
type CustomSlice []struct {
|
||||||
|
A string `binding:"max=8"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
invalidSlice = CustomSlice{{"12345678"}}
|
||||||
|
invalidVal = CustomSlice{{"123456789"}, {"abcdefgh"}}
|
||||||
|
validSlice = CustomSlice{{"12345678"}, {"abcdefgh"}}
|
||||||
|
invalidSliceVal = CustomSlice{{"123456789"}}
|
||||||
|
)
|
||||||
|
|
||||||
|
v := &defaultValidator{}
|
||||||
|
|
||||||
|
// no tags registered for the slice itself yet, so only elements are validated
|
||||||
|
assert.NoError(t, v.ValidateStruct(invalidSlice))
|
||||||
|
assert.Error(t, v.ValidateStruct(invalidVal))
|
||||||
|
assert.NoError(t, v.ValidateStruct(validSlice))
|
||||||
|
assert.NoError(t, v.ValidateStruct(&invalidSlice))
|
||||||
|
assert.Error(t, v.ValidateStruct(&invalidVal))
|
||||||
|
assert.NoError(t, v.ValidateStruct(&validSlice))
|
||||||
|
|
||||||
|
err := v.ValidateStruct(invalidSliceVal)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Len(t, err, 1) // only value error
|
||||||
|
|
||||||
|
RegisterValidatorTag("gt=1", CustomSlice{})
|
||||||
|
|
||||||
|
assert.Error(t, v.ValidateStruct(invalidSlice))
|
||||||
|
assert.Error(t, v.ValidateStruct(invalidVal))
|
||||||
|
assert.NoError(t, v.ValidateStruct(validSlice))
|
||||||
|
assert.Error(t, v.ValidateStruct(&invalidSlice))
|
||||||
|
assert.Error(t, v.ValidateStruct(&invalidVal))
|
||||||
|
assert.NoError(t, v.ValidateStruct(&validSlice))
|
||||||
|
|
||||||
|
err = v.ValidateStruct(invalidSliceVal)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Len(t, err, 2) // both slice length and value error
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidatorTagsMap(t *testing.T) {
|
||||||
|
type CustomMap map[string]struct {
|
||||||
|
B int `binding:"gt=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
invalidMap = CustomMap{"12345678": {1}}
|
||||||
|
invalidKey = CustomMap{"123456789": {1}, "abcdefgh": {2}}
|
||||||
|
invalidVal = CustomMap{"12345678": {0}, "abcdefgh": {2}}
|
||||||
|
invalidMapVal = CustomMap{"12345678": {0}}
|
||||||
|
validMap = CustomMap{"12345678": {1}, "abcdefgh": {2}}
|
||||||
|
)
|
||||||
|
|
||||||
|
v := &defaultValidator{}
|
||||||
|
|
||||||
|
// no tags registered for the map itself yet, so only values are validated
|
||||||
|
assert.NoError(t, v.ValidateStruct(invalidMap))
|
||||||
|
assert.NoError(t, v.ValidateStruct(invalidKey))
|
||||||
|
assert.Error(t, v.ValidateStruct(invalidVal))
|
||||||
|
assert.NoError(t, v.ValidateStruct(validMap))
|
||||||
|
assert.NoError(t, v.ValidateStruct(&invalidMap))
|
||||||
|
assert.NoError(t, v.ValidateStruct(&invalidKey))
|
||||||
|
assert.Error(t, v.ValidateStruct(&invalidVal))
|
||||||
|
assert.NoError(t, v.ValidateStruct(&validMap))
|
||||||
|
|
||||||
|
err := v.ValidateStruct(invalidMapVal)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Len(t, err, 1) // only value error
|
||||||
|
|
||||||
|
RegisterValidatorTag("gt=1,dive,keys,max=8,endkeys", CustomMap{})
|
||||||
|
|
||||||
|
assert.Error(t, v.ValidateStruct(invalidMap))
|
||||||
|
assert.Error(t, v.ValidateStruct(invalidKey))
|
||||||
|
assert.Error(t, v.ValidateStruct(invalidVal))
|
||||||
|
assert.NoError(t, v.ValidateStruct(validMap))
|
||||||
|
assert.Error(t, v.ValidateStruct(&invalidMap))
|
||||||
|
assert.Error(t, v.ValidateStruct(&invalidKey))
|
||||||
|
assert.Error(t, v.ValidateStruct(&invalidVal))
|
||||||
|
assert.NoError(t, v.ValidateStruct(&validMap))
|
||||||
|
|
||||||
|
err = v.ValidateStruct(invalidMapVal)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Len(t, err, 2) // both map size and value errors
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user