ส่วนที่ผ่านมาได้แนะนำวิธีอ่านพารามิเตอร์คำขอโดยตรง หากการอ่านแต่ละพารามิเตอร์มีความลำบาก โครงสร้างของ Iris ยังมีกลไกการผูกพารามิเตอร์ที่สามารถผูกพารามิเตอร์ของคำขอกับ struct และยังสนับสนุนกลไกการตรวจสอบพารามิเตอร์แบบฟอร์มด้วยครับ

การผูกและการตรวจสอบรายการ

เพื่อผูกข้อมูลร้องขอกับประเภท ให้ใช้การผูกโดยใช้โมเดล (model binding) ในปัจจุบันเราสนับสนุนการผูกประเภทเช่น JSON, JSONProtobuf, Protobuf, MsgPack, XML, YAML, และค่าฟอร์มมาตรฐาน (foo=bar&boo=baz).

// ด้านล่างคือการกำหนดฟังก์ชันสำหรับการผูกพารามิเตอร์ร้องขอจากรูปแบบต่าง ๆ เข้ากับ 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

เมื่อใช้ ReadBody, Iris จะใช้ตัวผูกโดยอัตโนมัติตามหัวข้อความ Content-Type หากคุณแน่ใจเกี่ยวกับเนื้อหาที่ต้องการผูก คุณสามารถใช้วิธี ReadXXX โดยเฉพาะ เช่น ReadJSON หรือ ReadProtobuf.

ReadBody(ptr interface{}) error

Iris มาพร้อมกับการตรวจสอบข้อมูลแบบอัจฉริยะที่ซึ่งซับซ้อน อย่างไรก็ตาม มันยังช่วยให้คุณสามารถแนบตัวตรวจสอบซึ่งจะโดยอัตโนมัติเมื่อใช้กับวิธี ReadJSON, ReadXML, เป็นต้น ในตัวอย่างนี้ เราจะเรียนรู้วิธีการใช้ go-playground/validator/v10 เพื่อตรวจสอบท่านของร้องขอ.

โปรดทราบว่าคุณต้องตั้งค่าแท็กผูกสองที่สอดคล้องกับฟิลด์ทุกฟิลด์ที่จะถูกผูก เช่น ในการผูกจาก JSON, ตั้งค่า json:"ชื่อฟิลด์".

คุณยังสามารถระบุฟิลด์บางอย่างให้เป็นฟิลด์ที่ต้องการ หากฟิลด์มีการตกแต่ง binding:"required" และไม่มีค่าที่ให้ระหว่างการผูก จะมีข้อผิดพลาดถูกส่งกลับมา

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) ประกอบด้วยข้อมูลผู้ใช้
type User struct {
    FirstName      string     `json:"fname" validate:"required"` // ชื่อจริง, จำเป็น
    LastName       string     `json:"lname" validate:"required"` // นามสกุล, จำเป็น
    Age            uint8      `json:"age" validate:"gte=0,lte=130"` // อายุ, ระหว่าง 0 ถึง 130
    Email          string     `json:"email" validate:"required,email"` // อีเมล, จำเป็น
    FavouriteColor string     `json:"favColor" validate:"hexcolor|rgb|rgba"` // สีที่ชอบ, ต้องเป็นค่าสีที่ถูกต้องในรูปแบบฐานสิบหก หรือ RGB หรือ RGBA
    Addresses      []*Address `json:"addresses" validate:"required,dive,required"` // รายการที่อยู่, ต้องไม่ว่างเปล่าและแต่ละรายการที่อยู่จำเป็น
}

// ที่อยู่เก็บข้อมูลของที่อยู่ของผู้ใช้
type Address struct {
    Street string `json:"street" validate:"required"` // ถนน, จำเป็น
    City   string `json:"city" validate:"required"` // เมือง, จำเป็น
    Planet string `json:"planet" validate:"required"` // ดาว, จำเป็น
    Phone  string `json:"phone" validate:"required"` // โทรศัพท์, จำเป็น
}

type validationError struct {
    ActualTag string `json:"tag"` // แท็กที่แท้จริง
    Namespace string `json:"namespace"` // พื้นที่ชื่อเรียก
    Kind      string `json:"kind"` // ชนิด
    Type      string `json:"type"` // ประเภท
    Value     string `json:"value"` // ค่า
    Param     string `json:"param"` // พารามิเตอร์
}

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 {
        // จัดการข้อผิดพลาด, วิธีการที่ถูกต้องคือ...

        if errs, ok := err.(validator.ValidationErrors); ok {
            // ห่อข้อผิดพลาดเป็นรูปแบบ JSON, ไลบรารีภายในคืดข้อผิดพลาดเป็นประเภทของอินเตอร์เฟซ
            validationErrors := wrapValidationErrors(errs)

            // ส่งคืนการตอบรับปัญหาของแอปพลิเคชัน/JSON และหยุดการดำเนินการของตัวประมวลผลต่อไป
            ctx.StopWithProblem(iris.StatusBadRequest, iris.NewProblem().
                Title("ข้อผิดพลาดการตรวจสอบ").
                Detail("หนึ่งหรือมากกว่าหนึ่งฟิลด์ไม่ผ่านการตรวจสอบ").
                Type("/user/validation-errors").
                Key("errors", validationErrors))

            return
        }

        // อาจจะเป็นข้อผิดพลาด JSON ภายใน, ไม่มีข้อมูลเพิ่มเติมที่นี้
        ctx.StopWithStatus(iris.StatusInternalServerError)
        return
    }

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

func resolveErrorsDocumentation(ctx iris.Context) {
    ctx.WriteString("หน้านี้ใช้สำหรับอธิบายวิธีการแก้ไขข้อผิดพลาดการตรวจสอบแก่นักพัฒนาเว็บหรือผู้ใช้ API")
}
{
    "title": "ข้อผิดพลาดในการตรวจสอบ",
    "detail": "มีฟิลด์หนึ่งรายการหรือมากกว่าที่ไม่ผ่านการตรวจสอบ",
    "type": "http://localhost:8080/user/validation-errors",
    "status": 400,
    "fields": [
        {
            "tag": "required",
            "namespace": "User.FirstName",
            "kind": "string",
            "type": "string",
            "value": "",
            "param": ""
        },
        {
            "tag": "required",
            "namespace": "User.LastName",
            "kind": "string",
            "type": "string",
            "value": "",
            "param": ""
        }
    ]
}

การผูกพารามิเตอร์คิวรี URL

เมธอด ReadQuery ผูกเฉพาะพารามิเตอร์คิวรีเท่านั้น และไม่ผูกข้อมูลของร่างของคำขอ ใช้ ReadForm เพื่อผูกข้อมูลของร่างของคำขอ

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

การผูกข้อมูลอย่างไม่จำกัด

ผูกร่างของคำขอให้กับ "ptr" โดยขึ้นอยู่กับประเภทของเนื้อหาที่ส่งมาจากไคลเอ็นต์ เช่น JSON, XML, YAML, MessagePack, Protobuf, Form, และ 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("Person: %#+v", person)
    ctx.WriteString("Success")
}

คุณสามารถทดสอบด้วยคำสั่งต่อไปนี้:

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

การผูกพารามิเตอร์เส้นทาง 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")
}

คำขอ

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

การผูกพารามิเตอร์ของหัวเรื่องการขอ

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

คำขอ

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

การตอบกลับ

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