/
t3
/
iam-server
Обзор
Документация
Войти
/
t3
/
iam-server
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
internal/api/errors.go
159 строк
5 KB
Ivan Shibkikh
added system roles
22 июл 2026, 16:09
22 июл 2026, 16:09
6fd3b35
Код
Авторство
О чём код?
package api import ( "encoding/json" "errors" "net/http" "gitverse.ru/t3/iam-server/internal/storage" ) // --- Error response shape --- // ErrorBody describes a structured API error. type ErrorBody struct { Code string `json:"code"` Message string `json:"message"` Fields []FieldError `json:"fields,omitempty"` RequestID string `json:"request_id,omitempty"` } // ErrorResponse is the top-level JSON envelope for errors. type ErrorResponse struct { Error ErrorBody `json:"error"` } // FieldError points to a specific field that failed validation. type FieldError struct { Field string `json:"field"` Issue string `json:"issue"` } // --- Error code constants --- const ( CodeValidationError = "validation_error" CodeNotFound = "not_found" CodeConflict = "conflict" CodeBadRequest = "bad_request" CodeUnauthorized = "unauthorized" CodeForbidden = "forbidden" CodePreconditionFailed = "precondition_failed" CodeRateLimitExceeded = "rate_limit_exceeded" CodeRequestEntityTooLarge = "request_too_large" CodeInternalError = "internal_error" ) // --- Write helpers --- // writeError writes a structured error response with the given HTTP status, code and message. func writeError(w http.ResponseWriter, status int, code, message string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(ErrorResponse{ Error: ErrorBody{Code: code, Message: message}, }) } // writeValidationError writes a 400 validation_error with per-field details. func writeValidationError(w http.ResponseWriter, message string, fields []FieldError) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(ErrorResponse{ Error: ErrorBody{ Code: CodeValidationError, Message: message, Fields: fields, }, }) } // writeInternalError writes a 500 internal_error and logs nothing extra // (the detailed error should have been logged by the caller if needed). func writeInternalError(w http.ResponseWriter) { writeError(w, http.StatusInternalServerError, CodeInternalError, "internal server error") } // writeNotFound writes a 404 not_found with a contextual message. func writeNotFound(w http.ResponseWriter, what string) { writeError(w, http.StatusNotFound, CodeNotFound, what+" not found") } // writeConflict writes a 409 conflict with a contextual message. func writeConflict(w http.ResponseWriter, message string) { writeError(w, http.StatusConflict, CodeConflict, message) } // writeBadRequest writes a 400 bad_request (not a field-level validation). func writeBadRequest(w http.ResponseWriter, message string) { writeError(w, http.StatusBadRequest, CodeBadRequest, message) } // writeUnauthorized writes a 401 unauthorized. func writeUnauthorized(w http.ResponseWriter, message string) { writeError(w, http.StatusUnauthorized, CodeUnauthorized, message) } // writeForbidden writes a 403 forbidden. func writeForbidden(w http.ResponseWriter, message string) { writeError(w, http.StatusForbidden, CodeForbidden, message) } // writePreconditionFailed writes a 412 precondition_failed. func writePreconditionFailed(w http.ResponseWriter, message string) { writeError(w, http.StatusPreconditionFailed, CodePreconditionFailed, message) } // writeRateLimitExceeded writes a 429 rate_limit_exceeded. func writeRateLimitExceeded(w http.ResponseWriter, message string) { writeError(w, http.StatusTooManyRequests, CodeRateLimitExceeded, message) } // writeRequestEntityTooLarge writes a 413 request_too_large. func writeRequestEntityTooLarge(w http.ResponseWriter, message string) { writeError(w, http.StatusRequestEntityTooLarge, CodeRequestEntityTooLarge, message) } // --- Response helpers --- // jsonEncode writes v as JSON to the response writer. It sets Content-Type // and does NOT call WriteHeader — the caller is responsible for the status code. func jsonEncode(w http.ResponseWriter, v any) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(v) } // --- Storage-error to HTTP mapping --- // storageErrToHTTP maps a known storage error to an HTTP status and error code. // Returns (0, "") when the error is not classified — the caller should treat it as 500. func storageErrToHTTP(err error) (status int, code string) { switch { case errors.Is(err, storage.ErrNotFound): return http.StatusNotFound, CodeNotFound case errors.Is(err, storage.ErrConflict): return http.StatusConflict, CodeConflict case errors.Is(err, storage.ErrVersionConflict): return http.StatusPreconditionFailed, CodePreconditionFailed case errors.Is(err, storage.ErrInvalidState): return http.StatusConflict, CodeConflict case errors.Is(err, storage.ErrForeignKey): return http.StatusNotFound, CodeNotFound // referenced entity not found case errors.Is(err, storage.ErrSystemRole): return http.StatusForbidden, CodeForbidden default: return 0, "" } } // writeStorageError writes an appropriate HTTP error based on a storage error. // Returns true if the error was classified and written; false means the caller // should write a generic 500. func writeStorageError(w http.ResponseWriter, err error) bool { status, code := storageErrToHTTP(err) if status == 0 { return false } writeError(w, status, code, err.Error()) return true }