binding

The binding middleware provides request data binding and validation for Flame instances, including Form, Multipart Form, JSON and YAML formats.

You can read source code of this middleware on GitHubbinding - 图1open in new window and API documentation on pkg.go.devbinding - 图2open in new window.

Installation

The minimum requirement of Go is 1.16.

  1. go get github.com/flamego/binding

Usage examples

TIP

Examples included in this section is to demonstrate the usage of the binding middleware, please refer to the documentation of validatorbinding - 图3open in new window package for validation syntax and constraints.

The type of binding object is injected into the request context and the special data type binding.Errorsbinding - 图4open in new window is provided to indicate any errors occurred in binding and/or validation phases.

DANGER

Pointers is prohibited be passed as the binding object to prevent side effects, and to make sure every handler gets a fresh copy of the object on every request.

Form

The binding.Formbinding - 图5open in new window takes a binding object and parses the request payload encoded as application/x-www-form-urlencoded, a binding.Optionsbinding - 图6open in new window can be used to further customize the behavior of the function.

The form struct tag should be used to indicate the binding relations between the payload and the object:

  • main.go
  • templates/home.tmpl
  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. "github.com/flamego/binding"
  6. "github.com/flamego/flamego"
  7. "github.com/flamego/template"
  8. "github.com/flamego/validator"
  9. )
  10. type User struct {
  11. FirstName string `form:"first_name" validate:"required"`
  12. LastName string `form:"last_name" validate:"required"`
  13. Age int `form:"age" validate:"gte=0,lte=130"`
  14. Email string `form:"email" validate:"required,email"`
  15. Hashtags []string `form:"hashtag"`
  16. }
  17. func main() {
  18. f := flamego.Classic()
  19. f.Use(template.Templater())
  20. f.Get("/", func(t template.Template) {
  21. t.HTML(http.StatusOK, "home")
  22. })
  23. f.Post("/", binding.Form(User{}), func(w http.ResponseWriter, form User, errs binding.Errors) {
  24. if len(errs) > 0 {
  25. var err error
  26. switch errs[0].Category {
  27. case binding.ErrorCategoryValidation:
  28. err = errs[0].Err.(validator.ValidationErrors)[0]
  29. default:
  30. err = errs[0].Err
  31. }
  32. w.WriteHeader(http.StatusBadRequest)
  33. _, _ = w.Write([]byte(fmt.Sprintf("Oops! Error occurred: %v", err)))
  34. return
  35. }
  36. w.WriteHeader(http.StatusOK)
  37. w.Write([]byte(fmt.Sprintf("User: %#+v", form)))
  38. })
  39. f.Run()
  40. }
  1. <form method="POST">
  2. <div>
  3. <label>First name:</label>
  4. <input type="text" name="first_name" value="John">
  5. </div>
  6. <div>
  7. <label>Last name:</label>
  8. <input type="text" name="last_name" value="Smith">
  9. </div>
  10. <div>
  11. <label>Age:</label>
  12. <input type="number" name="age" value="90">
  13. </div>
  14. <div>
  15. <label>Email:</label>
  16. <input type="email" name="email" value="john@example.com">
  17. </div>
  18. <div>
  19. <label>Hashtags:</label>
  20. <select name="hashtag" multiple>
  21. <option value="driver">Driver</option>
  22. <option value="developer">Developer</option>
  23. <option value="runner">Runner</option>
  24. </select>
  25. </div>
  26. <input type="submit" name="button" value="Submit">
  27. </form>

Multipart Form

The binding.MultipartFormbinding - 图7open in new window takes a binding object and parses the request payload encoded as multipart/form-data, a binding.Optionsbinding - 图8open in new window can be used to further customize the behavior of the function.

The form struct tag should be used to indicate the binding relations between the payload and the object, and *multipart.FileHeaderbinding - 图9open in new window should be type of the field that you’re going to store the uploaded content:

  • main.go
  • templates/home.tmpl
  1. package main
  2. import (
  3. "fmt"
  4. "mime/multipart"
  5. "net/http"
  6. "github.com/flamego/binding"
  7. "github.com/flamego/flamego"
  8. "github.com/flamego/template"
  9. "github.com/flamego/validator"
  10. )
  11. type User struct {
  12. FirstName string `form:"first_name" validate:"required"`
  13. LastName string `form:"last_name" validate:"required"`
  14. Avatar *multipart.FileHeader `form:"avatar"`
  15. }
  16. func main() {
  17. f := flamego.Classic()
  18. f.Use(template.Templater())
  19. f.Get("/", func(t template.Template) {
  20. t.HTML(http.StatusOK, "home")
  21. })
  22. f.Post("/", binding.MultipartForm(User{}), func(w http.ResponseWriter, form User, errs binding.Errors) {
  23. if len(errs) > 0 {
  24. var err error
  25. switch errs[0].Category {
  26. case binding.ErrorCategoryValidation:
  27. err = errs[0].Err.(validator.ValidationErrors)[0]
  28. default:
  29. err = errs[0].Err
  30. }
  31. w.WriteHeader(http.StatusBadRequest)
  32. _, _ = w.Write([]byte(fmt.Sprintf("Oops! Error occurred: %v", err)))
  33. return
  34. }
  35. w.WriteHeader(http.StatusOK)
  36. w.Write([]byte(fmt.Sprintf("User: %#+v", form)))
  37. })
  38. f.Run()
  39. }
  1. <form enctype="multipart/form-data" method="POST">
  2. <div>
  3. <label>First name:</label>
  4. <input type="text" name="first_name" value="John">
  5. </div>
  6. <div>
  7. <label>Last name:</label>
  8. <input type="text" name="last_name" value="Smith">
  9. </div>
  10. <div>
  11. <label>Avatar:</label>
  12. <input type="file" name="avatar">
  13. </div>
  14. <input type="submit" name="button" value="Submit">
  15. </form>

JSON

The binding.JSONbinding - 图10open in new window takes a binding object and parses the request payload encoded as application/json, a binding.Optionsbinding - 图11open in new window can be used to further customize the behavior of the function.

The json struct tag should be used to indicate the binding relations between the payload and the object:

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. "github.com/flamego/binding"
  6. "github.com/flamego/flamego"
  7. "github.com/flamego/validator"
  8. )
  9. type Address struct {
  10. Street string `json:"street" validate:"required"`
  11. City string `json:"city" validate:"required"`
  12. Planet string `json:"planet" validate:"required"`
  13. Phone string `json:"phone" validate:"required"`
  14. }
  15. type User struct {
  16. FirstName string `json:"first_name" validate:"required"`
  17. LastName string `json:"last_name" validate:"required"`
  18. Age uint8 `json:"age" validate:"gte=0,lte=130"`
  19. Email string `json:"email" validate:"required,email"`
  20. Addresses []*Address `json:"addresses" validate:"required,dive,required"`
  21. }
  22. func main() {
  23. f := flamego.Classic()
  24. f.Post("/", binding.JSON(User{}), func(w http.ResponseWriter, form User, errs binding.Errors) {
  25. if len(errs) > 0 {
  26. var err error
  27. switch errs[0].Category {
  28. case binding.ErrorCategoryValidation:
  29. err = errs[0].Err.(validator.ValidationErrors)[0]
  30. default:
  31. err = errs[0].Err
  32. }
  33. w.WriteHeader(http.StatusBadRequest)
  34. _, _ = w.Write([]byte(fmt.Sprintf("Oops! Error occurred: %v", err)))
  35. return
  36. }
  37. w.WriteHeader(http.StatusOK)
  38. w.Write([]byte(fmt.Sprintf("User: %#+v", form)))
  39. })
  40. f.Run()
  41. }

YAML

The binding.YAMLbinding - 图12open in new window takes a binding object and parses the request payload encoded as application/yaml, a binding.Optionsbinding - 图13open in new window can be used to further customize the behavior of the function.

The yaml struct tag should be used to indicate the binding relations between the payload and the object:

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. "github.com/flamego/binding"
  6. "github.com/flamego/flamego"
  7. "github.com/flamego/validator"
  8. )
  9. type Address struct {
  10. Street string `yaml:"street" validate:"required"`
  11. City string `yaml:"city" validate:"required"`
  12. Planet string `yaml:"planet" validate:"required"`
  13. Phone string `yaml:"phone" validate:"required"`
  14. }
  15. type User struct {
  16. FirstName string `yaml:"first_name" validate:"required"`
  17. LastName string `yaml:"last_name" validate:"required"`
  18. Age uint8 `yaml:"age" validate:"gte=0,lte=130"`
  19. Email string `yaml:"email" validate:"required,email"`
  20. Addresses []*Address `yaml:"addresses" validate:"required,dive,required"`
  21. }
  22. func main() {
  23. f := flamego.Classic()
  24. f.Post("/", binding.YAML(User{}), func(w http.ResponseWriter, form User, errs binding.Errors) {
  25. if len(errs) > 0 {
  26. var err error
  27. switch errs[0].Category {
  28. case binding.ErrorCategoryValidation:
  29. err = errs[0].Err.(validator.ValidationErrors)[0]
  30. default:
  31. err = errs[0].Err
  32. }
  33. w.WriteHeader(http.StatusBadRequest)
  34. _, _ = w.Write([]byte(fmt.Sprintf("Oops! Error occurred: %v", err)))
  35. return
  36. }
  37. w.WriteHeader(http.StatusOK)
  38. w.Write([]byte(fmt.Sprintf("User: %#+v", form)))
  39. })
  40. f.Run()
  41. }

Localize validation errors

If your web application supports localization for users speak in different languages, it is equally important to provide the error message in their preferred language.

Here is a playable example in your browser to get a taste of how to localize validation errors in your own style!

  • Directory
  • main.go
  • templates/home.tmpl
  • locale_en-US.ini
  • locale_zh-CN.ini
  1. $ tree .
  2. .
  3. ├── locales
  4. ├── locale_en-US.ini
  5. └── locale_zh-CN.ini
  6. ├── templates
  7. └── home.tmpl
  8. ├── go.mod
  9. ├── go.sum
  10. └── main.go
  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/http"
  6. "github.com/flamego/binding"
  7. "github.com/flamego/flamego"
  8. "github.com/flamego/i18n"
  9. "github.com/flamego/template"
  10. "github.com/flamego/validator"
  11. )
  12. type User struct {
  13. FirstName string `form:"first_name" validate:"required"`
  14. LastName string `form:"last_name" validate:"required"`
  15. Age int `form:"age" validate:"gte=0,lte=130"`
  16. Email string `form:"email" validate:"required,email"`
  17. }
  18. func main() {
  19. f := flamego.Classic()
  20. f.Use(template.Templater())
  21. f.Use(i18n.I18n(
  22. i18n.Options{
  23. Languages: []i18n.Language{
  24. {Name: "en-US", Description: "English"},
  25. {Name: "zh-CN", Description: "简体中文"},
  26. },
  27. },
  28. ))
  29. f.Get("/", func(t template.Template) {
  30. t.HTML(http.StatusOK, "home")
  31. })
  32. f.Post("/", binding.Form(User{}), func(w http.ResponseWriter, form User, errs binding.Errors, l i18n.Locale) {
  33. if len(errs) > 0 {
  34. var err error
  35. switch errs[0].Category {
  36. case binding.ErrorCategoryValidation:
  37. verr := errs[0].Err.(validator.ValidationErrors)[0]
  38. name := l.Translate("field::" + verr.Namespace())
  39. param := verr.Param()
  40. var reason string
  41. if param == "" {
  42. reason = l.Translate("validation::" + verr.Tag())
  43. } else {
  44. reason = l.Translate("validation::"+verr.Tag(), verr.Param())
  45. }
  46. err = errors.New(name + reason)
  47. default:
  48. err = errs[0].Err
  49. }
  50. w.WriteHeader(http.StatusBadRequest)
  51. _, _ = w.Write([]byte(fmt.Sprintf("Oops! Error occurred: %v", err)))
  52. return
  53. }
  54. w.WriteHeader(http.StatusOK)
  55. w.Write([]byte(fmt.Sprintf("User: %#+v", form)))
  56. })
  57. f.Run()
  58. }
  1. <form method="POST">
  2. <div>
  3. <label>First name:</label>
  4. <input type="text" name="first_name" value="John">
  5. </div>
  6. <div>
  7. <label>Last name:</label>
  8. <input type="text" name="last_name" value="Smith">
  9. </div>
  10. <div>
  11. <label>Age:</label>
  12. <input type="number" name="age" value="90">
  13. </div>
  14. <div>
  15. <label>Email:</label>
  16. <input type="email" name="email" value="john@example.com">
  17. </div>
  18. <div>
  19. <label>Language:</label>
  20. <a href="?lang=en-US">English</a>,
  21. <a href="?lang=zh-CN">简体中文</a>
  22. </div>
  23. <input type="submit" name="button" value="Submit">
  24. </form>
  1. [field]
  2. User.FirstName = First name
  3. User.LastName = Last name
  4. User.Age = Age
  5. User.Email = Email
  6. [validation]
  7. required = ` cannot be empty`
  8. gte = ` must be greater than or equal to %s`
  9. lte = ` must be less than or equal to %s`
  10. email = ` must be an email address`
  1. [field]
  2. User.FirstName = 名字
  3. User.LastName = 姓氏
  4. User.Age = 年龄
  5. User.Email = 邮箱
  6. [validation]
  7. required = 不能为空
  8. gte = 必须大于或等于 %s
  9. lte = 必须小于或等于 %s
  10. email = 必须是一个电子邮箱地址