Xatoliklarni standart formatda qaytarish
Muvaffaqiyatli javoblar kabi xato javoblari ham izchil bo‘lishi kerak. Klient dasturi har qanday xatoni bitta usul bilan qayta ishlay olishi lozim. Tasavvur qiling, API’da xatolar quyidagicha qaytadi:
POST /books → 400 kitob nomi majburiy (oddiy matn)
GET /books/99 → 404 {"message": "not found"}
POST /login → 401 {"error": true, "msg": "Xato parol"}
POST /orders → 422 {"errors": [{"field": "qty", ...}]}
Har bir endpoint o‘z uslubida javob beryapti. Mobil ilova dasturchisi har bir holat uchun alohida kod yozishga majbur bo‘ladi. Bu darsda barcha xatolar uchun yagona format tanlaymiz va uni Go’da amalga oshiramiz.
Yaxshi xato javobida nima bo‘lishi kerak?
| Maydon | Kim uchun | Nima uchun |
|---|---|---|
| HTTP status kod | Dastur | Xato turini umumiy ko‘rinishda bildiradi |
| Mashina o‘qiydigan kod | Dastur | Aniq holatni ajratadi: email_taken, out_of_stock |
| Xabar | Inson | Nima bo‘lganini tushuntiradi |
| Maydon xatolari | Dastur va inson | Formadagi qaysi maydon noto‘g‘ri ekanini ko‘rsatadi |
| Request ID | Qo‘llab-quvvatlash xizmati | Xatoni loglardan topishga yordam beradi |
Nima uchun xabar yetarli emas? Klient dasturi xabar matniga qarab qaror qilmasligi kerak. Xabar o‘zgarishi, tarjima qilinishi yoki aniqlashtirilishi mumkin. Mashina o‘qiydigan kod esa barqaror shartnoma hisoblanadi:
// ❌ Mo‘rt: xabar o‘zgarsa, kod ishlamay qoladi
if (err.message === "Bu email band") { ... }
// ✅ Barqaror
if (err.code === "email_taken") { ... }
Format
Bu kursda quyidagi formatdan foydalanamiz:
{
"error": {
"code": "validation_failed",
"message": "Ma’lumotlar noto‘g‘ri",
"fields": {
"email": "email formati noto‘g‘ri",
"age": "14 dan kichik bo‘lmasligi kerak"
},
"request_id": "5c1e0a9b3d7f2e84"
}
}
- Xato har doim
errorkaliti ichida keladi. Muvaffaqiyatli javob esadataichida keladi. Klient qaysi holat ekanini kalitning o‘zidan ham biladi. fieldsfaqat validatsiya xatolarida bo‘ladi.request_idlogging middleware yaratgan ID bilan bir xil.
Xato kodlari katalogi
Kodlarni tasodifiy satrlar sifatida emas, bitta joyda konstantalar ko‘rinishida e’lon qilamiz:
| Kod | Status | Qachon |
|---|---|---|
bad_request |
400 | So‘rov formati noto‘g‘ri, JSON buzilgan |
validation_failed |
422 | Maydonlar qoidalarga mos emas |
unauthorized |
401 | Autentifikatsiya yo‘q yoki token yaroqsiz |
forbidden |
403 | Ruxsat yo‘q |
not_found |
404 | Resurs topilmadi |
conflict |
409 | Holat ziddiyati: dublikat, eskirgan versiya |
rate_limited |
429 | So‘rovlar juda ko‘p |
internal |
500 | Kutilmagan server xatosi |
Kerak bo‘lsa, aniqroq kodlar qo‘shiladi: email_taken, book_out_of_stock. Ularni API hujjatida albatta yozib qo‘ying.
Go’da amalga oshirish
Xato turi
type Error struct {
Status int `json:"-"`
Code string `json:"code"`
Message string `json:"message"`
Fields map[string]string `json:"fields,omitempty"`
Err error `json:"-"` // ichki sabab, klientga ko‘rsatilmaydi
}
func (e *Error) Error() string {
if e.Err != nil {
return e.Code + ": " + e.Err.Error()
}
return e.Code + ": " + e.Message
}
func (e *Error) Unwrap() error { return e.Err }
StatusvaErrjson:"-"bilan belgilangan. Status kod HTTP sarlavhada yuboriladi, ichki sabab esa faqat logga yoziladi.Unwrapmetodierrors.Isvaerrors.Asfunksiyalariga ichki xatoni ko‘rish imkonini beradi.
Konstruktorlar
func NotFound(what string) *Error {
return &Error{Status: http.StatusNotFound, Code: "not_found",
Message: what + " topilmadi"}
}
func Validation(fields map[string]string) *Error {
return &Error{Status: http.StatusUnprocessableEntity, Code: "validation_failed",
Message: "Ma’lumotlar noto‘g‘ri", Fields: fields}
}
func Conflict(code, msg string) *Error {
return &Error{Status: http.StatusConflict, Code: code, Message: msg}
}
func Internal(err error) *Error {
return &Error{Status: http.StatusInternalServerError, Code: "internal",
Message: "Ichki server xatosi", Err: err}
}
Handler endi return NotFound("kitob") deb yozadi va status kod, kod nomi hamda xabarni har safar qaytadan o‘ylamaydi.
Xatoni yozadigan yagona funksiya
func WriteError(w http.ResponseWriter, r *http.Request, err error) {
var e *Error
if !errors.As(err, &e) {
e = Internal(err) // noma’lum xato — har doim 500
}
if e.Status >= 500 {
slog.Error("server xatosi", "err", e, "path", r.URL.Path,
"request_id", RequestID(r.Context()))
}
body := struct {
Error struct {
*Error
RequestID string `json:"request_id,omitempty"`
} `json:"error"`
}{}
body.Error.Error = e
body.Error.RequestID = RequestID(r.Context())
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(e.Status)
json.NewEncoder(w).Encode(body)
}
WriteError ikki narsani kafolatlaydi:
- Har qanday xato bir xil formatda qaytadi.
*Errorbo‘lmagan xato, masalan database xatosi, hech qachon klientga ko‘rsatilmaydi. Uinternalkodi bilan yashiriladi va logga yoziladi.
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 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | |
Note
Natijani o‘qish oson bo‘lishi uchun misoldagi withRequestID ketma-ket raqamlar (req-001, req-002) yaratadi. Hisoblagich atomic.Int64 turida, shuning uchun bir vaqtda kelgan so‘rovlarda ham xavfsiz. Real loyihada esa Logging middleware darsidagi tasodifiy ID ishlatiladi.
Sinab ko‘ramiz:
curl http://localhost:8080/books/abc
curl http://localhost:8080/books/7
curl -X POST http://localhost:8080/books -d '{"title":" ","year":0}'
curl -X POST http://localhost:8080/books -d '{"title":"o‘tkan kunlar","year":1926}'
curl -X POST http://localhost:8080/books -d '{"title":"Sarob","year":1937}'
{"error":{"code":"bad_request","message":"id son bo‘lishi kerak","request_id":"req-001"}}
{"error":{"code":"not_found","message":"kitob topilmadi","request_id":"req-002"}}
{"error":{"code":"validation_failed","message":"Ma’lumotlar noto‘g‘ri","fields":{"title":"majburiy maydon","year":"musbat son bo‘lishi kerak"},"request_id":"req-003"}}
{"error":{"code":"book_exists","message":"Bunday nomli kitob allaqachon mavjud","request_id":"req-004"}}
{"error":{"code":"internal","message":"Ichki server xatosi","request_id":"req-005"}}
Oxirgi so‘rovda database xatosi yuz berdi. Klient faqat umumiy xabar va request ID’ni oldi. Server logida esa to‘liq sabab bor:
{"level":"ERROR","msg":"server xatosi","err":"internal: kitobni saqlash: pq: disk is full","path":"/books","request_id":"req-005"}
Foydalanuvchi qo‘llab-quvvatlash xizmatiga req-005 raqamini aytsa, muammo loglardan darhol topiladi.
Standart: RFC 9457 Problem Details
HTTP API xatolari uchun rasmiy standart ham bor: RFC 9457 — "Problem Details for HTTP APIs". U avvalgi RFC 7807 standartining o‘rnini egallagan. Unda javob application/problem+json turida yuboriladi:
{
"type": "https://api.example.uz/errors/validation-failed",
"title": "Ma’lumotlar noto‘g‘ri",
"status": 422,
"detail": "Ikki maydon qoidalarga mos kelmadi",
"instance": "/books",
"errors": {"title": "majburiy maydon"}
}
| Maydon | Ma’nosi |
|---|---|
type |
Xato turini aniqlovchi URI. Uning manzilida xato haqida hujjat bo‘lishi mumkin |
title |
Xato turining qisqa nomi |
status |
HTTP status kod |
detail |
Aynan shu holat bo‘yicha tushuntirish |
instance |
Xato yuz bergan resurs |
Standartda qo‘shimcha maydonlar qo‘shishga ruxsat berilgan, masalan yuqoridagi errors.
Qaysi formatni tanlash kerak? Agar API ko‘p tashqi jamoalar uchun ochiq bo‘lsa, standart format ular uchun qulayroq. Ichki API uchun bu darsdagi soddaroq {"error": {...}} formati ham yetarli. Eng muhimi — tanlangan formatni barcha endpointlarda izchil qo‘llash.
Yuqoridagi WriteError funksiyasini Problem Details formatiga o‘tkazish uchun faqat JSON tuzilishini va Content-Type sarlavhasini o‘zgartirish kifoya. Handlerlar o‘zgarmaydi, chunki ular faqat *Error qaytaradi.
Xulosa
- Xato javobida status kod, mashina o‘qiydigan
code, inson uchunmessageva kerak bo‘lsafieldsbo‘lsin. - Klient kodi xabar matniga emas,
codeqiymatiga tayanishi kerak. - Barcha xatolar bitta
WriteErrorfunksiyasi orqali yozilsin. - Noma’lum xatolar doim
500va umumiy xabar bilan yashiriladi. Tafsilotlar logga yoziladi. - Request ID klient ko‘rgan xatoni server logi bilan bog‘laydi.
- Ommaviy API uchun RFC 9457 Problem Details standartini ko‘rib chiqing.
Shu bilan JSON bo‘limi tugadi. Keyingi bo‘limda ma’lumotlarni PostgreSQL’da saqlashni o‘rganamiz.