diff --git a/bind.go b/bind.go index eff595cd72..ad76594265 100644 --- a/bind.go +++ b/bind.go @@ -19,34 +19,35 @@ type StructValidator interface { // Bind struct type Bind struct { - ctx Ctx - should bool + ctx Ctx + dontHandleErrs bool } -// Should To handle binder errors manually, you can prefer Should method. +// If you want to handle binder errors manually, you can use `WithoutAutoHandling`. // It's default behavior of binder. -func (b *Bind) Should() *Bind { - b.should = true +func (b *Bind) WithoutAutoHandling() *Bind { + b.dontHandleErrs = true return b } -// Must If you want to handle binder errors automatically, you can use Must. -// If there's an error it'll return error and 400 as HTTP status. -func (b *Bind) Must() *Bind { - b.should = false +// If you want to handle binder errors automatically, you can use `WithAutoHandling`. +// If there's an error, it will return the error and set HTTP status to `400 Bad Request`. +// You must still return on error explicitly +func (b *Bind) WithAutoHandling() *Bind { + b.dontHandleErrs = false return b } -// Check Should/Must errors and return it by usage. +// Check WithAutoHandling/WithoutAutoHandling errors and return it by usage. func (b *Bind) returnErr(err error) error { - if !b.should { - b.ctx.Status(StatusBadRequest) - return NewError(StatusBadRequest, "Bad request: "+err.Error()) + if err == nil || b.dontHandleErrs { + return err } - return err + b.ctx.Status(StatusBadRequest) + return NewError(StatusBadRequest, "Bad request: "+err.Error()) } // Struct validation. @@ -62,7 +63,7 @@ func (b *Bind) validateStruct(out any) error { // Custom To use custom binders, you have to use this method. // You can register them from RegisterCustomBinder method of Fiber instance. // They're checked by name, if it's not found, it will return an error. -// NOTE: Should/Must is still valid for Custom binders. +// NOTE: WithAutoHandling/WithAutoHandling is still valid for Custom binders. func (b *Bind) Custom(name string, dest any) error { binders := b.ctx.App().customBinders for _, customBinder := range binders { @@ -92,7 +93,7 @@ func (b *Bind) RespHeader(out any) error { return b.validateStruct(out) } -// Cookie binds the requesr cookie strings into the struct, map[string]string and map[string][]string. +// Cookie binds the request cookie strings into the struct, map[string]string and map[string][]string. // NOTE: If your cookie is like key=val1,val2; they'll be binded as an slice if your map is map[string][]string. Else, it'll use last element of cookie. func (b *Bind) Cookie(out any) error { if err := b.returnErr(binder.CookieBinder.Bind(b.ctx.RequestCtx(), out)); err != nil { diff --git a/bind_test.go b/bind_test.go index aa00e191ca..a75722b3b4 100644 --- a/bind_test.go +++ b/bind_test.go @@ -19,6 +19,15 @@ import ( const helloWorld = "hello world" +// go test -run Test_returnErr -v +func Test_returnErr(t *testing.T) { + app := New() + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + + err := c.Bind().WithAutoHandling().returnErr(nil) + require.NoError(t, err) +} + // go test -run Test_Bind_Query -v func Test_Bind_Query(t *testing.T) { t.Parallel() @@ -1616,8 +1625,8 @@ func Test_Bind_CustomBinder(t *testing.T) { require.Equal(t, "john", d.Name) } -// go test -run Test_Bind_Must -func Test_Bind_Must(t *testing.T) { +// go test -run Test_Bind_WithAutoHandling +func Test_Bind_WithAutoHandling(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) @@ -1626,7 +1635,7 @@ func Test_Bind_Must(t *testing.T) { } rq := new(RequiredQuery) c.Request().URI().SetQueryString("") - err := c.Bind().Must().Query(rq) + err := c.Bind().WithAutoHandling().Query(rq) require.Equal(t, StatusBadRequest, c.Response().StatusCode()) require.Equal(t, "Bad request: bind: name is empty", err.Error()) } diff --git a/binder/README.md b/binder/README.md index 676e1c9e89..6794a3b93b 100644 --- a/binder/README.md +++ b/binder/README.md @@ -1,17 +1,19 @@ # Fiber Binders -Binder is a new request/response binding feature for Fiber. Against the old Fiber parsers, it supports custom binder registration, struct validation, `map[string]string`, `map[string][]string`, and more. It's introduced in Fiber v3 and a replacement of: +**Binder** is a new request/response binding feature for Fiber introduced in Fiber v3. It replaces the old Fiber parsers and offers enhanced capabilities such as custom binder registration, struct validation, support for `map[string]string`, `map[string][]string`, and more. Binder replaces the following components: -- BodyParser -- ParamsParser -- GetReqHeaders -- GetRespHeaders -- AllParams -- QueryParser -- ReqHeaderParser +- `BodyParser` +- `ParamsParser` +- `GetReqHeaders` +- `GetRespHeaders` +- `AllParams` +- `QueryParser` +- `ReqHeaderParser` ## Default Binders +Fiber provides several default binders out of the box: + - [Form](form.go) - [Query](query.go) - [URI](uri.go) @@ -23,12 +25,12 @@ Binder is a new request/response binding feature for Fiber. Against the old Fibe ## Guides -### Binding into the Struct +### Binding into a Struct -Fiber supports binding into the struct with [gorilla/schema](https://github.com/gorilla/schema). Here's an example: +Fiber supports binding request data directly into a struct using [gorilla/schema](https://github.com/gorilla/schema). Here's an example: ```go -// Field names should start with an uppercase letter +// Field names must start with an uppercase letter type Person struct { Name string `json:"name" xml:"name" form:"name"` Pass string `json:"pass" xml:"pass" form:"pass"` @@ -41,56 +43,63 @@ app.Post("/", func(c fiber.Ctx) error { return err } - log.Println(p.Name) // john - log.Println(p.Pass) // doe + log.Println(p.Name) // Output: john + log.Println(p.Pass) // Output: doe - // ... + // Additional logic... }) // Run tests with the following curl commands: -// curl -X POST -H "Content-Type: application/json" --data "{\"name\":\"john\",\"pass\":\"doe\"}" localhost:3000 +// JSON +curl -X POST -H "Content-Type: application/json" --data "{\"name\":\"john\",\"pass\":\"doe\"}" localhost:3000 -// curl -X POST -H "Content-Type: application/xml" --data "johndoe" localhost:3000 +// XML +curl -X POST -H "Content-Type: application/xml" --data "johndoe" localhost:3000 -// curl -X POST -H "Content-Type: application/x-www-form-urlencoded" --data "name=john&pass=doe" localhost:3000 +// URL-Encoded Form +curl -X POST -H "Content-Type: application/x-www-form-urlencoded" --data "name=john&pass=doe" localhost:3000 -// curl -X POST -F name=john -F pass=doe http://localhost:3000 +// Multipart Form +curl -X POST -F name=john -F pass=doe http://localhost:3000 -// curl -X POST "http://localhost:3000/?name=john&pass=doe" +// Query Parameters +curl -X POST "http://localhost:3000/?name=john&pass=doe" ``` -### Binding into the Map +### Binding into a Map -Fiber supports binding into the `map[string]string` or `map[string][]string`. Here's an example: +Fiber allows binding request data into a `map[string]string` or `map[string][]string`. Here's an example: ```go app.Get("/", func(c fiber.Ctx) error { - p := make(map[string][]string) + params := make(map[string][]string) - if err := c.Bind().Query(p); err != nil { + if err := c.Bind().Query(params); err != nil { return err } - log.Println(p["name"]) // john - log.Println(p["pass"]) // doe - log.Println(p["products"]) // [shoe, hat] + log.Println(params["name"]) // Output: [john] + log.Println(params["pass"]) // Output: [doe] + log.Println(params["products"]) // Output: [shoe hat] - // ... + // Additional logic... + return nil }) + // Run tests with the following curl command: -// curl "http://localhost:3000/?name=john&pass=doe&products=shoe,hat" +curl "http://localhost:3000/?name=john&pass=doe&products=shoe&products=hat" ``` -### Behaviors of Should/Must +### Automatic Error Handling with `WithAutoHandling` -Normally, Fiber returns binder error directly. However; if you want to handle it automatically, you can prefer `Must()`. +By default, Fiber returns binder errors directly. To handle errors automatically and return a `400 Bad Request` status, use the `WithAutoHandling()` method. -If there's an error it'll return error and 400 as HTTP status. Here's an example for it: +**Example:** ```go -// Field names should start with an uppercase letter +// Field names must start with an uppercase letter type Person struct { Name string `json:"name,required"` Pass string `json:"pass"` @@ -99,23 +108,24 @@ type Person struct { app.Get("/", func(c fiber.Ctx) error { p := new(Person) - if err := c.Bind().Must().JSON(p); err != nil { + if err := c.Bind().WithAutoHandling().JSON(p); err != nil { return err - // Status code: 400 + // Automatically returns status code 400 // Response: Bad request: name is empty } - // ... + // Additional logic... + return nil }) // Run tests with the following curl command: -// curl -X GET -H "Content-Type: application/json" --data "{\"pass\":\"doe\"}" localhost:3000 +curl -X GET -H "Content-Type: application/json" --data "{\"pass\":\"doe\"}" localhost:3000 ``` -### Defining Custom Binder +### Defining a Custom Binder -We didn't add much binder to make Fiber codebase minimal. If you want to use your own binders, it's easy to register and use them. Here's an example for TOML binder. +Fiber maintains a minimal codebase by not including every possible binder. If you need to use a custom binder, you can easily register and utilize it. Here's an example of creating a `toml` binder. ```go type Person struct { @@ -147,24 +157,26 @@ func main() { return err } - // or you can use like: + // Alternatively, specify the custom binder: // if err := c.Bind().Custom("toml", out); err != nil { - // return err + // return err // } - return c.SendString(out.Pass) // test + return c.SendString(out.Pass) // Output: test }) app.Listen(":3000") } -// curl -X GET -H "Content-Type: application/toml" --data "name = 'bar' -// pass = 'test'" localhost:3000 +// Run tests with the following curl command: + +curl -X GET -H "Content-Type: application/toml" --data "name = 'bar' +pass = 'test'" localhost:3000 ``` -### Defining Custom Validator +### Defining a Custom Validator -All Fiber binders supporting struct validation if you defined validator inside of the config. You can create own validator, or use [go-playground/validator](https://github.com/go-playground/validator), [go-ozzo/ozzo-validation](https://github.com/go-ozzo/ozzo-validation)... Here's an example of simple custom validator: +All Fiber binders support struct validation if a validator is defined in the configuration. You can create your own validator or use existing ones like [go-playground/validator](https://github.com/go-playground/validator) or [go-ozzo/ozzo-validation](https://github.com/go-ozzo/ozzo-validation). Here's an example of a simple custom validator: ```go type Query struct { @@ -174,27 +186,29 @@ type Query struct { type structValidator struct{} func (v *structValidator) Engine() any { - return "" + return nil // Implement if using an external validation engine } func (v *structValidator) ValidateStruct(out any) error { - out = reflect.ValueOf(out).Elem().Interface() - sq := out.(Query) + data := reflect.ValueOf(out).Elem().Interface() + query := data.(Query) - if sq.Name != "john" { - return errors.New("you should have entered right name!") + if query.Name != "john" { + return errors.New("you should have entered the correct name!") } return nil } func main() { - app := fiber.New(fiber.Config{StructValidator: &structValidator{}}) + app := fiber.New(fiber.Config{ + StructValidator: &structValidator{}, + }) app.Get("/", func(c fiber.Ctx) error { out := new(Query) if err := c.Bind().Query(out); err != nil { - return err // you should have entered right name! + return err // Returns: you should have entered the correct name! } return c.SendString(out.Name) }) @@ -204,5 +218,5 @@ func main() { // Run tests with the following curl command: -// curl "http://localhost:3000/?name=efe" +curl "http://localhost:3000/?name=efe" ``` diff --git a/ctx.go b/ctx.go index a2eee2754b..14fb7246b0 100644 --- a/ctx.go +++ b/ctx.go @@ -640,7 +640,7 @@ func (c *DefaultCtx) Get(key string, defaultValue ...string) string { } // GetReqHeader returns the HTTP request header specified by filed. -// This function is generic and can handle differnet headers type values. +// This function is generic and can handle different headers type values. func GetReqHeader[V GenericType](c Ctx, key string, defaultValue ...V) V { var v V return genericParseType[V](c.App().getString(c.Request().Header.Peek(key)), v, defaultValue...) @@ -1083,7 +1083,7 @@ func (c *DefaultCtx) Params(key string, defaultValue ...string) string { } // Params is used to get the route parameters. -// This function is generic and can handle differnet route parameters type values. +// This function is generic and can handle different route parameters type values. // // Example: // @@ -1860,8 +1860,8 @@ func (c *DefaultCtx) IsFromLocal() bool { func (c *DefaultCtx) Bind() *Bind { if c.bind == nil { c.bind = &Bind{ - ctx: c, - should: true, + ctx: c, + dontHandleErrs: true, } } return c.bind diff --git a/docs/api/bind.md b/docs/api/bind.md index 2ad0854ca9..a1cbedd18d 100644 --- a/docs/api/bind.md +++ b/docs/api/bind.md @@ -458,22 +458,23 @@ The `MIMETypes` method is used to check if the custom binder should be used for For more control over error handling, you can use the following methods. -### Must +### WithAutoHandling -If you want to handle binder errors automatically, you can use `Must`. +If you want to handle binder errors automatically, you can use `WithAutoHandling`. If there's an error, it will return the error and set HTTP status to `400 Bad Request`. +This function does NOT panic therefor you must still return on error explicitly ```go title="Signature" -func (b *Bind) Must() *Bind +func (b *Bind) WithAutoHandling() *Bind ``` -### Should +### WithoutAutoHandling -To handle binder errors manually, you can use the `Should` method. +To handle binder errors manually, you can use the `WithoutAutoHandling` method. It's the default behavior of the binder. ```go title="Signature" -func (b *Bind) Should() *Bind +func (b *Bind) WithoutAutoHandling() *Bind ``` ## SetParserDecoder