الأقسام السابقة قد قدمت طريقة قراءة معلمات الطلب مباشرة. إذا كان من الصعب قراءة كل معلمة على حدة، فإن إطار العمل iris يوفر أيضًا آلية ربط المعلمات، والتي يمكن أن ترتبط بمعلمات الطلب إلى هيكل بيانات، وتدعم أيضًا آلية التحقق من صحة معلمة النموذج.

الارتباط بالنماذج والتحقق من الصحة

لربط جسم الطلب بنوع معين، استخدم الارتباط بالنموذج. حاليًا، نحن ندعم أنواع ارتباط مثل "JSON"، "JSONProtobuf"، "Protobuf"، "MsgPack"، "XML"، "YAML"، وقيم النموذج القياسي (foo=bar&boo=baz).

// أدناه تعريفات الدوال لربط معاملات الطلب من مختلف الصيغ بنوع معين
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:"مطلوب" ولم يتم توفير قيمة أثناء الربط، سيتم إرجاع خطأ.

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

// النوع المستخدم لتخزين معلومات المستخدم.
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)

			// إرجاع استجابة بتنسيق application/json+problem وإيقاف تنفيذ المعالجات التالية
			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": "حسنًا"})
}

func resolveErrorsDocumentation(ctx iris.Context) {
	ctx.WriteString("يتم استخدام هذه الصفحة لشرح كيفية حل أخطاء التحقق لمطوري الويب أو مستخدمي واجهة برمجة التطبيقات")
}
{
    "عنوان": "خطأ في التحقق",
    "تفاصيل": "فشلت أحد أو أكثر من الحقول في التحقق",
    "نوع": "http://localhost:8080/user/validation-errors",
    "الحالة": 400,
    "حقول": [
        {
            "وسم": "مطلوب",
            "نطاق": "User.FirstName",
            "نوع": "سلسلة",
            "نوع": "سلسلة",
            "قيمة": "",
            "معلمة": ""
        },
        {
            "وسم": "مطلوب",
            "نطاق": "User.LastName",
            "نوع": "سلسلة",
            "نوع": "سلسلة",
            "قيمة": "",
            "معلمة": ""
        }
    ]
}

ربط معلمات الاستعلام في عنوان 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("الشخص: %#+v", person)
    ctx.WriteString("نجاح")
}

ربط بيانات متغيرة

قم بربط جسم الطلب إلى "ptr" استنادًا إلى نوع محتوى البيانات المرسلة بواسطة العميل، مثل JSON، XML، YAML، MessagePack، Protobuf، نموذج، واستعلام 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("الشخص: %#+v", person)
    ctx.WriteString("نجاح")
}

يمكنك اختبار ذلك باستخدام الأمر التالي:

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