Les sections précédentes ont introduit la méthode de lecture directe des paramètres de requête. S'il est fastidieux de lire chaque paramètre individuellement, le framework iris propose également un mécanisme de liaison de paramètres qui permet de lier les paramètres de requête à une structure, tout en prenant en charge un mécanisme de validation des paramètres de formulaire.

Liaison de modèle et validation

Pour lier le corps de la requête à un type, utilisez la liaison de modèle. Actuellement, nous prenons en charge le "binding" des types tels que JSON, JSONProtobuf, Protobuf, MsgPack, XML, YAML, et les valeurs de formulaire standard (foo=bar&boo=baz).

// ci-dessous se trouvent les définitions des fonctions pour lier les paramètres de requête de différents formats à une structure
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

En utilisant ReadBody, Iris inférera le lien en fonction de l'en-tête Content-Type. Si vous êtes certain du contenu que vous voulez lier, vous pouvez utiliser des méthodes spécifiques telles que ReadJSON ou ReadProtobuf.

ReadBody(ptr interface{}) error

Iris est livré avec une validation de données intégrée intelligente. Cependant, il vous permet d'attacher un validateur qui sera automatiquement appelé sur des méthodes telles que ReadJSON, ReadXML, etc. Dans cet exemple, nous allons apprendre comment utiliser go-playground/validator/v10 pour valider le corps de la requête.

Veuillez noter que vous devez définir les balises de liaison correspondantes sur tous les champs à lier. Par exemple, lors de la liaison depuis JSON, définissez json:"nomduchamp".

Vous pouvez également spécifier certains champs comme des champs obligatoires. Si un champ a la décoration binding:"required" et aucune valeur n'est fournie lors de la liaison, une erreur sera renvoyée.

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:"prenom" validate:"required"` // Prénom, requis
    LastName       string     `json:"nom" validate:"required"` // Nom de famille, requis
    Age            uint8      `json:"age" validate:"gte=0,lte=130"` // Âge, compris entre 0 et 130
    Email          string     `json:"email" validate:"required,email"` // Email, requis
    FavouriteColor string     `json:"couleurPref" validate:"hexcolor|rgb|rgba"` // Couleur préférée, doit être une valeur hexadécimale, RGB ou RGBA légale
    Addresses      []*Address `json:"adresses" validate:"required,dive,required"` // Liste d'adresses, ne doit pas être vide et chaque élément d'adresse est requis
}

// Address stores user address information.
type Address struct {
    Street string `json:"rue" validate:"required"` // Rue, requis
    City   string `json:"ville" validate:"required"` // Ville, requis
    Planet string `json:"planete" validate:"required"` // Planète, requis
    Phone  string `json:"tel" validate:"required"` // Téléphone, requis
}

type validationError struct {
    ActualTag string `json:"tag"` // Tag réel
    Namespace string `json:"espaceNom"` // Espace de noms
    Kind      string `json:"type"` // Type
    Type      string `json:"categorie"` // Catégorie
    Value     string `json:"valeur"` // Valeur
    Param     string `json:"param"` // Paramètre
}

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 {
        // Gérer les erreurs, voici la manière correcte...

        if errs, ok := err.(validator.ValidationErrors); ok {
            // Encapsuler les erreurs au format JSON, la bibliothèque sous-jacente renvoie des erreurs de type interface.
            validationErrors := wrapValidationErrors(errs)

            // Retourner une réponse application/json+problem et arrêter l'exécution des gestionnaires suivants
            ctx.StopWithProblem(iris.StatusBadRequest, iris.NewProblem().
                Title("Erreurs de validation").
                Detail("Un ou plusieurs champs n'ont pas réussi la validation").
                Type("/user/validation-errors").
                Key("errors", validationErrors))

            return
        }

        // Il pourrait s'agir d'une erreur JSON interne, aucune information supplémentaire fournie ici.
        ctx.StopWithStatus(iris.StatusInternalServerError)
        return
    }

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

func resolveErrorsDocumentation(ctx iris.Context) {
    ctx.WriteString("Cette page est utilisée pour expliquer comment résoudre les erreurs de validation aux développeurs web ou aux utilisateurs de l'API")
}
{
    "title": "Erreur de validation",
    "detail": "Un ou plusieurs champs n'ont pas passé la validation",
    "type": "http://localhost:8080/user/validation-errors",
    "status": 400,
    "fields": [
        {
            "tag": "obligatoire",
            "namespace": "Utilisateur.Prénom",
            "kind": "string",
            "type": "string",
            "value": "",
            "param": ""
        },
        {
            "tag": "obligatoire",
            "namespace": "Utilisateur.NomDeFamille",
            "kind": "string",
            "type": "string",
            "value": "",
            "param": ""
        }
    ]
}

Liaison de paramètres de requête d'URL

La méthode ReadQuery lie uniquement les paramètres de requête, et non les données du corps de la requête. Utilisez ReadForm pour lier les données du corps de la requête.

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("Personne : %#+v", person)
    ctx.WriteString("Succès")
}

Liaison de données arbitraires

Lie le corps de la requête à "ptr" en fonction du type de contenu des données envoyées par le client, tel que JSON, XML, YAML, MessagePack, Protobuf, Form, et la requête d'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("Personne : %#+v", person)
    ctx.WriteString("Succès")
}

Vous pouvez tester avec la commande suivante :

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

Liaison de paramètres de chemin d'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("MesParamètres : %#v", p)
    })
    app.Listen(":8088")
}

Requête

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

Liaison de paramètres de requête d'en-tête

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

Requête

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

Réponse

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