Poprzednie sekcje przedstawiały metodę bezpośredniego odczytywania parametrów żądania. Jeśli uciążliwe jest odczytywanie każdego parametru osobno, framework iris zapewnia również mechanizm wiązania parametrów, który umoże wiązać parametry żądania z typem struktury, obsługując również mechanizm walidacji parametrów formularza.

Wiązanie modelu i walidacja

Aby powiązać ciało żądania z typem, należy użyć wiązania modelu. Obecnie obsługujemy wiązanie typów takich jak JSON, JSONProtobuf, Protobuf, MsgPack, XML, YAML oraz standardowe wartości formularza (foo=bar&boo=baz).

// Poniżej znajdują się definicje funkcji służące do wiązania parametrów żądania w różnych formatach z 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

Podczas korzystania z ReadBody, Iris wywnioskował będzie wiązacza na podstawie nagłówka Content-Type. Jeśli jesteś pewien co do zawartości, którą chcesz powiązać, możesz użyć konkretnych metod ReadXXX, takich jak ReadJSON lub ReadProtobuf.

ReadBody(ptr interface{}) error

Iris posiada inteligentną wbudowaną walidację danych. Jednak pozwala ona dołączyć walidator, który będzie automatycznie wywoływany w przypadku metod takich jak ReadJSON, ReadXML itp. W tym przykładzie dowiemy się, jak wykorzystać go-playground/validator/v10 do walidacji treści żądania.

Zauważ, że musisz ustawić odpowiednie tagi wiązania na wszystkich polach do powiązania. Na przykład, podczas wiązania z JSON, ustaw json:"nazwa_pola".

Możesz również określić określone pola jako wymagane. Jeśli pole ma dekorację binding:"required" i nie zostanie podana wartość podczas wiązania, zostanie zwrócony błąd.

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("/użytkownik")
    {
        userRouter.Get("/błędy-walidacji", rozwiązanieDokumentacjiBłędów)
        userRouter.Post("/", dodajUżytkownika)
    }
    app.Listen(":8080")
}

// Użytkownik zawiera informacje o użytkowniku.
type Użytkownik struct {
    Imię           string     `json:"imie" validate:"required"` // Imię, wymagane
    Nazwisko       string     `json:"nazwisko" validate:"required"` // Nazwisko, wymagane
    Wiek           uint8      `json:"wiek" validate:"gte=0,lte=130"` // Wiek, zakres od 0 do 130
    Email          string     `json:"email" validate:"required,email"` // Email, wymagany
    UlubionyKolor  string     `json:"ulubionyKolor" validate:"hexcolor|rgb|rgba"` // Ulubiony kolor, musi być legalną wartością szesnastkową, RGB lub RGBA
    Adresy         []*Adres   `json:"adresy" validate:"required,dive,required"` // Lista adresów, nie może być pusta i każdy element adresu jest wymagany
}

// Adres przechowuje informacje o adresie użytkownika.
type Adres struct {
    Ulica  string `json:"ulica" validate:"required"` // Ulica, wymagana
    Miasto string `json:"miasto" validate:"required"` // Miasto, wymagane
    Planeta string `json:"planeta" validate:"required"` // Planeta, wymagana
    Telefon string `json:"telefon" validate:"required"` // Telefon, wymagany
}

type błądWalidacji struct {
    ActualTag string `json:"tag"` // Aktualny tag
    Namespace string `json:"namespace"` // Przestrzeń nazw
    Kind      string `json:"kind"` // Typ
    Type      string `json:"type"` // Rodzaj
    Value     string `json:"value"` // Wartość
    Param     string `json:"param"` // Parametr
}

func wrapBłędyWalidacji(errs validator.ValidationErrors) []błądWalidacji {
    validationErrors := make([]błądWalidacji, 0, len(errs))
    for _, validationErr := range errs {
        validationErrors = append(validationErrors, błądWalidacji{
            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 dodajUżytkownika(ctx iris.Context) {
    var użytkownik Użytkownik
    err := ctx.ReadJSON(&użytkownik)
    if err != nil {
        // Obsługa błędów, poniżej znajduje się poprawny sposób...

        if errs, ok := err.(validator.ValidationErrors); ok {
            // Owijanie błędów w formacie JSON, podstawowa biblioteka zwraca błędy typu interfejsowego.
            błędyWalidacji := wrapBłędyWalidacji(errs)

            // Zwracanie odpowiedzi aplikacji w formacie application/json+problem i przerwanie wykonywania kolejnych handlerów
            ctx.StopWithProblem(iris.StatusBadRequest, iris.NewProblem().
                Title("Błędy walidacji").
                Detail("Jeden lub więcej pól nie przeszło walidacji").
                Type("/użytkownik/błędy-walidacji").
                Key("błędy", błędyWalidacji))

            return
        }

        // Może to być wewnętrzny błąd JSON, tutaj nie ma dalszych informacji.
        ctx.StopWithStatus(iris.StatusInternalServerError)
        return
    }

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

func rozwiązanieDokumentacjiBłędów(ctx iris.Context) {
    ctx.WriteString("Ta strona służy do wyjaśnienia, jak rozwiązywać błędy walidacji dla programistów sieciowych lub użytkowników interfejsu API")
}
{
    "tytuł": "Błąd walidacji",
    "szczegóły": "Jedno lub więcej pól nie przeszło walidacji",
    "typ": "http://localhost:8080/użytkownik/błędy-walidacji",
    "status": 400,
    "pola": [
        {
            "tag": "wymagane",
            "przestrzeńNazw": "Użytkownik.Imię",
            "rodzaj": "string",
            "typ": "string",
            "wartość": "",
            "parametr": ""
        },
        {
            "tag": "wymagane",
            "przestrzeńNazw": "Użytkownik.Nazwisko",
            "rodzaj": "string",
            "typ": "string",
            "wartość": "",
            "parametr": ""
        }
    ]
}

Bindowanie parametrów zapytania URL

Metoda ReadQuery wiąże tylko parametry zapytania, a nie dane ciała żądania. Użyj ReadForm, aby wiązać dane ciała żądania.

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

Wiązanie dowolnych danych

Wiąż żądanie ciała do "ptr" w oparciu o typ zawartości danych wysłanych przez klienta, takie jak JSON, XML, YAML, MessagePack, Protobuf, Form i zapytanie 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("Person: %#+v", person)
    ctx.WriteString("Success")
}

Możesz przetestować za pomocą następującej komendy:

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

Wiązanie parametrów ścieżki 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")
}

Żądanie

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

Wiązanie parametrów nagłówka żądania

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

Żądanie

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

Odpowiedź

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