The previous sections introduced the method of directly reading request parameters. If it's cumbersome to read each parameter individually, the iris framework also provides a parameter binding mechanism, which can bind request parameters to a struct, and also supports a form parameter validation mechanism.
Model Binding and Validation
To bind the request body to a type, use model binding. Currently, we support binding types such as JSON
, JSONProtobuf
, Protobuf
, MsgPack
, XML
, YAML
, and standard form values (foo=bar&boo=baz).
// Below are the function definitions for binding request parameters of various formats to a struct
ReadJSON(outPtr interface{}) error
ReadJSONProtobuf(ptr proto.Message, opts ...ProtoUnmarshalOptions) error
ReadProtobuf(ptr proto.Message) error
ReadMsgPack(ptr interface{}) error
ReadXML(outPtr interface{}) error
ReadYAML(outPtr interface{}) error
ReadForm(formObject interface{}) error
ReadQuery(ptr interface{}) error
When using ReadBody
, Iris will infer the binder based on the Content-Type header. If you are certain about the content you want to bind, you can use specific ReadXXX
methods, such as ReadJSON
or ReadProtobuf
.
ReadBody(ptr interface{}) error
Iris comes with intelligent built-in data validation. However, it allows you to attach a validator that will be automatically called on methods like ReadJSON
, ReadXML
, etc. In this example, we will learn how to use go-playground/validator/v10 to validate the request body.
Please note that you need to set the corresponding binding tags on all fields to be bound. For example, when binding from JSON, set json:"fieldname"
.
You can also specify certain fields as required fields. If a field has the binding:"required"
decoration and no value is provided during binding, an error will be returned.
package main
import (
"fmt"
"github.com/kataras/iris/v12"
"github.com/go-playground/validator/v10"
)
func main() {
app := iris.New()
app.Validator = validator.New()
userRouter := app.Party("/user")
{
userRouter.Get("/validation-errors", resolveErrorsDocumentation)
userRouter.Post("/", postUser)
}
app.Listen(":8080")
}
// User contains user information.
type User struct {
FirstName string `json:"fname" validate:"required"` // First name, required
LastName string `json:"lname" validate:"required"` // Last name, required
Age uint8 `json:"age" validate:"gte=0,lte=130"` // Age, range between 0 and 130
Email string `json:"email" validate:"required,email"` // Email, required
FavouriteColor string `json:"favColor" validate:"hexcolor|rgb|rgba"` // Favorite color, must be a legal hexadecimal, RGB, or RGBA color value
Addresses []*Address `json:"addresses" validate:"required,dive,required"` // Address list, must not be empty and each address item is required
}
// Address stores user address information.
type Address struct {
Street string `json:"street" validate:"required"` // Street, required
City string `json:"city" validate:"required"` // City, required
Planet string `json:"planet" validate:"required"` // Planet, required
Phone string `json:"phone" validate:"required"` // Phone, required
}
type validationError struct {
ActualTag string `json:"tag"` // Actual tag
Namespace string `json:"namespace"` // Namespace
Kind string `json:"kind"` // Kind
Type string `json:"type"` // Type
Value string `json:"value"` // Value
Param string `json:"param"` // Parameter
}
func wrapValidationErrors(errs validator.ValidationErrors) []validationError {
validationErrors := make([]validationError, 0, len(errs))
for _, validationErr := range errs {
validationErrors = append(validationErrors, validationError{
ActualTag: validationErr.ActualTag(),
Namespace: validationErr.Namespace(),
Kind: validationErr.Kind().String(),
Type: validationErr.Type().String(),
Value: fmt.Sprintf("%v", validationErr.Value()),
Param: validationErr.Param(),
})
}
return validationErrors
}
func postUser(ctx iris.Context) {
var user User
err := ctx.ReadJSON(&user)
if err != nil {
// Handle errors, the following is the correct way...
if errs, ok := err.(validator.ValidationErrors); ok {
// Wrap errors in JSON format, the underlying library returns errors of type interface.
validationErrors := wrapValidationErrors(errs)
// Return an application/json+problem response and stop executing subsequent handlers
ctx.StopWithProblem(iris.StatusBadRequest, iris.NewProblem().
Title("Validation Errors").
Detail("One or more fields did not pass validation").
Type("/user/validation-errors").
Key("errors", validationErrors))
return
}
// It might be an internal JSON error, no further information provided here.
ctx.StopWithStatus(iris.StatusInternalServerError)
return
}
ctx.JSON(iris.Map{"message": "OK"})
}
func resolveErrorsDocumentation(ctx iris.Context) {
ctx.WriteString("This page is used to explain how to resolve validation errors to web developers or API users")
}
Sample Request
{
"fname": "",
"lname": "",
"age": 45,
"email": "[email protected]",
"favColor": "#000",
"addresses": [{
"street": "Eavesdown Docks",
"planet": "Persphone",
"phone": "none",
"city": "Unknown"
}]
}
Sample Response
{
"title": "Validation Error",
"detail": "One or more fields failed validation",
"type": "http://localhost:8080/user/validation-errors",
"status": 400,
"fields": [
{
"tag": "required",
"namespace": "User.FirstName",
"kind": "string",
"type": "string",
"value": "",
"param": ""
},
{
"tag": "required",
"namespace": "User.LastName",
"kind": "string",
"type": "string",
"value": "",
"param": ""
}
]
}
Binding URL query parameters
The ReadQuery
method binds only the query parameters, not the request body data. Use ReadForm
to bind the request body data.
package main
import "github.com/kataras/iris/v12"
type Person struct {
Name string `url:"name,required"`
Address string `url:"address"`
}
func main() {
app := iris.Default()
app.Any("/", index)
app.Listen(":8080")
}
func index(ctx iris.Context) {
var person Person
if err := ctx.ReadQuery(&person); err != nil {
ctx.StopWithError(iris.StatusBadRequest, err)
return
}
ctx.Application().Logger().Infof("Person: %#+v", person)
ctx.WriteString("Success")
}
Binding arbitrary data
Bind the request body to "ptr" based on the content type of the data sent by the client, such as JSON, XML, YAML, MessagePack, Protobuf, Form, and URL query.
package main
import (
"time"
"github.com/kataras/iris/v12"
)
type Person struct {
Name string `form:"name" json:"name" url:"name" msgpack:"name"`
Address string `form:"address" json:"address" url:"address" msgpack:"address"`
Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1" json:"birthday" url:"birthday" msgpack:"birthday"`
CreateTime time.Time `form:"createTime" time_format:"unixNano" json:"create_time" url:"create_time" msgpack:"createTime"`
UnixTime time.Time `form:"unixTime" time_format:"unix" json:"unix_time" url:"unix_time" msgpack:"unixTime"`
}
func main() {
app := iris.Default()
app.Any("/", index)
app.Listen(":8080")
}
func index(ctx iris.Context) {
var person Person
if err := ctx.ReadBody(&person); err != nil {
ctx.StopWithError(iris.StatusBadRequest, err)
return
}
ctx.Application().Logger().Infof("Person: %#+v", person)
ctx.WriteString("Success")
}
You can test with the following command:
$ curl -X GET "localhost:8085/testing?name=kataras&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033"
Binding URL path parameters
package main
import "github.com/kataras/iris/v12"
type myParams struct {
Name string `param:"name"`
Age int `param:"age"`
Tail []string `param:"tail"`
}
func main() {
app := iris.Default()
app.Get("/{name}/{age:int}/{tail:path}", func(ctx iris.Context) {
var p myParams
if err := ctx.ReadParams(&p); err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
ctx.Writef("myParams: %#v", p)
})
app.Listen(":8088")
}
Request
$ curl -v http://localhost:8080/kataras/27/iris/web/framework
Binding header request parameters
package main
import "github.com/kataras/iris/v12"
type myHeaders struct {
RequestID string `header:"X-Request-Id,required"`
Authentication string `header:"Authentication,required"`
}
func main() {
app := iris.Default()
r.GET("/", func(ctx iris.Context) {
var hs myHeaders
if err := ctx.ReadHeaders(&hs); err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
ctx.JSON(hs)
})
app.Listen(":8080")
}
Request
curl -H "x-request-id:373713f0-6b4b-42ea-ab9f-e2e04bc38e73" -H "authentication: Bearer my-token" \
http://localhost:8080
Response
{
"RequestID": "373713f0-6b4b-42ea-ab9f-e2e04bc38e73",
"Authentication": "Bearer my-token"
}