Bagian sebelumnya memperkenalkan metode membaca parameter permintaan secara langsung. Jika terlalu rumit untuk membaca setiap parameter secara individual, kerangka kerja iris juga menyediakan mekanisme pengikatan parameter, yang dapat mengikat parameter permintaan ke dalam sebuah struktur, dan juga mendukung mekanisme validasi parameter formulir.

Pemetaan Model dan Validasi

Untuk memetakan badan permintaan ke suatu tipe, gunakan pemetaan model. Saat ini, kami mendukung pemetaan tipe seperti JSON, JSONProtobuf, Protobuf, MsgPack, XML, YAML, dan nilai formulir standar (foo=bar&boo=baz).

// Di bawah ini adalah definisi fungsi untuk memetakan parameter permintaan dari berbagai format ke dalam sebuah struktur
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

Saat menggunakan ReadBody, Iris akan menyimpulkan pemeta pengikat berdasarkan header Content-Type. Jika Anda yakin tentang konten yang ingin Anda ikat, Anda dapat menggunakan metode khusus ReadXXX, seperti ReadJSON atau ReadProtobuf.

ReadBody(ptr interface{}) error

Iris dilengkapi dengan validasi data bawaan yang cerdas. Namun, Anda dapat melampirkan validator yang akan otomatis dipanggil pada metode seperti ReadJSON, ReadXML, dll. Pada contoh ini, kami akan belajar bagaimana menggunakan go-playground/validator/v10 untuk memvalidasi badan permintaan.

Harap dicatat bahwa Anda perlu menetapkan tag pemetaan yang sesuai pada semua bidang yang akan diikat. Misalnya, ketika melakukan pemetaan dari JSON, tetapkan json:"namaBidang".

Anda juga dapat menetapkan beberapa bidang sebagai bidang yang diperlukan. Jika suatu bidang memiliki dekorasi binding:"required" dan tidak ada nilai yang disediakan selama pemetaan, sebuah kesalahan akan dikembalikan.

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 berisi informasi pengguna.
type User struct {
    FirstName      string     `json:"fname" validate:"required"` // Nama depan, wajib
    LastName       string     `json:"lname" validate:"required"` // Nama belakang, wajib
    Age            uint8      `json:"age" validate:"gte=0,lte=130"` // Usia, rentang antara 0 dan 130
    Email          string     `json:"email" validate:"required,email"` // Email, wajib
    FavouriteColor string     `json:"favColor" validate:"hexcolor|rgb|rgba"` // Warna favorit, harus berupa nilai warna heksadesimal, RGB, atau RGBA yang sah
    Addresses      []*Address `json:"addresses" validate:"required,dive,required"` // Daftar alamat, tidak boleh kosong dan setiap item alamat harus wajib
}

// Address menyimpan informasi alamat pengguna.
type Address struct {
    Street string `json:"street" validate:"required"` // Jalan, wajib
    City   string `json:"city" validate:"required"` // Kota, wajib
    Planet string `json:"planet" validate:"required"` // Planet, wajib
    Phone  string `json:"phone" validate:"required"` // Telepon, wajib
}

type validationError struct {
    ActualTag string `json:"tag"` // Tag aktual
    Namespace string `json:"namespace"` // Namespace
    Kind      string `json:"kind"` // Jenis
    Type      string `json:"type"` // Tipe
    Value     string `json:"value"` // Nilai
    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 {
        // Tangani kesalahan, berikut adalah cara yang benar...

        if errs, ok := err.(validator.ValidationErrors); ok {
            // Bungkus kesalahan dalam format JSON, pustaka yang mendasarinya mengembalikan kesalahan berjenis antarmuka.
            validationErrors := wrapValidationErrors(errs)

            // Kembalikan tanggapan application/json+problem dan hentikan menjalankan penanganan berikutnya
            ctx.StopWithProblem(iris.StatusBadRequest, iris.NewProblem().
                Title("Kesalahan Validasi").
                Detail("Salah satu atau lebih bidang tidak lulus validasi").
                Type("/user/validation-errors").
                Key("errors", validationErrors))

            return
        }

        // Mungkin kesalahan JSON internal, tidak ada informasi lebih lanjut yang disediakan di sini.
        ctx.StopWithStatus(iris.StatusInternalServerError)
        return
    }

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

func resolveErrorsDocumentation(ctx iris.Context) {
    ctx.WriteString("Halaman ini digunakan untuk menjelaskan cara menyelesaikan kesalahan validasi kepada pengembang web atau pengguna API")
}

Permintaan Contoh

{
    "fname": "",
    "lname": "",
    "age": 45,
    "email": "[email protected]",
    "favColor": "#000",
    "addresses": [{
        "street": "Pelabuhan Eavesdown",
        "planet": "Persphone",
        "phone": "tidak ada",
        "city": "Tidak Dikenal"
    }]
}

Tanggapan Contoh

{
    "title": "Error Validasi",
    "detail": "Satu atau lebih bidang gagal divalidasi",
    "type": "http://localhost:8080/user/validation-errors",
    "status": 400,
    "fields": [
        {
            "tag": "dibutuhkan",
            "namespace": "User.FirstName",
            "kind": "string",
            "type": "string",
            "value": "",
            "param": ""
        },
        {
            "tag": "dibutuhkan",
            "namespace": "User.LastName",
            "kind": "string",
            "type": "string",
            "value": "",
            "param": ""
        }
    ]
}

Mengikat parameter kueri URL

Metode ReadQuery hanya mengikat parameter kueri, bukan data permintaan tubuh. Gunakan ReadForm untuk mengikat data permintaan tubuh.

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

Mengikat data sembarang

Ikatan tubuh permintaan ke "ptr" berdasarkan jenis konten data yang dikirim oleh klien, seperti JSON, XML, YAML, MessagePack, Protobuf, Form, dan kueri 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")
}

Anda dapat menguji dengan perintah berikut:

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

Mengikat parameter jalur 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")
}

Permintaan

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

Mengikat parameter header permintaan

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

Permintaan

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

Respon

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