Las secciones anteriores presentaron el método de leer directamente los parámetros de solicitud. Si resulta engorroso leer cada parámetro individualmente, el marco de trabajo iris también proporciona un mecanismo de enlace de parámetros, que puede vincular los parámetros de solicitud a una estructura y también admite un mecanismo de validación de parámetros de formulario.

Vinculación de modelos y validación

Para vincular el cuerpo de la solicitud a un tipo, se utiliza la vinculación de modelos. Actualmente, admitimos la vinculación de tipos como JSON, JSONProtobuf, Protobuf, MsgPack, XML, YAML y valores de formulario estándar (foo=bar&boo=baz).

// A continuación se presentan las definiciones de funciones para vincular parámetros de solicitud de varios formatos a una estructura
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

Al utilizar ReadBody, Iris inferirá el vinculador según el encabezado Content-Type. Si está seguro sobre el contenido que desea vincular, puede utilizar métodos específicos como ReadJSON o ReadProtobuf.

ReadBody(ptr interface{}) error

Iris cuenta con una inteligente validación de datos integrada. Sin embargo, le permite adjuntar un validador que se llamará automáticamente en métodos como ReadJSON, ReadXML, etc. En este ejemplo, aprenderemos a utilizar go-playground/validator/v10 para validar el cuerpo de la solicitud.

Tenga en cuenta que es necesario establecer las etiquetas de vinculación correspondientes en todos los campos a vincular. Por ejemplo, al vincular desde JSON, establezca json:"nombre_campo".

También puede especificar ciertos campos como campos requeridos. Si un campo tiene la decoración binding:"required" y no se proporciona ningún valor durante la vinculación, se devolverá un error.

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("/usuario")
    {
        userRouter.Get("/errores-de-validacion", resolverDocumentacionErrores)
        userRouter.Post("/", postUsuario)
    }
    app.Listen(":8080")
}

// Usuario contiene información del usuario.
type Usuario struct {
    Nombre         string     `json:"fname" validate:"required"` // Nombre, requerido
    Apellido       string     `json:"lname" validate:"required"` // Apellido, requerido
    Edad           uint8      `json:"age" validate:"gte=0,lte=130"` // Edad, rango entre 0 y 130
    Correo         string     `json:"email" validate:"required,email"` // Correo electrónico, requerido
    ColorFavorito  string     `json:"favColor" validate:"hexcolor|rgb|rgba"` // Color favorito, debe ser un valor legal de color hexadecimal, RGB o RGBA
    Direcciones    []*Direccion `json:"addresses" validate:"required,dive,required"` // Lista de direcciones, no debe estar vacía y cada elemento de la dirección es requerido
}

// Dirección almacena información de dirección de usuario.
type Direccion struct {
    Calle  string `json:"street" validate:"required"` // Calle, requerida
    Ciudad string `json:"city" validate:"required"` // Ciudad, requerida
    Planeta string `json:"planet" validate:"required"` // Planeta, requerido
    Teléfono string `json:"phone" validate:"required"` // Teléfono, requerido
}

type errorDeValidacion struct {
    EtiquetaActual string `json:"tag"` // Etiqueta actual
    EspacioNombres  string `json:"namespace"` // Espacio de nombres
    Tipo      string `json:"kind"` // Tipo
    Val       string `json:"type"` // Valor
    Parámetro string `json:"param"` // Parámetro
}

func envolverErroresDeValidacion(errs validator.ValidationErrors) []errorDeValidacion {
    erroresDeValidacion := make([]errorDeValidacion, 0, len(errs))
    for _, errorDeValidacion := range errs {
        erroresDeValidacion = append(erroresDeValidacion, errorDeValidacion{
            EtiquetaActual: errorDeValidacion.ActualTag(),
            EspacioNombres: errorDeValidacion.Namespace(),
            Tipo:      errorDeValidacion.Kind().String(),
            Val:      errorDeValidacion.Type().String(),
            Parámetro: errorDeValidacion.Param(),
        })
    }

    return erroresDeValidacion
}

func postUsuario(ctx iris.Context) {
    var usuario Usuario
    err := ctx.ReadJSON(&usuario)
    if err != nil {
        // Manejar errores, la siguiente es la forma correcta...

        if errs, ok := err.(validator.ValidationErrors); ok {
            // Envolver errores en formato JSON, la biblioteca subyacente devuelve errores de tipo interfaz.
            erroresDeValidacion := envolverErroresDeValidacion(errs)

            // Devolver una respuesta de aplicación/json+problem y detener la ejecución de los manejadores subsiguientes
            ctx.StopWithProblem(iris.StatusBadRequest, iris.NewProblem().
                Title("Errores de Validación").
                Detail("Uno o más campos no superaron la validación").
                Type("/usuario/errores-de-validacion").
                Key("errores", erroresDeValidacion))

            return
        }

        // Podría ser un error interno de JSON, no se proporciona más información aquí.
        ctx.StopWithStatus(iris.StatusInternalServerError)
        return
    }

    ctx.JSON(iris.Map{"mensaje": "OK"})
}

func resolverDocumentacionErrores(ctx iris.Context) {
    ctx.WriteString("Esta página se utiliza para explicar cómo resolver errores de validación a desarrolladores web o usuarios de API")
}
{
    "nombre": "",
    "apellido": "",
    "edad": 45,
    "email": "[email protected]",
    "colorFavorito": "#000",
    "direcciones": [{
        "calle": "Muelles Eavesdown",
        "planeta": "Perséfone",
        "teléfono": "ninguno",
        "ciudad": "Desconocida"
    }]
}
{
    "título": "Error de validación",
    "detalle": "Uno o más campos no superaron la validación",
    "tipo": "http://localhost:8080/user/validation-errors",
    "estado": 400,
    "campos": [
        {
            "etiqueta": "requerido",
            "espacio-nombres": "Usuario.PrimerNombre",
            "tipo": "cadena",
            "valor": "",
            "parámetro": ""
        },
        {
            "etiqueta": "requerido",
            "espacio-nombres": "Usuario.Apellido",
            "tipo": "cadena",
            "valor": "",
            "parámetro": ""
        }
    ]
}

Parámetros de consulta de URL

El método ReadQuery vincula solo los parámetros de consulta, no los datos del cuerpo de la solicitud. Utiliza ReadForm para vincular los datos del cuerpo de la solicitud.

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("Persona: %#+v", person)
    ctx.WriteString("Éxito")
}

Vinculación de datos arbitrarios

Vincula el cuerpo de la solicitud a "ptr" basado en el tipo de contenido de los datos enviados por el cliente, como JSON, XML, YAML, MessagePack, Protobuf, Form y consulta de URL.

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("Persona: %#+v", person)
    ctx.WriteString("Éxito")
}

Puedes probar con el siguiente comando:

$ curl -X GET "localhost:8085/testing?name=kataras&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033"

Vinculación de parámetros de ruta de URL

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("misParams: %#v", p)
    })
    app.Listen(":8088")
}

Solicitud

$ curl -v http://localhost:8080/kataras/27/iris/web/framework

Vinculación de parámetros de solicitud de encabezado

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")
}

Solicitud

curl -H "x-request-id:373713f0-6b4b-42ea-ab9f-e2e04bc38e73" -H "authentication: Bearer my-token" \
http://localhost:8080

Respuesta

{
  "RequestID": "373713f0-6b4b-42ea-ab9f-e2e04bc38e73",
  "Authentication": "Bearer my-token"
}