You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

148 lines
3.9 KiB
Go

// 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,
"",
))
}