Le sezioni precedenti hanno introdotto il metodo di lettura diretta dei parametri della richiesta. Se risulta complicato leggere singolarmente ciascun parametro, il framework iris fornisce anche un meccanismo di binding dei parametri, che consente di associare i parametri della richiesta a una struttura, e supporta anche un meccanismo di validazione dei parametri del modulo.

Associazione dei modelli e convalida

Per associare il corpo della richiesta a un tipo, utilizzare l'associazione dei modelli. Attualmente supportiamo tipi di associazione come JSON, JSONProtobuf, Protobuf, MsgPack, XML, YAML e valori di modulo standard (foo=bar&boo=baz).

// Di seguito sono riportate le definizioni di funzione per l'associazione dei parametri della richiesta di vari formati a una struttura
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

Quando si utilizza ReadBody, Iris dedurrà l'associazione in base all'intestazione Content-Type. Se si è certi del contenuto che si desidera associare, è possibile utilizzare metodi specifici come ReadJSON o ReadProtobuf.

ReadBody(ptr interface{}) error

Iris dispone di un'intelligente convalida dati integrata. Tuttavia, consente di allegare un validatore che verrà chiamato automaticamente nei metodi come ReadJSON, ReadXML, ecc. In questo esempio, vedremo come utilizzare go-playground/validator/v10 per convalidare il corpo della richiesta.

Si tenga presente che è necessario impostare le etichette di associazione corrispondenti su tutti i campi da associare. Ad esempio, quando si associa da JSON, impostare json:"nomecampo".

È inoltre possibile specificare determinati campi come campi obbligatori. Se un campo ha la decorazione binding:"required" e durante l'associazione non viene fornito alcun valore, verrà restituito un errore.

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("/utente")
    {
        userRouter.Get("/errori-di-validazione", resolveErrorsDocumentation)
        userRouter.Post("/", postUser)
    }
    app.Listen(":8080")
}

// User contiene le informazioni dell'utente.
type User struct {
    Nome          string     `json:"nome" validate:"required"` // Nome, obbligatorio
    Cognome       string     `json:"cognome" validate:"required"` // Cognome, obbligatorio
    Età           uint8      `json:"età" validate:"gte=0,lte=130"` // Età, compresa tra 0 e 130
    Email         string     `json:"email" validate:"required,email"` // Email, obbligatoria
    ColorePreferito string     `json:"colorePref" validate:"hexcolor|rgb|rgba"` // Colore preferito, deve essere un valore di colore esadecimale, RGB o RGBA legale
    Indirizzi     []*Indirizzo `json:"indirizzi" validate:"required,dive,required"` // Elenco degli indirizzi, non può essere vuoto e ciascun elemento dell'indirizzo è obbligatorio
}

// Indirizzo memorizza le informazioni dell'indirizzo dell'utente.
type Indirizzo struct {
    Via    string `json:"via" validate:"required"` // Via, obbligatoria
    Città  string `json:"città" validate:"required"` // Città, obbligatoria
    Pianeta string `json:"pianeta" validate:"required"` // Pianeta, obbligatorio
    Telefono string `json:"telefono" validate:"required"` // Telefono, obbligatorio
}

type erroreDiValidazione struct {
    TagEffettivo string `json:"tag"` // Tag effettivo
    SpazioNomi string `json:"spazioNomi"` // Spazio dei nomi
    Tipo      string `json:"tipo"` // Tipo
    Valore    string `json:"valore"` // Valore
    Parametro string `json:"parametro"` // Parametro
}

func wrapErroriDiValidazione(errs validator.ValidationErrors) []erroreDiValidazione {
    erroriDiValidazione := make([]erroreDiValidazione, 0, len(errs))
    for _, erroreDiValidazione := range errs {
        erroriDiValidazione = append(erroriDiValidazione, erroreDiValidazione{
            TagEffettivo: erroreDiValidazione.TagEffettivo(),
            SpazioNomi: erroreDiValidazione.SpazioNomi(),
            Tipo:   erroreDiValidazione.Tipo().String(),
            Valore: fmt.Sprintf("%v", erroreDiValidazione.Valore()),
            Parametro: erroreDiValidazione.Parametro(),
        })
    }

    return erroriDiValidazione
}

func postUser(ctx iris.Context) {
    var utente User
    err := ctx.ReadJSON(&utente)
    if err != nil {
        // Gestire gli errori, quanto segue è il modo corretto...

        if errs, ok := err.(validator.ValidationErrors); ok {
            // Incapsulare gli errori nel formato JSON, la libreria sottostante restituisce errori di tipo interfaccia.
            erroriDiValidazione := wrapErroriDiValidazione(errs)

            // Restituisce una risposta application/json+problem e interrompe l'esecuzione degli handler successivi
            ctx.StopWithProblem(iris.StatusBadRequest, iris.NewProblem().
                Title("Errori di Validazione").
                Detail("Uno o più campi non hanno superato la validazione").
                Type("/utente/errori-di-validazione").
                Key("errori", erroriDiValidazione))

            return
        }

        // Potrebbe essere un errore JSON interno, qui non vengono fornite ulteriori informazioni.
        ctx.StopWithStatus(iris.StatusInternalServerError)
        return
    }

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

func resolveErrorsDocumentation(ctx iris.Context) {
    ctx.WriteString("Questa pagina è utilizzata per spiegare come risolvere gli errori di validazione agli sviluppatori web o agli utenti API")
}
{
    "fname": "",
    "lname": "",
    "age": 45,
    "email": "[email protected]",
    "favColor": "#000",
    "addresses": [{
        "street": "Eavesdown Docks",
        "planet": "Persphone",
        "phone": "none",
        "city": "Unknown"
    }]
}

Esempio di richiesta

{
    "nome": "",
    "cognome": "",
    "età": 45,
    "email": "[email protected]",
    "colorePreferito": "#000",
    "indirizzi": [{
        "via": "Banchine di Eavesdown",
        "planeta": "Persphone",
        "telefono": "nessuno",
        "città": "Sconosciuta"
    }]
}

Esempio di risposta

{
    "titolo": "Errore di convalida",
    "dettaglio": "Uno o più campi non hanno superato la convalida",
    "tipo": "http://localhost:8080/user/validation-errors",
    "stato": 400,
    "campi": [
        {
            "tag": "richiesto",
            "namespace": "User.FirstName",
            "tipo": "string",
            "valore": "",
            "parametro": ""
        },
        {
            "tag": "richiesto",
            "namespace": "User.LastName",
            "tipo": "string",
            "valore": "",
            "parametro": ""
        }
    ]
}

Binding URL query parameters

Il metodo ReadQuery lega solo i parametri della query, non i dati del corpo della richiesta. Usa ReadForm per legare i dati del corpo della richiesta.

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

Binding dati arbitrari

Legare il corpo della richiesta a "ptr" in base al tipo di contenuto dei dati inviati dal client, come JSON, XML, YAML, MessagePack, Protobuf, Form e 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("Persona: %#+v", person)
    ctx.WriteString("Successo")
}

Puoi testare con il seguente comando:

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

Binding parametri del percorso 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("myParams: %#v", p)
    })
    app.Listen(":8088")
}

Richiesta

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

Binding parametri della richiesta header

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

Richiesta

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

Risposta

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