178 lines
5.1 KiB
Go
178 lines
5.1 KiB
Go
package response
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
apperrors "veza-backend-api/internal/errors"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// APIResponse is the unified response envelope
|
|
type APIResponse struct {
|
|
Success bool `json:"success"`
|
|
Data interface{} `json:"data,omitempty"`
|
|
Error interface{} `json:"error,omitempty"`
|
|
}
|
|
|
|
// Success sends a successful JSON response
|
|
func Success(c *gin.Context, data interface{}, message ...string) {
|
|
response := gin.H{
|
|
"success": true,
|
|
"data": data,
|
|
}
|
|
if len(message) > 0 {
|
|
response["message"] = message[0]
|
|
}
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// Created sends a 201 Created response
|
|
func Created(c *gin.Context, data interface{}, message ...string) {
|
|
response := gin.H{
|
|
"success": true,
|
|
"data": data,
|
|
}
|
|
if len(message) > 0 {
|
|
response["message"] = message[0]
|
|
}
|
|
c.JSON(http.StatusCreated, response)
|
|
}
|
|
|
|
// BadRequest sends a 400 Bad Request response
|
|
// P0: Migré vers format AppError standardisé
|
|
func BadRequest(c *gin.Context, message string) {
|
|
Error(c, http.StatusBadRequest, message)
|
|
}
|
|
|
|
// Unauthorized sends a 401 Unauthorized response
|
|
// P0: Migré vers format AppError standardisé
|
|
func Unauthorized(c *gin.Context, message string) {
|
|
Error(c, http.StatusUnauthorized, message)
|
|
}
|
|
|
|
// Forbidden sends a 403 Forbidden response
|
|
// P0: Migré vers format AppError standardisé
|
|
func Forbidden(c *gin.Context, message string) {
|
|
Error(c, http.StatusForbidden, message)
|
|
}
|
|
|
|
// NotFound sends a 404 Not Found response
|
|
// P0: Migré vers format AppError standardisé
|
|
func NotFound(c *gin.Context, message string) {
|
|
Error(c, http.StatusNotFound, message)
|
|
}
|
|
|
|
// InternalServerError sends a 500 Internal Server Error response
|
|
// P0: Migré vers format AppError standardisé
|
|
func InternalServerError(c *gin.Context, message string) {
|
|
Error(c, http.StatusInternalServerError, message)
|
|
}
|
|
|
|
// Error sends a custom error response with specified status code
|
|
// P0: Migré vers format AppError standardisé
|
|
func Error(c *gin.Context, status int, message string) {
|
|
var errorCode apperrors.ErrorCode
|
|
switch status {
|
|
case http.StatusBadRequest:
|
|
errorCode = apperrors.ErrCodeValidation
|
|
case http.StatusUnauthorized:
|
|
errorCode = apperrors.ErrCodeUnauthorized
|
|
case http.StatusForbidden:
|
|
errorCode = apperrors.ErrCodeForbidden
|
|
case http.StatusNotFound:
|
|
errorCode = apperrors.ErrCodeNotFound
|
|
case http.StatusConflict:
|
|
errorCode = apperrors.ErrCodeConflict
|
|
case http.StatusTooManyRequests:
|
|
errorCode = apperrors.ErrCodeRateLimitExceeded
|
|
case http.StatusServiceUnavailable:
|
|
errorCode = apperrors.ErrCodeServiceUnavailable
|
|
default:
|
|
errorCode = apperrors.ErrCodeInternal
|
|
}
|
|
appErr := apperrors.New(errorCode, message)
|
|
RespondWithAppError(c, appErr)
|
|
}
|
|
|
|
// RespondWithAppError répond avec une AppError au format standardisé ORIGIN_API_SPECIFICATION
|
|
// Le code HTTP est dérivé automatiquement du ErrorCode
|
|
func RespondWithAppError(c *gin.Context, appErr *apperrors.AppError) {
|
|
statusCode := mapErrorCodeToHTTPStatus(appErr.Code)
|
|
errorData := struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
Details []apperrors.ErrorDetail `json:"details,omitempty"`
|
|
RequestID string `json:"request_id,omitempty"`
|
|
Timestamp string `json:"timestamp"`
|
|
Context map[string]interface{} `json:"context,omitempty"`
|
|
}{
|
|
Code: int(appErr.Code),
|
|
Message: appErr.Message,
|
|
Details: appErr.Details,
|
|
RequestID: c.GetString("request_id"),
|
|
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
|
Context: appErr.Context,
|
|
}
|
|
c.JSON(statusCode, APIResponse{
|
|
Success: false,
|
|
Data: nil,
|
|
Error: errorData,
|
|
})
|
|
}
|
|
|
|
func mapErrorCodeToHTTPStatus(code apperrors.ErrorCode) int {
|
|
if code >= 1000 && code < 2000 {
|
|
switch code {
|
|
case apperrors.ErrCodeForbidden, apperrors.ErrCodeQuotaExceeded:
|
|
return http.StatusForbidden
|
|
default:
|
|
return http.StatusUnauthorized
|
|
}
|
|
}
|
|
if code >= 2000 && code < 3000 {
|
|
return http.StatusBadRequest
|
|
}
|
|
if code >= 3000 && code < 4000 {
|
|
switch code {
|
|
case apperrors.ErrCodeNotFound:
|
|
return http.StatusNotFound
|
|
case apperrors.ErrCodeAlreadyExists, apperrors.ErrCodeConflict:
|
|
return http.StatusConflict
|
|
case apperrors.ErrCodeLocked:
|
|
return http.StatusLocked
|
|
default:
|
|
return http.StatusNotFound
|
|
}
|
|
}
|
|
if code >= 4000 && code < 5000 {
|
|
return http.StatusUnprocessableEntity
|
|
}
|
|
if code >= 5000 && code < 5100 {
|
|
return http.StatusTooManyRequests
|
|
}
|
|
if code >= 6000 && code < 7000 {
|
|
if code == apperrors.ErrCodeServiceUnavailable {
|
|
return http.StatusServiceUnavailable
|
|
}
|
|
return http.StatusBadGateway
|
|
}
|
|
if code >= 9000 && code < 10000 {
|
|
return http.StatusInternalServerError
|
|
}
|
|
return http.StatusInternalServerError
|
|
}
|
|
|
|
// ValidationError sends a 400 Bad Request response with detailed validation errors
|
|
func ValidationError(c *gin.Context, message string, details map[string]string) {
|
|
errorDetails := make([]apperrors.ErrorDetail, 0, len(details))
|
|
for field, msg := range details {
|
|
errorDetails = append(errorDetails, apperrors.ErrorDetail{
|
|
Field: field,
|
|
Message: msg,
|
|
})
|
|
}
|
|
appErr := apperrors.NewValidationError(message, errorDetails...)
|
|
RespondWithAppError(c, appErr)
|
|
}
|