Request Parameters - Auto Bind

This document provides insights into aah’s Request Parameters auto parse and bind capabilities. So that you can take full advantage of them.

aah provides two ways to access your request parameters:

Note: Auto Parse and Bind prevents the XSS attacks by sanitizing values. It is highly recommended to use.

Auto Parse and Bind

aah provides very flexible way to parse and bind request values into appropriate Go data types. It supports following:

  • Bind any Path, Form, Query into controller action parameters, examples
  • Bind JSON or XML request body into struct, examples
  • Bind any Path, Form, Query into controller action struct fields, examples
  • Bind any Path, Form, Query into nested struct following . notation convention, examples
  • Bind supports bind of pointer and non-pointer, examples
  • And you can also do combinations of above options
  • You can added your own custom Value Parser by Type

Parse and Bind Priority

request.auto_bind.* configuration is used for auto parse and bind. refer to config.

Supported Data Types

Binding of both pointer and non-pointer is supported.

  • int, int8, int16, int32, int64
  • uint, uint8, uint16, uint32, uint64
  • float32, float64
  • string
  • bool - 1, t, T, true, TRUE, True, on, On will result as true
  • slice - []<type> and []*<type> supported
  • time.Time - gets parsed based on format.time = [...] config from aah.conf
  • struct types
  • Type aliases of built-in types
  • Custom types and aliases add your own value parser

Note:

  • Any auto parse error will result into 400 Bad Request, error details gets logged.
  • Multipart file binding is intentionally not supported by auto bind. It is mainly to use resources effectively. Instead aah provides dedicated method called ctx.Req.SaveFile for convenience to save uploaded multipart-form files into disk easily.

Auto Parse and Bind Example Snippets

Following examples provides an idea to get started quickly with auto parse and bind feature. Let’s start with simple one.

Go with your creativity and use case to exploit the auto parse and bind feature.

Getting Pagination Values

// Let's say we have `/v1/products`
// Request is `/v1/products?page=3&count=25&sort=desc`
func (c *ProductController) Products(page, count int, sort string) {
  c.Log().Info("Page No: ", page)
  c.Log().Info("Count Per Page: ", count)
  c.Log().Info("Sort Order: ", sort)

  // ...
}

Getting Pagination Values into struct

// Tip: You can bind any `Path`, `Form`, `Query` fields into controller action `struct` fields.

// models package has this struct
// Pagination holds request pagination values.
type Pagination struct {
	No    int `bind:"page"`
	Count int `bind:"count"`
}

// Let's say we have `/v1/products`
// It receives GET request with `/v1/products?page=3&count=25`
func (c *ProductController) Products(pagination *models.Pagination) {
  c.Log().Info("Page No: ", pagination.No)
  c.Log().Info("Count Per Page: ", pagination.Count)

  // ...
}

Getting JSON Request Body into struct

Same principle is applicable for XML content-type too.

// models package has this struct
// Product struct holds product fields.
type Product struct {
	ID         string   `json:"id"`
	Name       string   `json:"name"`
	Title      string   `json:"title"`
	IsActive   bool     `json:"is_active"`
	Categories []string `json:"categories"`
}

// Let's say we have `/v1/products`
// It receives POST request to create product as JSON payload
func (c *ProductController) Products(product *models.Product) {
  c.Log().Infof("%+v\n", product)

  // ...
}

Getting Values into struct and nested struct

Here we’re gonna use the user profile fields. Focus on how form fields residence.* and shipping.* are mapped into struct fields with . notation.

// models package has this struct
// Address struct holds address information
type Address struct {
	Address1 string `bind:"address1"`
	Address2 string `bind:"address2"`
	City     string `bind:"city"`
	ZipCode  string `bind:"zip_code"`
}

// User struct holds user profile info
type User struct {
	FirstName        string   `bind:"first_name"`
	LastName         string   `bind:"last_name"`
	Email            string   `bind:"email"`
	ResidenceAddress *Address `bind:"residence"`
	ShippingAddress  *Address `bind:"shipping"`
}

// Request Information
// Method: POST
// Content-Type: application/x-www-form-urlencoded
// Form data:
//    first_name=User Firstname
//    last_name=User Lastname
//    email=email@email.com
//    residence.address1=Residence Address 1
//    residence.address2=Residence Address 2
//    residence.city=Residence City
//    residence.zip_code=10002
//    shipping.address1=Shipping Address 1
//    shipping.address2=Shipping Address 2
//    shipping.city=Shipping City
//    shipping.zip_code=10001

// Let's say we have `/user/profile`
// It receives POST Form request to store user information.
func (c *UserController) Profile(user *models.User) {
  c.Log().Infof("%+v\n", user)

  // ...
}

Adding Custom Value Parser by Type

Implement your value parser per interface valpar.Parser.

type Parser func(key string, typ reflect.Type, params url.Values) (reflect.Value, error)

Then add your parser into aah as follows:

func init()  {
  if err := aah.App().AddValueParser(reflect.TypeOf(CustomType{}), customParser); err != nil {
    aah.App().Log().Error(err)
  }
}