Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ func (b *Bind) WithAutoHandling() *Bind {
return b
}

// SkipValidation enables or disables struct validation for the current bind chain.
func (b *Bind) SkipValidation(skip bool) *Bind {
b.skipValidation = skip

return b
}

// Check WithAutoHandling/WithoutAutoHandling errors and return it by usage.
func (b *Bind) returnErr(err error) error {
if err == nil || b.dontHandleErrs {
Expand All @@ -97,12 +104,27 @@ func (b *Bind) validateStruct(out any) error {
if b.skipValidation {
return nil
}

validator := b.ctx.App().config.StructValidator
if validator != nil {
return validator.Validate(out)
if validator == nil {
return nil
}

t := reflect.TypeOf(out)
if t == nil {
return nil
}

// Unwrap pointers (e.g. *T, **T) to inspect the underlying destination type.
for t.Kind() == reflect.Ptr {
t = t.Elem()
}

if t.Kind() != reflect.Struct {
return nil
}

return nil
return validator.Validate(out)
}

// Custom To use custom binders, you have to use this method.
Expand Down
166 changes: 166 additions & 0 deletions bind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1873,6 +1873,172 @@ type simpleQuery struct {
Name string `query:"name" json:"name"`
}

type countingStructValidator struct {
calls int
}

func (v *countingStructValidator) Validate(_ any) error {
v.calls++

return nil
}

// go test -run Test_Bind_Form_Map_SkipsStructValidator
func Test_Bind_Form_Map_SkipsStructValidator(t *testing.T) {
t.Parallel()

makeRequest := func(c Ctx) {
body := []byte("name=john")
c.Request().SetBody(body)
c.Request().Header.SetContentType(MIMEApplicationForm)
c.Request().Header.SetContentLength(len(body))
}

t.Run("without validator", func(t *testing.T) {
t.Parallel()

app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
t.Cleanup(func() {
app.ReleaseCtx(c)
})

makeRequest(c)
req := make(map[string]string)
require.NoError(t, c.Bind().Form(&req))
require.Equal(t, "john", req["name"])
})

t.Run("with struct validator configured", func(t *testing.T) {
t.Parallel()

validator := &countingStructValidator{}
app := New(Config{StructValidator: validator})
c := app.AcquireCtx(&fasthttp.RequestCtx{})
t.Cleanup(func() {
app.ReleaseCtx(c)
})

makeRequest(c)
req := make(map[string]string)
require.NoError(t, c.Bind().Form(&req))
require.Equal(t, "john", req["name"])
require.Equal(t, 0, validator.calls)
})
}

// go test -run Test_Bind_SkipValidation
func Test_Bind_SkipValidation(t *testing.T) {
t.Parallel()

app := New(Config{StructValidator: &structValidator{}})

t.Run("validation enabled", func(t *testing.T) {
t.Parallel()

c := app.AcquireCtx(&fasthttp.RequestCtx{})
t.Cleanup(func() {
app.ReleaseCtx(c)
})

rq := new(simpleQuery)
c.Request().URI().SetQueryString("name=efe")
require.Equal(t, "you should have entered right name", c.Bind().SkipValidation(false).Query(rq).Error())
})

t.Run("validation skipped", func(t *testing.T) {
t.Parallel()

c := app.AcquireCtx(&fasthttp.RequestCtx{})
t.Cleanup(func() {
app.ReleaseCtx(c)
})

rq := new(simpleQuery)
c.Request().URI().SetQueryString("name=efe")
require.NoError(t, c.Bind().SkipValidation(true).Query(rq))
require.Equal(t, "efe", rq.Name)
})
}

// go test -run Test_Bind_SkipValidation_Usage
func Test_Bind_SkipValidation_Usage(t *testing.T) {
t.Parallel()

app := New(Config{StructValidator: &structValidator{}})

t.Run("skip validation for json", func(t *testing.T) {
t.Parallel()

c := app.AcquireCtx(&fasthttp.RequestCtx{})
t.Cleanup(func() {
app.ReleaseCtx(c)
})

body := []byte(`{"name":"efe"}`)
c.Request().SetBody(body)
c.Request().Header.SetContentType(MIMEApplicationJSON)
c.Request().Header.SetContentLength(len(body))

req := new(simpleQuery)
require.NoError(t, c.Bind().SkipValidation(true).JSON(req))
require.Equal(t, "efe", req.Name)
})

t.Run("re-enable validation explicitly", func(t *testing.T) {
t.Parallel()

c := app.AcquireCtx(&fasthttp.RequestCtx{})
t.Cleanup(func() {
app.ReleaseCtx(c)
})

body := []byte(`{"name":"efe"}`)
c.Request().SetBody(body)
c.Request().Header.SetContentType(MIMEApplicationJSON)
c.Request().Header.SetContentLength(len(body))

req := new(simpleQuery)
require.EqualError(t, c.Bind().SkipValidation(false).JSON(req), "you should have entered right name")
})

t.Run("toggle on same bind instance", func(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
t.Cleanup(func() {
app.ReleaseCtx(c)
})

body := []byte(`{"name":"efe"}`)
c.Request().SetBody(body)
c.Request().Header.SetContentType(MIMEApplicationJSON)
c.Request().Header.SetContentLength(len(body))

req := new(simpleQuery)
require.NoError(t, c.Bind().SkipValidation(true).JSON(req))

c.Request().SetBody(body)
c.Request().Header.SetContentType(MIMEApplicationJSON)
c.Request().Header.SetContentLength(len(body))
req = new(simpleQuery)
require.EqualError(t, c.Bind().SkipValidation(false).JSON(req), "you should have entered right name")
})
}

// go test -run Test_Bind_ValidateStruct_NilTarget
func Test_Bind_ValidateStruct_NilTarget(t *testing.T) {
t.Parallel()

validator := &countingStructValidator{}
app := New(Config{StructValidator: validator})
c := app.AcquireCtx(&fasthttp.RequestCtx{})
t.Cleanup(func() {
app.ReleaseCtx(c)
})

require.NoError(t, c.Bind().validateStruct(nil))
require.Equal(t, 0, validator.calls)
}

// go test -run Test_Bind_StructValidator
func Test_Bind_StructValidator(t *testing.T) {
app := New(Config{StructValidator: &structValidator{}})
Expand Down
12 changes: 12 additions & 0 deletions docs/api/bind.md
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,15 @@ It's the default behavior of the binder.
func (b *Bind) WithoutAutoHandling() *Bind
```

### SkipValidation

To enable or disable validation for the current bind chain, use `SkipValidation`.
By default, validation is enabled (`skip = false`).

```go title="Signature"
func (b *Bind) SkipValidation(skip bool) *Bind
```

## SetParserDecoder

Allows you to configure the BodyParser/QueryParser decoder based on schema options, providing the possibility to add custom types for parsing.
Expand Down Expand Up @@ -707,6 +716,9 @@ app := fiber.New(fiber.Config{
})
```

Fiber only runs `StructValidator` for struct destinations (or pointers to structs).
Binding into maps and other non-struct types skips the validator step.

### Usage of Validation in Binding Methods

```go title="Example"
Expand Down
4 changes: 4 additions & 0 deletions docs/guide/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ app := fiber.New(fiber.Config{
StructValidator: &structValidator{validate: validator.New()},
})

// Note:
// StructValidator runs only for struct destinations (or pointers to structs).
// Binding into maps and other non-struct types skips validation.

type User struct {
Name string `json:"name" form:"name" query:"name" validate:"required"`
Age int `json:"age" form:"age" query:"age" validate:"gte=0,lte=100"`
Expand Down
Loading