Validation (go-playground/validator)
Validatsiya — kelgan ma’lumot to‘g‘ri va mantiqiy ekanini tekshirish. JSON sintaksisi to‘g‘ri bo‘lishi mumkin, lekin ichidagi qiymatlar noto‘g‘ri bo‘lishi mumkin:
Bu so‘rov Request body parsing darsidagi decodeJSON tekshiruvidan muvaffaqiyatli o‘tadi. Lekin salom email emas, yosh manfiy bo‘lolmaydi, 123 esa juda qisqa parol.
Bunday tekshiruvlarni if bilan yozish mumkin. CRUD loyihasida aynan shunday qilgan edik. Lekin maydonlar ko‘paygan sari iflar ham ko‘payadi, kod takrorlanadi va o‘qish qiyinlashadi. Go ekotizimida bu ish uchun keng qo‘llanadigan kutubxona bor: github.com/go-playground/validator. Bu kutubxona Gin framework’ining ichida ham ishlatiladi.
O‘rnatish
Birinchi misol
Qoidalar struct maydonlariga validate tagi orqali yoziladi:
Name: min qoidasi buzildi (param="2")
Email: email qoidasi buzildi (param="")
Age: gte qoidasi buzildi (param="14")
Password: min qoidasi buzildi (param="8")
Kod qanday ishladi:
validator.New()validator obyektini yaratadi. U birinchi ishlatilganda har bir struct turi haqidagi ma’lumotni keshlaydi. Shu sababli uni bir marta yaratib, butun dastur davomida qayta ishlatish kerak.WithRequiredStructEnabled()— ichki struct maydonlaridarequiredtagining to‘g‘ri ishlashini yoqadi. Kutubxonaning keyingi asosiy versiyasida bu xatti-harakat standart bo‘lishi rejalashtirilgan, shuning uchun uni hozirdan yoqib qo‘yish tavsiya etiladi.validate.Struct(in)barcha qoidalarni tekshiradi va buzilganlarinivalidator.ValidationErrorsko‘rinishida qaytaradi.- Har bir
FieldErrordan maydon nomi (Field), qoida nomi (Tag) va qoida parametri (Param) olinadi.
Ko‘p ishlatiladigan qoidalar
Qoidalar vergul bilan ajratiladi va chapdan o‘ngga tekshiriladi.
| Qoida | Ma’nosi | Misol |
|---|---|---|
required |
Nol qiymat bo‘lmasligi kerak | validate:"required" |
omitempty |
Bo‘sh bo‘lsa, keyingi qoidalar tekshirilmaydi | validate:"omitempty,url" |
min, max |
Satr uzunligi, son qiymati yoki slice uzunligi | validate:"min=3,max=100" |
len |
Aniq uzunlik | validate:"len=9" |
gt, gte, lt, lte |
Katta, katta yoki teng, kichik, kichik yoki teng | validate:"gte=0" |
oneof |
Ro‘yxatdagi qiymatlardan biri | validate:"oneof=draft published" |
email |
Email formati | validate:"email" |
url, http_url |
URL formati | validate:"http_url" |
uuid4 |
UUID v4 | validate:"uuid4" |
numeric, alphanum |
Faqat raqam, faqat harf va raqam | validate:"numeric" |
eqfield |
Boshqa maydonga teng | validate:"eqfield=Password" |
dive |
Slice yoki map elementlarini tekshirish | validate:"dive,min=2" |
Info
Satrlar uchun min va max baytlarni emas, belgilarni (rune) sanaydi. "O‘tkir" satri 8 bayt, lekin 6 belgi. Shuning uchun max=6 qoidasidan o‘tadi.
required va nol qiymat
required qiymat o‘z turining nol qiymatiga teng emasligini tekshiradi: satr uchun "", son uchun 0, ko‘rsatkich uchun nil. Bu ba’zan muammo tug‘diradi.
Masalan, Discount int \validate:"required"`maydonida0qiymatini yuborib bo‘lmaydi, chunki0nol qiymat. Chegirma0` bo‘lishi mumkin bo‘lsa, maydonni ko‘rsatkich qiling:
Endi required faqat maydon yuborilganini (nil emasligini) tekshiradi. gte va lte esa ko‘rsatkich ko‘rsatayotgan qiymatga qo‘llanadi.
Ichki struct va slice’lar
type Address struct {
City string `json:"city" validate:"required"`
Street string `json:"street" validate:"required"`
}
type OrderInput struct {
Address Address `json:"address" validate:"required"`
Items []Item `json:"items" validate:"required,min=1,max=50,dive"`
Tags []string `json:"tags" validate:"max=5,dive,min=2,max=20"`
}
Addressichidagi maydonlar avtomatik tekshiriladi.divedan oldingi qoidalar slice’ning o‘ziga, keyingilari esa har bir elementga qo‘llanadi.Tagsuchun: ko‘pi bilan 5 ta teg, har biri 2 dan 20 belgigacha.
JSON maydon nomlarini ishlatish
Xatolarda Go maydon nomi chiqadi: Email, Password. Klient esa JSON nomlarini biladi: email, password. Validatorga JSON tagidagi nomni ishlatishni o‘rgatamiz:
validate.RegisterTagNameFunc(func(f reflect.StructField) string {
name := strings.SplitN(f.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})
Endi fe.Field() email qaytaradi. Ichki maydonlar uchun fe.Namespace() to‘liq yo‘lni beradi, masalan OrderInput.address.city.
O‘zbekcha xato xabarlari
fe.Tag() va fe.Param() asosida o‘zimizning xabarlarimizni tuzamiz:
func message(fe validator.FieldError) string {
switch fe.Tag() {
case "required":
return "majburiy maydon"
case "email":
return "email formati noto‘g‘ri"
case "min":
if fe.Kind() == reflect.String {
return fmt.Sprintf("kamida %s ta belgi bo‘lishi kerak", fe.Param())
}
return fmt.Sprintf("kamida %s bo‘lishi kerak", fe.Param())
case "max":
if fe.Kind() == reflect.String {
return fmt.Sprintf("ko‘pi bilan %s ta belgi bo‘lishi kerak", fe.Param())
}
return fmt.Sprintf("ko‘pi bilan %s bo‘lishi kerak", fe.Param())
case "gte":
return fmt.Sprintf("%s dan kichik bo‘lmasligi kerak", fe.Param())
case "lte":
return fmt.Sprintf("%s dan katta bo‘lmasligi kerak", fe.Param())
case "oneof":
return fmt.Sprintf("quyidagilardan biri bo‘lishi kerak: %s", fe.Param())
case "eqfield":
return fmt.Sprintf("%s maydoniga teng bo‘lishi kerak", fe.Param())
case "uzphone":
return "telefon raqami +998XXXXXXXXX formatida bo‘lishi kerak"
default:
return "qiymat noto‘g‘ri"
}
}
fe.Kind() maydon turini beradi. min=8 satr uchun "8 ta belgi", son uchun esa "8 dan kichik emas" degan ma’noni bildiradi. Xabar ham shunga qarab tanlanadi.
Kutubxonada tayyor tarjima tizimi ham bor: go-playground/universal-translator paketi. Lekin unda o‘zbek tili uchun tayyor tarjimalar yo‘q. Bir nechta qoida uchun yuqoridagi switch ancha sodda va tushunarli.
Maxsus qoida qo‘shish
Tayyor qoidalar yetmasa, o‘zingiz yozasiz. O‘zbekiston telefon raqami uchun uzphone qoidasini qo‘shamiz:
var uzPhoneRe = regexp.MustCompile(`^\+998\d{9}$`)
validate.RegisterValidation("uzphone", func(fl validator.FieldLevel) bool {
return uzPhoneRe.MatchString(fl.Field().String())
})
Endi uni boshqa qoidalar kabi ishlatish mumkin:
fl.Field() tekshirilayotgan maydon qiymatini reflect.Value ko‘rinishida beradi. Funksiya true qaytarsa, qiymat to‘g‘ri hisoblanadi.
To‘liq misol: API’da validatsiya
| 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 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | |
Noto‘g‘ri ma’lumot yuboramiz:
curl -X POST http://localhost:8080/signup -d '{
"name": "A",
"email": "salom",
"phone": "901234567",
"age": 10,
"role": "admin",
"password": "12345678",
"password_confirm": "12345679",
"interests": ["go", "x"],
"address": {"city": "T"}
}'
{
"error": "ma’lumotlar noto‘g‘ri",
"fields": {
"address.city": "kamida 2 ta belgi bo‘lishi kerak",
"address.street": "majburiy maydon",
"age": "14 dan kichik bo‘lmasligi kerak",
"email": "email formati noto‘g‘ri",
"interests[1]": "kamida 2 ta belgi bo‘lishi kerak",
"name": "kamida 2 ta belgi bo‘lishi kerak",
"password_confirm": "password maydoniga teng bo‘lishi kerak",
"phone": "telefon raqami +998XXXXXXXXX formatida bo‘lishi kerak",
"role": "quyidagilardan biri bo‘lishi kerak: reader author"
}
}
(Javob o‘qish qulay bo‘lishi uchun formatlangan.)
To‘g‘ri ma’lumot:
curl -X POST http://localhost:8080/signup -d '{
"name": "Aziza",
"email": "[email protected]",
"phone": "+998901234567",
"age": 21,
"password": "juda-maxfiy-parol",
"password_confirm": "juda-maxfiy-parol",
"address": {"city": "Toshkent", "street": "Navoiy ko‘chasi, 5"}
}'
{"email":"[email protected]","message":"ro‘yxatdan o‘tdingiz"}
Natijani tahlil qilamiz:
- Barcha xatolar bir vaqtda qaytdi. Klient formadagi barcha xatolarni bir urinishda ko‘rsatadi, foydalanuvchi ularni birma-bir tuzatib, qayta-qayta yuborishga majbur bo‘lmaydi.
- Maydon nomlari JSON’dagidek:
password_confirm,address.city,interests[1]. Frontend ularni to‘g‘ridan-to‘g‘ri forma maydonlariga bog‘lashi mumkin. roleyuborilmasa,omitemptytufayli tekshirilmaydi. Yuborilsa, faqatreaderyokiauthorbo‘lishi mumkin.adminrolini klient o‘zi tanlab ololmaydi.- Status kod —
422 Unprocessable Entity. So‘rov sintaktik jihatdan to‘g‘ri, lekin mazmuni noto‘g‘ri bo‘lgani uchun shu kod tanlandi. Ko‘p API’lar bu holatda400ham qaytaradi. Qaysi birini tanlasangiz ham, butun API bo‘ylab izchil bo‘ling.
Warning
Parol uchun max=72 qo‘yilishi tasodifiy emas. bcrypt algoritmi 72 baytdan uzun parollarni qabul qilmaydi. Batafsil Parollarni xeshlash darsida. Belgilar va baytlar soni farq qilishi mumkinligini ham unutmang: kirill harflarining har biri 2 bayt joy egallaydi.
Validatsiya qayerda bo‘lishi kerak?
Validatsiya ikki darajada bo‘ladi:
| Daraja | Nimani tekshiradi | Qayerda |
|---|---|---|
| Kirish validatsiyasi | Format, uzunlik, majburiy maydonlar | Handler yoki kirish DTO’si |
| Biznes validatsiyasi | "Bu email band emasmi?", "Omborda yetarli mahsulot bormi?" | Service |
validate taglari birinchi darajaga mos keladi. Database’ga murojaat qilishni talab qiladigan tekshiruvlarni validator ichiga yozmang. Ular service qatlamiga tegishli.
Xulosa
validator.New()ni bir marta yarating va qayta ishlating.- Qoidalar
validatetagida vergul bilan yoziladi.diveslice elementlarini,omitemptyesa ixtiyoriy maydonlarni boshqaradi. requirednol qiymatni rad etadi.0yokifalseruxsat etilgan bo‘lsa, ko‘rsatkich turidan foydalaning.RegisterTagNameFuncxatolarda JSON maydon nomlarini ko‘rsatadi.FieldErrorningTagvaParamqiymatlari asosida o‘zbekcha xabarlar tuziladi.RegisterValidationbilan maxsus qoidalar qo‘shiladi.
Keyingi darsda barcha xato javoblarini, jumladan validatsiya xatolarini, yagona standart formatga keltiramiz.