// Package httputil provides HTTP utility functions for the KnowFoolery application. package httputil import ( "github.com/gofiber/fiber/v3" "knowfoolery/backend/shared/domain/errors" ) // ErrorResponse represents a standard error response. type ErrorResponse struct { Error bool `json:"error"` Code string `json:"code,omitempty"` Message string `json:"message"` Details string `json:"details,omitempty"` } // NewErrorResponse creates a new ErrorResponse. func NewErrorResponse(code, message, details string) ErrorResponse { return ErrorResponse{ Error: true, Code: code, Message: message, Details: details, } } // SendError sends an error response with the appropriate HTTP status code. func SendError(c fiber.Ctx, err error) error { statusCode, response := MapError(err) return c.Status(statusCode).JSON(response) } // MapError maps a domain error to an HTTP status code and response. func MapError(err error) (int, ErrorResponse) { if err == nil { return fiber.StatusInternalServerError, NewErrorResponse( "INTERNAL", "An unexpected error occurred", "", ) } domainErr, ok := err.(*errors.DomainError) if !ok { return fiber.StatusInternalServerError, NewErrorResponse( "INTERNAL", "An unexpected error occurred", err.Error(), ) } statusCode := mapErrorCodeToStatus(domainErr.Code) return statusCode, NewErrorResponse( domainErr.Code.String(), domainErr.Message, "", ) } // mapErrorCodeToStatus maps an error code to an HTTP status code. func mapErrorCodeToStatus(code errors.ErrorCode) int { switch code { case errors.CodeNotFound, errors.CodeQuestionNotFound, errors.CodeUserNotFound: return fiber.StatusNotFound case errors.CodeInvalidInput, errors.CodeValidationFailed, errors.CodeInvalidPlayerName, errors.CodeInvalidAnswer: return fiber.StatusBadRequest case errors.CodeUnauthorized, errors.CodeInvalidToken, errors.CodeTokenExpired: return fiber.StatusUnauthorized case errors.CodeForbidden, errors.CodeMFARequired: return fiber.StatusForbidden case errors.CodeConflict, errors.CodeUserAlreadyExists, errors.CodeGameInProgress: return fiber.StatusConflict case errors.CodeRateLimitExceeded: return fiber.StatusTooManyRequests case errors.CodeSessionExpired, errors.CodeSessionNotActive: return fiber.StatusGone case errors.CodeMaxAttemptsReached: return fiber.StatusUnprocessableEntity default: return fiber.StatusInternalServerError } } // BadRequest sends a 400 Bad Request response. func BadRequest(c fiber.Ctx, message string) error { return c.Status(fiber.StatusBadRequest).JSON(NewErrorResponse( "BAD_REQUEST", message, "", )) } // Unauthorized sends a 401 Unauthorized response. func Unauthorized(c fiber.Ctx, message string) error { return c.Status(fiber.StatusUnauthorized).JSON(NewErrorResponse( "UNAUTHORIZED", message, "", )) } // Forbidden sends a 403 Forbidden response. func Forbidden(c fiber.Ctx, message string) error { return c.Status(fiber.StatusForbidden).JSON(NewErrorResponse( "FORBIDDEN", message, "", )) } // NotFound sends a 404 Not Found response. func NotFound(c fiber.Ctx, message string) error { return c.Status(fiber.StatusNotFound).JSON(NewErrorResponse( "NOT_FOUND", message, "", )) } // Conflict sends a 409 Conflict response. func Conflict(c fiber.Ctx, message string) error { return c.Status(fiber.StatusConflict).JSON(NewErrorResponse( "CONFLICT", message, "", )) } // TooManyRequests sends a 429 Too Many Requests response. func TooManyRequests(c fiber.Ctx, message string, retryAfter int) error { c.Set("Retry-After", string(rune(retryAfter))) return c.Status(fiber.StatusTooManyRequests).JSON(NewErrorResponse( "RATE_LIMIT_EXCEEDED", message, "", )) } // InternalError sends a 500 Internal Server Error response. func InternalError(c fiber.Ctx, message string) error { return c.Status(fiber.StatusInternalServerError).JSON(NewErrorResponse( "INTERNAL", message, "", )) }