mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-17 22:32:26 +08:00
Merge b626f639064cf3a72e4698c7804ecef2b06b7ec4 into 6aee45608da726e4d7e51f558728e0eb90ac6790
This commit is contained in:
commit
c9b3f20495
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).
|
||||||
|
@ -7,7 +7,10 @@
|
|||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
|
||||||
import "net/http"
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
// Content-Type MIME of the most common data formats.
|
// Content-Type MIME of the most common data formats.
|
||||||
const (
|
const (
|
||||||
@ -32,29 +35,50 @@ type Binding interface {
|
|||||||
Bind(*http.Request, interface{}) error
|
Bind(*http.Request, interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
|
// ContextBinding enables contextual validation by adding BindContext to Binding.
|
||||||
|
// Custom validators can take advantage of the information in the context.
|
||||||
|
type ContextBinding interface {
|
||||||
|
Binding
|
||||||
|
BindContext(context.Context, *http.Request, interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingBody adds BindBody method to Binding. BindBody is similar to Bind,
|
||||||
// but it reads the body from supplied bytes instead of req.Body.
|
// but it reads the body from supplied bytes instead of req.Body.
|
||||||
type BindingBody interface {
|
type BindingBody interface {
|
||||||
Binding
|
Binding
|
||||||
BindBody([]byte, interface{}) error
|
BindBody([]byte, interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
|
// ContextBindingBody enables contextual validation by adding BindBodyContext to BindingBody.
|
||||||
// but it read the Params.
|
// Custom validators can take advantage of the information in the context.
|
||||||
|
type ContextBindingBody interface {
|
||||||
|
BindingBody
|
||||||
|
BindContext(context.Context, *http.Request, interface{}) error
|
||||||
|
BindBodyContext(context.Context, []byte, interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingUri is similar to Bind, but it read the Params.
|
||||||
type BindingUri interface {
|
type BindingUri interface {
|
||||||
Name() string
|
Name() string
|
||||||
BindUri(map[string][]string, interface{}) error
|
BindUri(map[string][]string, interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContextBindingUri enables contextual validation by adding BindUriContext to BindingUri.
|
||||||
|
// Custom validators can take advantage of the information in the context.
|
||||||
|
type ContextBindingUri interface {
|
||||||
|
BindingUri
|
||||||
|
BindUriContext(context.Context, map[string][]string, interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
// StructValidator is the minimal interface which needs to be implemented in
|
// StructValidator is the minimal interface which needs to be implemented in
|
||||||
// order for it to be used as the validator engine for ensuring the correctness
|
// order for it to be used as the validator engine for ensuring the correctness
|
||||||
// of the request. Gin provides a default implementation for this using
|
// of the request. Gin provides a default implementation for this using
|
||||||
// https://github.com/go-playground/validator/tree/v10.6.1.
|
// https://github.com/go-playground/validator/tree/v10.6.1.
|
||||||
type StructValidator interface {
|
type StructValidator interface {
|
||||||
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
||||||
// If the received type is a slice|array, the validation should be performed travel on every element.
|
// If the received type is a slice/array/map, the validation should be performed on every element.
|
||||||
// If the received type is not a struct or slice|array, any validation should be skipped and nil must be returned.
|
// If the received type is not a struct or slice/array/map, any validation should be skipped and nil must be returned.
|
||||||
// If the received type is a struct or pointer to a struct, the validation should be performed.
|
// If the received type is a pointer to a struct/slice/array/map, the validation should be performed.
|
||||||
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
|
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
|
||||||
// Otherwise nil must be returned.
|
// Otherwise nil must be returned.
|
||||||
ValidateStruct(interface{}) error
|
ValidateStruct(interface{}) error
|
||||||
@ -64,6 +88,14 @@ type StructValidator interface {
|
|||||||
Engine() interface{}
|
Engine() interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContextStructValidator is an extension of StructValidator that requires implementing
|
||||||
|
// context-aware validation.
|
||||||
|
// Custom validators can take advantage of the information in the context.
|
||||||
|
type ContextStructValidator interface {
|
||||||
|
StructValidator
|
||||||
|
ValidateStructContext(context.Context, interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
// Validator is the default validator which implements the StructValidator
|
// Validator is the default validator which implements the StructValidator
|
||||||
// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1
|
// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1
|
||||||
// under the hood.
|
// under the hood.
|
||||||
@ -110,9 +142,12 @@ func Default(method, contentType string) Binding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validate(obj interface{}) error {
|
func validateContext(ctx context.Context, obj interface{}) error {
|
||||||
if Validator == nil {
|
if Validator == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if v, ok := Validator.(ContextStructValidator); ok {
|
||||||
|
return v.ValidateStructContext(ctx, obj)
|
||||||
|
}
|
||||||
return Validator.ValidateStruct(obj)
|
return Validator.ValidateStruct(obj)
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ package binding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -35,7 +36,7 @@ func TestBindingMsgPack(t *testing.T) {
|
|||||||
string(data), string(data[1:]))
|
string(data), string(data[1:]))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
|
func testMsgPackBodyBinding(t *testing.T, b ContextBinding, name, path, badPath, body, badBody string) {
|
||||||
assert.Equal(t, name, b.Name())
|
assert.Equal(t, name, b.Name())
|
||||||
|
|
||||||
obj := FooStruct{}
|
obj := FooStruct{}
|
||||||
@ -48,7 +49,17 @@ func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body,
|
|||||||
obj = FooStruct{}
|
obj = FooStruct{}
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody("POST", badPath, badBody)
|
||||||
req.Header.Add("Content-Type", MIMEMSGPACK)
|
req.Header.Add("Content-Type", MIMEMSGPACK)
|
||||||
err = MsgPack.Bind(req, &obj)
|
err = b.Bind(req, &obj)
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
obj2 := ConditionalFooStruct{}
|
||||||
|
req = requestWithBody("POST", path, body)
|
||||||
|
req.Header.Add("Content-Type", MIMEMSGPACK)
|
||||||
|
err = b.BindContext(context.Background(), req, &obj2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "bar", obj2.Foo)
|
||||||
|
|
||||||
|
err = b.BindContext(context.WithValue(context.Background(), "condition", true), req, &obj2) // nolint
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,10 @@
|
|||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
|
||||||
import "net/http"
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
// Content-Type MIME of the most common data formats.
|
// Content-Type MIME of the most common data formats.
|
||||||
const (
|
const (
|
||||||
@ -30,28 +33,50 @@ type Binding interface {
|
|||||||
Bind(*http.Request, interface{}) error
|
Bind(*http.Request, interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
|
// ContextBinding enables contextual validation by adding BindContext to Binding.
|
||||||
|
// Custom validators can take advantage of the information in the context.
|
||||||
|
type ContextBinding interface {
|
||||||
|
Binding
|
||||||
|
BindContext(context.Context, *http.Request, interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingBody adds BindBody method to Binding. BindBody is similar to Bind,
|
||||||
// but it reads the body from supplied bytes instead of req.Body.
|
// but it reads the body from supplied bytes instead of req.Body.
|
||||||
type BindingBody interface {
|
type BindingBody interface {
|
||||||
Binding
|
Binding
|
||||||
BindBody([]byte, interface{}) error
|
BindBody([]byte, interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
|
// ContextBindingBody enables contextual validation by adding BindBodyContext to BindingBody.
|
||||||
// but it read the Params.
|
// Custom validators can take advantage of the information in the context.
|
||||||
|
type ContextBindingBody interface {
|
||||||
|
BindingBody
|
||||||
|
BindContext(context.Context, *http.Request, interface{}) error
|
||||||
|
BindBodyContext(context.Context, []byte, interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingUri is similar to Bind, but it read the Params.
|
||||||
type BindingUri interface {
|
type BindingUri interface {
|
||||||
Name() string
|
Name() string
|
||||||
BindUri(map[string][]string, interface{}) error
|
BindUri(map[string][]string, interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContextBindingUri enables contextual validation by adding BindUriContext to BindingUri.
|
||||||
|
// Custom validators can take advantage of the information in the context.
|
||||||
|
type ContextBindingUri interface {
|
||||||
|
BindingUri
|
||||||
|
BindUriContext(context.Context, map[string][]string, interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
// StructValidator is the minimal interface which needs to be implemented in
|
// StructValidator is the minimal interface which needs to be implemented in
|
||||||
// order for it to be used as the validator engine for ensuring the correctness
|
// order for it to be used as the validator engine for ensuring the correctness
|
||||||
// of the request. Gin provides a default implementation for this using
|
// of the request. Gin provides a default implementation for this using
|
||||||
// https://github.com/go-playground/validator/tree/v10.6.1.
|
// https://github.com/go-playground/validator/tree/v10.6.1.
|
||||||
type StructValidator interface {
|
type StructValidator interface {
|
||||||
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
||||||
// If the received type is not a struct, any validation should be skipped and nil must be returned.
|
// If the received type is a slice/array/map, the validation should be performed on every element.
|
||||||
// If the received type is a struct or pointer to a struct, the validation should be performed.
|
// If the received type is not a struct or slice/array/map, any validation should be skipped and nil must be returned.
|
||||||
|
// If the received type is a pointer to a struct/slice/array/map, the validation should be performed.
|
||||||
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
|
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
|
||||||
// Otherwise nil must be returned.
|
// Otherwise nil must be returned.
|
||||||
ValidateStruct(interface{}) error
|
ValidateStruct(interface{}) error
|
||||||
@ -61,6 +86,14 @@ type StructValidator interface {
|
|||||||
Engine() interface{}
|
Engine() interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContextStructValidator is an extension of StructValidator that requires implementing
|
||||||
|
// context-aware validation.
|
||||||
|
// Custom validators can take advantage of the information in the context.
|
||||||
|
type ContextStructValidator interface {
|
||||||
|
StructValidator
|
||||||
|
ValidateStructContext(context.Context, interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
// Validator is the default validator which implements the StructValidator
|
// Validator is the default validator which implements the StructValidator
|
||||||
// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1
|
// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1
|
||||||
// under the hood.
|
// under the hood.
|
||||||
@ -84,7 +117,7 @@ var (
|
|||||||
// Default returns the appropriate Binding instance based on the HTTP method
|
// Default returns the appropriate Binding instance based on the HTTP method
|
||||||
// and the content type.
|
// and the content type.
|
||||||
func Default(method, contentType string) Binding {
|
func Default(method, contentType string) Binding {
|
||||||
if method == "GET" {
|
if method == http.MethodGet {
|
||||||
return Form
|
return Form
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,9 +137,12 @@ func Default(method, contentType string) Binding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validate(obj interface{}) error {
|
func validateContext(ctx context.Context, obj interface{}) error {
|
||||||
if Validator == nil {
|
if Validator == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if v, ok := Validator.(ContextStructValidator); ok {
|
||||||
|
return v.ValidateStructContext(ctx, obj)
|
||||||
|
}
|
||||||
return Validator.ValidateStruct(obj)
|
return Validator.ValidateStruct(obj)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ package binding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
@ -20,6 +21,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/testdata/protoexample"
|
"github.com/gin-gonic/gin/testdata/protoexample"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
@ -38,6 +40,10 @@ type FooStruct struct {
|
|||||||
Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required,max=32"`
|
Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required,max=32"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConditionalFooStruct struct {
|
||||||
|
Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required_if_condition,max=32"`
|
||||||
|
}
|
||||||
|
|
||||||
type FooBarStruct struct {
|
type FooBarStruct struct {
|
||||||
FooStruct
|
FooStruct
|
||||||
Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"`
|
Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"`
|
||||||
@ -144,6 +150,16 @@ type FooStructForMapPtrType struct {
|
|||||||
PtrBar *map[string]interface{} `form:"ptr_bar"`
|
PtrBar *map[string]interface{} `form:"ptr_bar"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
_ = Validator.Engine().(*validator.Validate).RegisterValidationCtx(
|
||||||
|
"required_if_condition", func(ctx context.Context, fl validator.FieldLevel) bool {
|
||||||
|
if ctx.Value("condition") == true {
|
||||||
|
return !fl.Field().IsZero()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestBindingDefault(t *testing.T) {
|
func TestBindingDefault(t *testing.T) {
|
||||||
assert.Equal(t, Form, Default("GET", ""))
|
assert.Equal(t, Form, Default("GET", ""))
|
||||||
assert.Equal(t, Form, Default("GET", MIMEJSON))
|
assert.Equal(t, Form, Default("GET", MIMEJSON))
|
||||||
@ -796,6 +812,38 @@ func TestUriBinding(t *testing.T) {
|
|||||||
assert.Equal(t, map[string]interface{}(nil), not.Name)
|
assert.Equal(t, map[string]interface{}(nil), not.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUriBindingWithContext(t *testing.T) {
|
||||||
|
b := Uri
|
||||||
|
|
||||||
|
type Tag struct {
|
||||||
|
Name string `uri:"name" binding:"required_if_condition"`
|
||||||
|
}
|
||||||
|
|
||||||
|
empty := make(map[string][]string)
|
||||||
|
assert.NoError(t, b.BindUriContext(context.Background(), empty, new(Tag)))
|
||||||
|
assert.Error(t, b.BindUriContext(context.WithValue(context.Background(), "condition", true), empty, new(Tag))) // nolint
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUriBindingWithNotContextValidator(t *testing.T) {
|
||||||
|
prev := Validator
|
||||||
|
defer func() {
|
||||||
|
Validator = prev
|
||||||
|
}()
|
||||||
|
Validator = ¬ContextValidator{}
|
||||||
|
|
||||||
|
TestUriBinding(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
type notContextValidator defaultValidator
|
||||||
|
|
||||||
|
func (v *notContextValidator) ValidateStruct(obj interface{}) error {
|
||||||
|
return (*defaultValidator)(v).ValidateStruct(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *notContextValidator) Engine() interface{} {
|
||||||
|
return (*defaultValidator)(v).Engine()
|
||||||
|
}
|
||||||
|
|
||||||
func TestUriInnerBinding(t *testing.T) {
|
func TestUriInnerBinding(t *testing.T) {
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
Name string `uri:"name"`
|
Name string `uri:"name"`
|
||||||
@ -1179,7 +1227,7 @@ func testQueryBindingBoolFail(t *testing.T, method, path, badPath, body, badBody
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
|
func testBodyBinding(t *testing.T, b ContextBinding, name, path, badPath, body, badBody string) {
|
||||||
assert.Equal(t, name, b.Name())
|
assert.Equal(t, name, b.Name())
|
||||||
|
|
||||||
obj := FooStruct{}
|
obj := FooStruct{}
|
||||||
@ -1190,7 +1238,16 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody
|
|||||||
|
|
||||||
obj = FooStruct{}
|
obj = FooStruct{}
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody("POST", badPath, badBody)
|
||||||
err = JSON.Bind(req, &obj)
|
err = b.Bind(req, &obj)
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
obj2 := ConditionalFooStruct{}
|
||||||
|
req = requestWithBody("POST", path, body)
|
||||||
|
err = b.BindContext(context.Background(), req, &obj2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "bar", obj2.Foo)
|
||||||
|
|
||||||
|
err = b.BindContext(context.WithValue(context.Background(), "condition", true), req, &obj2) // nolint
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1204,7 +1261,7 @@ func testBodyBindingSlice(t *testing.T, b Binding, name, path, badPath, body, ba
|
|||||||
|
|
||||||
var obj2 []FooStruct
|
var obj2 []FooStruct
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody("POST", badPath, badBody)
|
||||||
err = JSON.Bind(req, &obj2)
|
err = b.Bind(req, &obj2)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1249,7 +1306,7 @@ func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body
|
|||||||
|
|
||||||
obj = FooStructUseNumber{}
|
obj = FooStructUseNumber{}
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody("POST", badPath, badBody)
|
||||||
err = JSON.Bind(req, &obj)
|
err = b.Bind(req, &obj)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1267,7 +1324,7 @@ func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, bod
|
|||||||
|
|
||||||
obj = FooStructUseNumber{}
|
obj = FooStructUseNumber{}
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody("POST", badPath, badBody)
|
||||||
err = JSON.Bind(req, &obj)
|
err = b.Bind(req, &obj)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1285,7 +1342,7 @@ func testBodyBindingDisallowUnknownFields(t *testing.T, b Binding, path, badPath
|
|||||||
|
|
||||||
obj = FooStructDisallowUnknownFields{}
|
obj = FooStructDisallowUnknownFields{}
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody("POST", badPath, badBody)
|
||||||
err = JSON.Bind(req, &obj)
|
err = b.Bind(req, &obj)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "what")
|
assert.Contains(t, err.Error(), "what")
|
||||||
}
|
}
|
||||||
@ -1301,7 +1358,7 @@ func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, bad
|
|||||||
|
|
||||||
obj = FooStruct{}
|
obj = FooStruct{}
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody("POST", badPath, badBody)
|
||||||
err = JSON.Bind(req, &obj)
|
err = b.Bind(req, &obj)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1318,7 +1375,7 @@ func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, ba
|
|||||||
obj = protoexample.Test{}
|
obj = protoexample.Test{}
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody("POST", badPath, badBody)
|
||||||
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
||||||
err = ProtoBuf.Bind(req, &obj)
|
err = b.Bind(req, &obj)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1349,7 +1406,7 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body
|
|||||||
obj = protoexample.Test{}
|
obj = protoexample.Test{}
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody("POST", badPath, badBody)
|
||||||
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
||||||
err = ProtoBuf.Bind(req, &obj)
|
err = b.Bind(req, &obj)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,48 +5,102 @@
|
|||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"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
|
||||||
}
|
}
|
||||||
|
|
||||||
type sliceValidateError []error
|
// SliceFieldError is returned for invalid slice or array elements.
|
||||||
|
// It extends validator.FieldError with the index of the failing element.
|
||||||
// Error concatenates all error elements in sliceValidateError into a single string separated by \n.
|
type SliceFieldError interface {
|
||||||
func (err sliceValidateError) Error() string {
|
validator.FieldError
|
||||||
n := len(err)
|
Index() int
|
||||||
switch n {
|
|
||||||
case 0:
|
|
||||||
return ""
|
|
||||||
default:
|
|
||||||
var b strings.Builder
|
|
||||||
if err[0] != nil {
|
|
||||||
fmt.Fprintf(&b, "[%d]: %s", 0, err[0].Error())
|
|
||||||
}
|
|
||||||
if n > 1 {
|
|
||||||
for i := 1; i < n; i++ {
|
|
||||||
if err[i] != nil {
|
|
||||||
b.WriteString("\n")
|
|
||||||
fmt.Fprintf(&b, "[%d]: %s", i, err[i].Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ StructValidator = &defaultValidator{}
|
type sliceFieldError struct {
|
||||||
|
validator.FieldError
|
||||||
|
index int
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.
|
func (fe sliceFieldError) Index() int {
|
||||||
|
return fe.index
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fe sliceFieldError) Error() string {
|
||||||
|
return fmt.Sprintf("[%d]: %s", fe.index, fe.FieldError.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fe sliceFieldError) Unwrap() error {
|
||||||
|
return fe.FieldError
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapFieldError is returned for invalid map values.
|
||||||
|
// It extends validator.FieldError with the key of the failing value.
|
||||||
|
type MapFieldError interface {
|
||||||
|
validator.FieldError
|
||||||
|
Key() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mapFieldError struct {
|
||||||
|
validator.FieldError
|
||||||
|
key interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fe mapFieldError) Key() interface{} {
|
||||||
|
return fe.key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fe mapFieldError) Error() string {
|
||||||
|
return fmt.Sprintf("[%v]: %s", fe.key, fe.FieldError.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fe mapFieldError) Unwrap() error {
|
||||||
|
return fe.FieldError
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ContextStructValidator = &defaultValidator{}
|
||||||
|
|
||||||
|
// ValidateStruct receives any kind of type, but validates only structs, pointers, slices, arrays, and maps.
|
||||||
func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
||||||
|
return v.ValidateStructContext(context.Background(), obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *defaultValidator) ValidateStructContext(ctx context.Context, obj interface{}) error {
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -54,30 +108,66 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
|||||||
value := reflect.ValueOf(obj)
|
value := reflect.ValueOf(obj)
|
||||||
switch value.Kind() {
|
switch value.Kind() {
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
return v.ValidateStruct(value.Elem().Interface())
|
return v.ValidateStructContext(ctx, value.Elem().Interface())
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
return v.validateStruct(obj)
|
return v.validateStruct(ctx, obj)
|
||||||
case reflect.Slice, reflect.Array:
|
case reflect.Slice, reflect.Array:
|
||||||
count := value.Len()
|
var errs validator.ValidationErrors
|
||||||
validateRet := make(sliceValidateError, 0)
|
|
||||||
for i := 0; i < count; i++ {
|
if tag, ok := validatorTags[value.Type()]; ok {
|
||||||
if err := v.ValidateStruct(value.Index(i).Interface()); err != nil {
|
if err := v.validateVar(ctx, obj, tag); err != nil {
|
||||||
validateRet = append(validateRet, err)
|
errs = append(errs, err.(validator.ValidationErrors)...) // nolint: errorlint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(validateRet) == 0 {
|
|
||||||
return nil
|
count := value.Len()
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
if err := v.ValidateStructContext(ctx, value.Index(i).Interface()); err != nil {
|
||||||
|
for _, fieldError := range err.(validator.ValidationErrors) { // nolint: errorlint
|
||||||
|
errs = append(errs, sliceFieldError{fieldError, i})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return validateRet
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case reflect.Map:
|
||||||
|
var errs validator.ValidationErrors
|
||||||
|
|
||||||
|
if tag, ok := validatorTags[value.Type()]; ok {
|
||||||
|
if err := v.validateVar(ctx, obj, tag); err != nil {
|
||||||
|
errs = append(errs, err.(validator.ValidationErrors)...) // nolint: errorlint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range value.MapKeys() {
|
||||||
|
if err := v.ValidateStructContext(ctx, value.MapIndex(key).Interface()); err != nil {
|
||||||
|
for _, fieldError := range err.(validator.ValidationErrors) { // nolint: errorlint
|
||||||
|
errs = append(errs, mapFieldError{fieldError, key.Interface()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
return nil
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateStruct receives struct type
|
// validateStruct receives struct type
|
||||||
func (v *defaultValidator) validateStruct(obj interface{}) error {
|
func (v *defaultValidator) validateStruct(ctx context.Context, obj interface{}) error {
|
||||||
v.lazyinit()
|
v.lazyinit()
|
||||||
return v.validate.Struct(obj)
|
return v.validate.StructCtx(ctx, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateStruct receives slice, array, and map types
|
||||||
|
func (v *defaultValidator) validateVar(ctx context.Context, obj interface{}, tag string) error {
|
||||||
|
v.lazyinit()
|
||||||
|
return v.validate.VarCtx(ctx, obj, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Engine returns the underlying validator engine which powers the default
|
// Engine returns the underlying validator engine which powers the default
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package binding
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func BenchmarkSliceValidateError(b *testing.B) {
|
|
||||||
const size int = 100
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
e := make(sliceValidateError, size)
|
|
||||||
for j := 0; j < size; j++ {
|
|
||||||
e[j] = errors.New(strconv.Itoa(j))
|
|
||||||
}
|
|
||||||
if len(e.Error()) == 0 {
|
|
||||||
b.Errorf("error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,43 +7,41 @@ package binding
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSliceValidateError(t *testing.T) {
|
func TestSliceFieldError(t *testing.T) {
|
||||||
tests := []struct {
|
var fe validator.FieldError = dummyFieldError{msg: "test error"}
|
||||||
name string
|
|
||||||
err sliceValidateError
|
var err SliceFieldError = sliceFieldError{fe, 10}
|
||||||
want string
|
assert.Equal(t, 10, err.Index())
|
||||||
}{
|
assert.Equal(t, "[10]: test error", err.Error())
|
||||||
{"has nil elements", sliceValidateError{errors.New("test error"), nil}, "[0]: test error"},
|
assert.Equal(t, fe, errors.Unwrap(err))
|
||||||
{"has zero elements", sliceValidateError{}, ""},
|
}
|
||||||
{"has one element", sliceValidateError{errors.New("test one error")}, "[0]: test one error"},
|
|
||||||
{"has two elements",
|
func TestMapFieldError(t *testing.T) {
|
||||||
sliceValidateError{
|
var fe validator.FieldError = dummyFieldError{msg: "test error"}
|
||||||
errors.New("first error"),
|
|
||||||
errors.New("second error"),
|
var err MapFieldError = mapFieldError{fe, "test key"}
|
||||||
},
|
assert.Equal(t, "test key", err.Key())
|
||||||
"[0]: first error\n[1]: second error",
|
assert.Equal(t, "[test key]: test error", err.Error())
|
||||||
},
|
assert.Equal(t, fe, errors.Unwrap(err))
|
||||||
{"has many elements",
|
|
||||||
sliceValidateError{
|
err = mapFieldError{fe, 123}
|
||||||
errors.New("first error"),
|
assert.Equal(t, 123, err.Key())
|
||||||
errors.New("second error"),
|
assert.Equal(t, "[123]: test error", err.Error())
|
||||||
nil,
|
assert.Equal(t, fe, errors.Unwrap(err))
|
||||||
nil,
|
}
|
||||||
nil,
|
|
||||||
errors.New("last error"),
|
type dummyFieldError struct {
|
||||||
},
|
validator.FieldError
|
||||||
"[0]: first error\n[1]: second error\n[5]: last error",
|
msg string
|
||||||
},
|
}
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
func (fe dummyFieldError) Error() string {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
return fe.msg
|
||||||
if got := tt.err.Error(); got != tt.want {
|
|
||||||
t.Errorf("sliceValidateError.Error() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultValidator(t *testing.T) {
|
func TestDefaultValidator(t *testing.T) {
|
||||||
@ -77,6 +75,18 @@ func TestDefaultValidator(t *testing.T) {
|
|||||||
{"validate *[]*struct failed-1", &defaultValidator{}, &[]*exampleStruct{{A: "123456789", B: 1}}, true},
|
{"validate *[]*struct failed-1", &defaultValidator{}, &[]*exampleStruct{{A: "123456789", B: 1}}, true},
|
||||||
{"validate *[]*struct failed-2", &defaultValidator{}, &[]*exampleStruct{{A: "12345678", B: 0}}, true},
|
{"validate *[]*struct failed-2", &defaultValidator{}, &[]*exampleStruct{{A: "12345678", B: 0}}, true},
|
||||||
{"validate *[]*struct passed", &defaultValidator{}, &[]*exampleStruct{{A: "12345678", B: 1}}, false},
|
{"validate *[]*struct passed", &defaultValidator{}, &[]*exampleStruct{{A: "12345678", B: 1}}, false},
|
||||||
|
{"validate map[string]struct failed-1", &defaultValidator{}, map[string]exampleStruct{"x": {A: "123456789", B: 1}}, true},
|
||||||
|
{"validate map[string]struct failed-2", &defaultValidator{}, map[string]exampleStruct{"x": {A: "12345678", B: 0}}, true},
|
||||||
|
{"validate map[string]struct passed", &defaultValidator{}, map[string]exampleStruct{"x": {A: "12345678", B: 1}}, false},
|
||||||
|
{"validate map[string]*struct failed-1", &defaultValidator{}, map[string]*exampleStruct{"x": {A: "123456789", B: 1}}, true},
|
||||||
|
{"validate map[string]*struct failed-2", &defaultValidator{}, map[string]*exampleStruct{"x": {A: "12345678", B: 0}}, true},
|
||||||
|
{"validate map[string]*struct passed", &defaultValidator{}, map[string]*exampleStruct{"x": {A: "12345678", B: 1}}, false},
|
||||||
|
{"validate *map[string]struct failed-1", &defaultValidator{}, &map[string]exampleStruct{"x": {A: "123456789", B: 1}}, true},
|
||||||
|
{"validate *map[string]struct failed-2", &defaultValidator{}, &map[string]exampleStruct{"x": {A: "12345678", B: 0}}, true},
|
||||||
|
{"validate *map[string]struct passed", &defaultValidator{}, &map[string]exampleStruct{"x": {A: "12345678", B: 1}}, false},
|
||||||
|
{"validate *map[string]*struct failed-1", &defaultValidator{}, &map[string]*exampleStruct{"x": {A: "123456789", B: 1}}, true},
|
||||||
|
{"validate *map[string]*struct failed-2", &defaultValidator{}, &map[string]*exampleStruct{"x": {A: "12345678", B: 0}}, true},
|
||||||
|
{"validate *map[string]*struct passed", &defaultValidator{}, &map[string]*exampleStruct{"x": {A: "12345678", B: 1}}, false},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@ -86,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
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
@ -19,7 +20,11 @@ func (formBinding) Name() string {
|
|||||||
return "form"
|
return "form"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (formBinding) Bind(req *http.Request, obj interface{}) error {
|
func (b formBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
|
return b.BindContext(context.Background(), req, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formBinding) BindContext(ctx context.Context, req *http.Request, obj interface{}) error {
|
||||||
if err := req.ParseForm(); err != nil {
|
if err := req.ParseForm(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -29,34 +34,41 @@ func (formBinding) Bind(req *http.Request, obj interface{}) error {
|
|||||||
if err := mapForm(obj, req.Form); err != nil {
|
if err := mapForm(obj, req.Form); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return validate(obj)
|
return validateContext(ctx, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (formPostBinding) Name() string {
|
func (formPostBinding) Name() string {
|
||||||
return "form-urlencoded"
|
return "form-urlencoded"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (formPostBinding) Bind(req *http.Request, obj interface{}) error {
|
func (b formPostBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
|
return b.BindContext(context.Background(), req, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formPostBinding) BindContext(ctx context.Context, req *http.Request, obj interface{}) error {
|
||||||
if err := req.ParseForm(); err != nil {
|
if err := req.ParseForm(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := mapForm(obj, req.PostForm); err != nil {
|
if err := mapForm(obj, req.PostForm); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return validate(obj)
|
return validateContext(ctx, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (formMultipartBinding) Name() string {
|
func (formMultipartBinding) Name() string {
|
||||||
return "multipart/form-data"
|
return "multipart/form-data"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error {
|
func (b formMultipartBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
|
return b.BindContext(context.Background(), req, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formMultipartBinding) BindContext(ctx context.Context, req *http.Request, obj interface{}) error {
|
||||||
if err := req.ParseMultipartForm(defaultMemory); err != nil {
|
if err := req.ParseMultipartForm(defaultMemory); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := mappingByPtr(obj, (*multipartRequest)(req), "form"); err != nil {
|
if err := mappingByPtr(obj, (*multipartRequest)(req), "form"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return validateContext(ctx, obj)
|
||||||
return validate(obj)
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -12,13 +13,15 @@ func (headerBinding) Name() string {
|
|||||||
return "header"
|
return "header"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (headerBinding) Bind(req *http.Request, obj interface{}) error {
|
func (b headerBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
|
return b.BindContext(context.Background(), req, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (headerBinding) BindContext(ctx context.Context, req *http.Request, obj interface{}) error {
|
||||||
if err := mapHeader(obj, req.Header); err != nil {
|
if err := mapHeader(obj, req.Header); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return validateContext(ctx, obj)
|
||||||
return validate(obj)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapHeader(ptr interface{}, h map[string][]string) error {
|
func mapHeader(ptr interface{}, h map[string][]string) error {
|
||||||
|
@ -6,6 +6,7 @@ package binding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -30,18 +31,26 @@ func (jsonBinding) Name() string {
|
|||||||
return "json"
|
return "json"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jsonBinding) Bind(req *http.Request, obj interface{}) error {
|
func (b jsonBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
|
return b.BindContext(context.Background(), req, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jsonBinding) BindContext(ctx context.Context, req *http.Request, obj interface{}) error {
|
||||||
if req == nil || req.Body == nil {
|
if req == nil || req.Body == nil {
|
||||||
return errors.New("invalid request")
|
return errors.New("invalid request")
|
||||||
}
|
}
|
||||||
return decodeJSON(req.Body, obj)
|
return decodeJSON(ctx, req.Body, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jsonBinding) BindBody(body []byte, obj interface{}) error {
|
func (b jsonBinding) BindBody(body []byte, obj interface{}) error {
|
||||||
return decodeJSON(bytes.NewReader(body), obj)
|
return b.BindBodyContext(context.Background(), body, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeJSON(r io.Reader, obj interface{}) error {
|
func (jsonBinding) BindBodyContext(ctx context.Context, body []byte, obj interface{}) error {
|
||||||
|
return decodeJSON(ctx, bytes.NewReader(body), obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeJSON(ctx context.Context, r io.Reader, obj interface{}) error {
|
||||||
decoder := json.NewDecoder(r)
|
decoder := json.NewDecoder(r)
|
||||||
if EnableDecoderUseNumber {
|
if EnableDecoderUseNumber {
|
||||||
decoder.UseNumber()
|
decoder.UseNumber()
|
||||||
@ -52,5 +61,5 @@ func decodeJSON(r io.Reader, obj interface{}) error {
|
|||||||
if err := decoder.Decode(obj); err != nil {
|
if err := decoder.Decode(obj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return validate(obj)
|
return validateContext(ctx, obj)
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ package binding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@ -21,18 +22,26 @@ func (msgpackBinding) Name() string {
|
|||||||
return "msgpack"
|
return "msgpack"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msgpackBinding) Bind(req *http.Request, obj interface{}) error {
|
func (b msgpackBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
return decodeMsgPack(req.Body, obj)
|
return b.BindContext(context.Background(), req, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msgpackBinding) BindBody(body []byte, obj interface{}) error {
|
func (msgpackBinding) BindContext(ctx context.Context, req *http.Request, obj interface{}) error {
|
||||||
return decodeMsgPack(bytes.NewReader(body), obj)
|
return decodeMsgPack(ctx, req.Body, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeMsgPack(r io.Reader, obj interface{}) error {
|
func (b msgpackBinding) BindBody(body []byte, obj interface{}) error {
|
||||||
|
return b.BindBodyContext(context.Background(), body, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msgpackBinding) BindBodyContext(ctx context.Context, body []byte, obj interface{}) error {
|
||||||
|
return decodeMsgPack(ctx, bytes.NewReader(body), obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeMsgPack(ctx context.Context, r io.Reader, obj interface{}) error {
|
||||||
cdc := new(codec.MsgpackHandle)
|
cdc := new(codec.MsgpackHandle)
|
||||||
if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil {
|
if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return validate(obj)
|
return validateContext(ctx, obj)
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,10 @@
|
|||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
|
||||||
import "net/http"
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
type queryBinding struct{}
|
type queryBinding struct{}
|
||||||
|
|
||||||
@ -12,10 +15,14 @@ func (queryBinding) Name() string {
|
|||||||
return "query"
|
return "query"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (queryBinding) Bind(req *http.Request, obj interface{}) error {
|
func (b queryBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
|
return b.BindContext(context.Background(), req, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (queryBinding) BindContext(ctx context.Context, req *http.Request, obj interface{}) error {
|
||||||
values := req.URL.Query()
|
values := req.URL.Query()
|
||||||
if err := mapForm(obj, values); err != nil {
|
if err := mapForm(obj, values); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return validate(obj)
|
return validateContext(ctx, obj)
|
||||||
}
|
}
|
||||||
|
@ -4,15 +4,21 @@
|
|||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
type uriBinding struct{}
|
type uriBinding struct{}
|
||||||
|
|
||||||
func (uriBinding) Name() string {
|
func (uriBinding) Name() string {
|
||||||
return "uri"
|
return "uri"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uriBinding) BindUri(m map[string][]string, obj interface{}) error {
|
func (b uriBinding) BindUri(m map[string][]string, obj interface{}) error {
|
||||||
|
return b.BindUriContext(context.Background(), m, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uriBinding) BindUriContext(ctx context.Context, m map[string][]string, obj interface{}) error {
|
||||||
if err := mapURI(obj, m); err != nil {
|
if err := mapURI(obj, m); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return validate(obj)
|
return validateContext(ctx, obj)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ package binding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -226,3 +227,7 @@ func TestValidatorEngine(t *testing.T) {
|
|||||||
// Check that the error matches expectation
|
// Check that the error matches expectation
|
||||||
assert.Error(t, errs, "", "", "notone")
|
assert.Error(t, errs, "", "", "notone")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validate(obj interface{}) error {
|
||||||
|
return validateContext(context.Background(), obj)
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ package binding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -17,17 +18,26 @@ func (xmlBinding) Name() string {
|
|||||||
return "xml"
|
return "xml"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (xmlBinding) Bind(req *http.Request, obj interface{}) error {
|
func (b xmlBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
return decodeXML(req.Body, obj)
|
return b.BindContext(context.Background(), req, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (xmlBinding) BindBody(body []byte, obj interface{}) error {
|
func (xmlBinding) BindContext(ctx context.Context, req *http.Request, obj interface{}) error {
|
||||||
return decodeXML(bytes.NewReader(body), obj)
|
return decodeXML(ctx, req.Body, obj)
|
||||||
}
|
}
|
||||||
func decodeXML(r io.Reader, obj interface{}) error {
|
|
||||||
|
func (b xmlBinding) BindBody(body []byte, obj interface{}) error {
|
||||||
|
return b.BindBodyContext(context.Background(), body, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (xmlBinding) BindBodyContext(ctx context.Context, body []byte, obj interface{}) error {
|
||||||
|
return decodeXML(ctx, bytes.NewReader(body), obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeXML(ctx context.Context, r io.Reader, obj interface{}) error {
|
||||||
decoder := xml.NewDecoder(r)
|
decoder := xml.NewDecoder(r)
|
||||||
if err := decoder.Decode(obj); err != nil {
|
if err := decoder.Decode(obj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return validate(obj)
|
return validateContext(ctx, obj)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ package binding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@ -18,18 +19,26 @@ func (yamlBinding) Name() string {
|
|||||||
return "yaml"
|
return "yaml"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (yamlBinding) Bind(req *http.Request, obj interface{}) error {
|
func (b yamlBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
return decodeYAML(req.Body, obj)
|
return b.BindContext(context.Background(), req, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (yamlBinding) BindBody(body []byte, obj interface{}) error {
|
func (yamlBinding) BindContext(ctx context.Context, req *http.Request, obj interface{}) error {
|
||||||
return decodeYAML(bytes.NewReader(body), obj)
|
return decodeYAML(ctx, req.Body, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeYAML(r io.Reader, obj interface{}) error {
|
func (b yamlBinding) BindBody(body []byte, obj interface{}) error {
|
||||||
|
return b.BindBodyContext(context.Background(), body, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (yamlBinding) BindBodyContext(ctx context.Context, body []byte, obj interface{}) error {
|
||||||
|
return decodeYAML(ctx, bytes.NewReader(body), obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeYAML(ctx context.Context, r io.Reader, obj interface{}) error {
|
||||||
decoder := yaml.NewDecoder(r)
|
decoder := yaml.NewDecoder(r)
|
||||||
if err := decoder.Decode(obj); err != nil {
|
if err := decoder.Decode(obj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return validate(obj)
|
return validateContext(ctx, obj)
|
||||||
}
|
}
|
||||||
|
@ -704,12 +704,15 @@ func (c *Context) ShouldBindUri(obj interface{}) error {
|
|||||||
for _, v := range c.Params {
|
for _, v := range c.Params {
|
||||||
m[v.Key] = []string{v.Value}
|
m[v.Key] = []string{v.Value}
|
||||||
}
|
}
|
||||||
return binding.Uri.BindUri(m, obj)
|
return binding.Uri.BindUriContext(c, m, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldBindWith binds the passed struct pointer using the specified binding engine.
|
// ShouldBindWith binds the passed struct pointer using the specified binding engine.
|
||||||
// See the binding package.
|
// See the binding package.
|
||||||
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
|
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
|
||||||
|
if b, ok := b.(binding.ContextBinding); ok {
|
||||||
|
return b.BindContext(c, c.Request, obj)
|
||||||
|
}
|
||||||
return b.Bind(c.Request, obj)
|
return b.Bind(c.Request, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -732,6 +735,9 @@ func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (e
|
|||||||
}
|
}
|
||||||
c.Set(BodyBytesKey, body)
|
c.Set(BodyBytesKey, body)
|
||||||
}
|
}
|
||||||
|
if bb, ok := bb.(binding.ContextBindingBody); ok {
|
||||||
|
return bb.BindBodyContext(c, body, obj)
|
||||||
|
}
|
||||||
return bb.BindBody(body, obj)
|
return bb.BindBody(body, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
118
context_test.go
118
context_test.go
@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/gin-contrib/sse"
|
"github.com/gin-contrib/sse"
|
||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
@ -36,6 +37,16 @@ var _ context.Context = &Context{}
|
|||||||
// BAD case: func (c *Context) Render(code int, render render.Render, obj ...interface{}) {
|
// BAD case: func (c *Context) Render(code int, render render.Render, obj ...interface{}) {
|
||||||
// test that information is not leaked when reusing Contexts (using the Pool)
|
// test that information is not leaked when reusing Contexts (using the Pool)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
_ = binding.Validator.Engine().(*validator.Validate).RegisterValidationCtx(
|
||||||
|
"required_if_condition", func(ctx context.Context, fl validator.FieldLevel) bool {
|
||||||
|
if ctx.Value("condition") == true {
|
||||||
|
return !fl.Field().IsZero()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func createMultipartRequest() *http.Request {
|
func createMultipartRequest() *http.Request {
|
||||||
boundary := "--testboundary"
|
boundary := "--testboundary"
|
||||||
body := new(bytes.Buffer)
|
body := new(bytes.Buffer)
|
||||||
@ -1543,6 +1554,27 @@ func TestContextBindWithJSON(t *testing.T) {
|
|||||||
assert.Equal(t, 0, w.Body.Len())
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextBindWithJSONContextual(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"bar\":\"foo\"}"))
|
||||||
|
c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type
|
||||||
|
|
||||||
|
var obj struct {
|
||||||
|
Foo string `json:"foo" binding:"required_if_condition"`
|
||||||
|
Bar string `json:"bar"`
|
||||||
|
}
|
||||||
|
c.Set("condition", true)
|
||||||
|
assert.Error(t, c.BindJSON(&obj))
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
|
||||||
|
assert.NoError(t, c.BindJSON(&obj))
|
||||||
|
assert.Equal(t, "foo", obj.Bar)
|
||||||
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextBindWithXML(t *testing.T) {
|
func TestContextBindWithXML(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
@ -1672,6 +1704,92 @@ func TestContextShouldBindWithJSON(t *testing.T) {
|
|||||||
assert.Equal(t, 0, w.Body.Len())
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextShouldBindWithJSONContextual(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"bar\":\"foo\"}"))
|
||||||
|
c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type
|
||||||
|
|
||||||
|
var obj struct {
|
||||||
|
Foo string `json:"foo" binding:"required_if_condition"`
|
||||||
|
Bar string `json:"bar"`
|
||||||
|
}
|
||||||
|
c.Set("condition", true)
|
||||||
|
assert.Error(t, c.ShouldBindJSON(&obj))
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
|
||||||
|
assert.NoError(t, c.ShouldBindJSON(&obj))
|
||||||
|
assert.Equal(t, "foo", obj.Bar)
|
||||||
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextShouldBindBodyWithJSONContextual(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
var obj struct {
|
||||||
|
Foo string `json:"foo" binding:"required_if_condition"`
|
||||||
|
Bar string `json:"bar"`
|
||||||
|
}
|
||||||
|
c.Set("condition", true)
|
||||||
|
c.Set(BodyBytesKey, []byte("{\"bar\":\"foo\"}"))
|
||||||
|
assert.Error(t, c.ShouldBindBodyWith(&obj, binding.JSON))
|
||||||
|
|
||||||
|
c.Set(BodyBytesKey, []byte("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
|
||||||
|
assert.NoError(t, c.ShouldBindBodyWith(&obj, binding.JSON))
|
||||||
|
assert.Equal(t, "foo", obj.Bar)
|
||||||
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextShouldBindWithNotContextBinding(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
|
||||||
|
c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type
|
||||||
|
|
||||||
|
var obj struct {
|
||||||
|
Foo string `json:"foo" binding:"required_if_condition"`
|
||||||
|
Bar string `json:"bar"`
|
||||||
|
}
|
||||||
|
assert.NoError(t, c.ShouldBindWith(&obj, notContextBinding{}))
|
||||||
|
assert.Equal(t, "foo", obj.Bar)
|
||||||
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextShouldBindBodyWithNotContextBinding(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
var obj struct {
|
||||||
|
Foo string `json:"foo"`
|
||||||
|
Bar string `json:"bar"`
|
||||||
|
}
|
||||||
|
c.Set(BodyBytesKey, []byte("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
|
||||||
|
assert.NoError(t, c.ShouldBindBodyWith(&obj, notContextBinding{}))
|
||||||
|
assert.Equal(t, "foo", obj.Bar)
|
||||||
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
type notContextBinding struct{}
|
||||||
|
|
||||||
|
func (notContextBinding) Name() string {
|
||||||
|
return binding.JSON.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b notContextBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
|
return binding.JSON.Bind(req, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b notContextBinding) BindBody(body []byte, obj interface{}) error {
|
||||||
|
return binding.JSON.BindBody(body, obj)
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextShouldBindWithXML(t *testing.T) {
|
func TestContextShouldBindWithXML(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user