Request binding
Binding — so‘rov ma’lumotlarini Go struct’iga avtomatik o‘tkazish va shu bilan birga ularni tekshirish. So‘rov tanasidagi JSON, URL’dagi query parametrlar, yo‘l parametrlari va sarlavhalar — barchasi struct taglari asosida o‘qiladi.
net/httpda bu ish bir necha qadamdan iborat edi: tanani o‘qish, query parametrlarni turga o‘tkazish va validatorni alohida chaqirish. Gin’da esa ularning barchasi bitta chaqiruvda bajariladi.
Birinchi misol
type CreateBookRequest struct {
Title string `json:"title" binding:"required,min=1,max=200"`
Author string `json:"author" binding:"required"`
Year int `json:"year" binding:"required,gte=1000,lte=2100"`
}
r.POST("/books", func(c *gin.Context) {
var req CreateBookRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, req)
})
binding tagi go-playground/validator qoidalarini ishlatadi. Gin bu kutubxonani ichida o‘zi chaqiradi, lekin tag nomi validate emas, binding. Qoidalarning ma’nosi Validation darsida ko‘rilgan: required, min, max, email, oneof va boshqalar.
ShouldBindJSON ikki ishni bajaradi:
- So‘rov tanasidagi JSON’ni struct’ga o‘qiydi.
bindingtaglari bo‘yicha qiymatlarni tekshiradi.
Ikkala bosqichdagi xato ham err orqali qaytadi.
Ma’lumot manbalari
| Manba | Tag | Metod |
|---|---|---|
| JSON tana | json:"..." |
c.ShouldBindJSON(&v) |
| Query parametrlar | form:"..." |
c.ShouldBindQuery(&v) |
| Yo‘l parametrlari | uri:"..." |
c.ShouldBindUri(&v) |
| Sarlavhalar | header:"..." |
c.ShouldBindHeader(&v) |
| HTML forma | form:"..." |
c.ShouldBind(&v) |
Query parametrlar
type ListBooksQuery struct {
Page int `form:"page,default=1" binding:"gte=1"`
PerPage int `form:"per_page,default=20" binding:"gte=1,lte=100"`
Sort string `form:"sort,default=created_at" binding:"oneof=title year created_at"`
Author string `form:"author"`
}
r.GET("/books", func(c *gin.Context) {
var q ListBooksQuery
if err := c.ShouldBindQuery(&q); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// q.Page, q.PerPage allaqachon int turida va tekshirilgan
})
form tagidagi default=1 parametr berilmaganda standart qiymat qo‘yadi. strconv.Atoi va qo‘lda yoziladigan chegara tekshiruvlari endi kerak emas.
sort parametri oneof bilan cheklangan. Bu SQL injection darsida aytilgan ruxsat ro‘yxati tamoyilining amaldagi ko‘rinishi.
Yo‘l parametrlari
Gin’da yo‘l parametrlari regex bilan cheklanmaydi. Ularni binding orqali tekshirish qulay:
type BookURI struct {
ID int64 `uri:"id" binding:"required,gt=0"`
}
r.GET("/books/:id", func(c *gin.Context) {
var u BookURI
if err := c.ShouldBindUri(&u); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "id musbat son bo‘lishi kerak"})
return
}
// u.ID — int64
})
Sarlavhalar
type ClientHeaders struct {
RequestID string `header:"X-Request-ID"`
AppVersion string `header:"X-App-Version" binding:"required"`
}
Bind va ShouldBind farqi
Gin’da har bir binding metodining ikki xil varianti bor:
ShouldBind... |
Bind... |
|---|---|
| Xatoni qaytaradi | Xatoni qaytaradi va avtomatik 400 javob yozadi |
| Javobni siz o‘zingiz yozasiz | Javob text/plain formatida, matni o‘zgartirilmaydi |
// Bind: xato bo‘lsa Gin o‘zi 400 yozadi va c.Abort() chaqiradi
if err := c.BindJSON(&req); err != nil {
return // bu yerda c.JSON yozsangiz, "headers were already written" ogohlantirishi chiqadi
}
API’da deyarli har doim ShouldBind... ishlatiladi. Chunki xato javobining formati yagona standartda bo‘lishi kerak, Bind esa o‘z formatida yozib yuboradi.
Xatolarni tushunarli qilish
err.Error() matni klient uchun qulay emas:
Validatsiya xatolarini Validation darsidagi kabi maydon va xabar juftliklariga aylantiramiz. Buning uchun Gin ichidagi validator’ga murojaat qilish kerak:
import (
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
)
func init() {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterTagNameFunc(func(f reflect.StructField) string {
for _, tag := range []string{"json", "form", "uri", "header"} {
if name := strings.SplitN(f.Tag.Get(tag), ",", 2)[0]; name != "" && name != "-" {
return name
}
}
return f.Name
})
}
}
binding.Validator.Engine() Gin ishlatadigan *validator.Validate obyektini qaytaradi. Unga maxsus qoidalar va maydon nomi funksiyasini qo‘shish mumkin. RegisterTagNameFunc json, form, uri va header taglaridan birinchi topilganini oladi. Shunda xatolarda klientga tanish nomlar chiqadi.
Binding xatosi ikki xil bo‘ladi: JSON formati buzilgan yoki validatsiya o‘tmagan. Ularni ajratib qayta ishlaymiz:
func bindError(c *gin.Context, err error) {
var verrs validator.ValidationErrors
if errors.As(err, &verrs) {
fields := make(map[string]string, len(verrs))
for _, fe := range verrs {
fields[fe.Field()] = message(fe)
}
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
"error": gin.H{"code": "validation_failed", "message": "Ma’lumotlar noto‘g‘ri", "fields": fields},
})
return
}
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"error": gin.H{"code": "bad_request", "message": "So‘rov formati noto‘g‘ri"},
})
}
Qat’iy JSON o‘qish
net/http bo‘limida notanish maydonlar DisallowUnknownFields bilan rad etilgan edi. Gin’da buning uchun global sozlama bor:
Tana hajmini cheklash esa Gin’da ham http.MaxBytesReader bilan qilinadi. Uni middleware ko‘rinishida yozish qulay:
func maxBody(n int64) gin.HandlerFunc {
return func(c *gin.Context) {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, n)
c.Next()
}
}
To‘liq misol
| main.go | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | |
Sinab ko‘ramiz:
curl -X POST localhost:8080/books -d '{"title":"Sarob","author":"Abdulla Qahhor","year":1937,"tags":["roman"]}'
curl -X POST localhost:8080/books -d '{"title":"","year":3000,"tags":["a"]}'
curl -X POST localhost:8080/books -d '{"title":"X","author":"Y","year":2000,"titel":"Z"}'
curl -X POST localhost:8080/books -d '{"title": "Sarob"'
curl "localhost:8080/books"
curl "localhost:8080/books?page=2&per_page=500&sort=price"
curl localhost:8080/books/42
curl localhost:8080/books/abc
{"data":{"title":"Sarob","author":"Abdulla Qahhor","year":1937,"tags":["roman"]}}
{"error":{"code":"validation_failed","fields":{"author":"majburiy maydon","tags[0]":"kamida 2","title":"majburiy maydon","year":"2100 dan katta bo‘lmasligi kerak"},"message":"Ma’lumotlar noto‘g‘ri"}}
{"error":{"code":"bad_request","message":"So‘rov formati noto‘g‘ri"}}
{"error":{"code":"bad_request","message":"So‘rov formati noto‘g‘ri"}}
{"query":{"Page":1,"PerPage":20,"Sort":"created_at"}}
{"error":{"code":"validation_failed","fields":{"per_page":"100 dan katta bo‘lmasligi kerak","sort":"ruxsat etilgan qiymatlar: title year created_at"},"message":"Ma’lumotlar noto‘g‘ri"}}
{"data":{"id":42}}
{"error":{"code":"bad_request","message":"So‘rov formati noto‘g‘ri"}}
Natijalar:
- Validatsiya xatolari JSON maydon nomlari bilan va bir vaqtda qaytdi.
- Notanish
titelmaydoniEnableDecoderDisallowUnknownFieldstufayli rad etildi. - Query parametrlar berilmaganda
defaultqiymatlari qo‘yildi. /books/abcso‘rovidaabcqiymatiniint64ga o‘tkazib bo‘lmadi. Bu validatsiya emas, tur o‘zgartirish xatosi, shuning uchun400qaytdi.
ListBooksQuery javobida maydonlar Page, PerPage ko‘rinishida chiqdi, chunki ularda json tagi yo‘q. Bu misolda so‘rov struct’i to‘g‘ridan-to‘g‘ri javobga yozildi. Real loyihada javob uchun alohida DTO ishlatiladi.
Xulosa
ShouldBindJSON,ShouldBindQuery,ShouldBindUrivaShouldBindHeaderso‘rov ma’lumotini struct’ga o‘qiydi vabindingtaglari bo‘yicha tekshiradi.- Manba tag orqali ko‘rsatiladi:
json,form,uri,header.formtagidadefaultqiymat berish mumkin. - API’da
Bind...emas,ShouldBind...ishlatiladi. Javob formatini o‘zingiz boshqarasiz. binding.Validator.Engine()orqali Gin validatoriga maydon nomlari va maxsus qoidalar qo‘shiladi.- Validatsiya xatolari (
422) va format xatolari (400) alohida qayta ishlanadi. EnableDecoderDisallowUnknownFieldsnotanish maydonlarni rad etadi, tana hajmi esaMaxBytesReaderbilan cheklanadi.
Keyingi darsda Gin’da JWT middleware yozamiz.