Finished Task 8: Infra Utils

master
oabrivard 1 month ago
parent 1c404d6474
commit 474ebc0801

@ -2,6 +2,9 @@
package httputil package httputil
import ( import (
stderrors "errors"
"strconv"
"github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3"
"knowfoolery/backend/shared/domain/errors" "knowfoolery/backend/shared/domain/errors"
@ -41,7 +44,8 @@ func MapError(err error) (int, ErrorResponse) {
) )
} }
domainErr, ok := err.(*errors.DomainError) var domainErr *errors.DomainError
ok := stderrors.As(err, &domainErr)
if !ok { if !ok {
return fiber.StatusInternalServerError, NewErrorResponse( return fiber.StatusInternalServerError, NewErrorResponse(
"INTERNAL", "INTERNAL",
@ -131,7 +135,7 @@ func Conflict(c fiber.Ctx, message string) error {
// TooManyRequests sends a 429 Too Many Requests response. // TooManyRequests sends a 429 Too Many Requests response.
func TooManyRequests(c fiber.Ctx, message string, retryAfter int) error { func TooManyRequests(c fiber.Ctx, message string, retryAfter int) error {
c.Set("Retry-After", string(rune(retryAfter))) c.Set("Retry-After", strconv.Itoa(retryAfter))
return c.Status(fiber.StatusTooManyRequests).JSON(NewErrorResponse( return c.Status(fiber.StatusTooManyRequests).JSON(NewErrorResponse(
"RATE_LIMIT_EXCEEDED", "RATE_LIMIT_EXCEEDED",
message, message,

@ -5,6 +5,7 @@ package httputil
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
@ -116,3 +117,28 @@ func TestSendError(t *testing.T) {
require.NoError(t, json.NewDecoder(resp.Body).Decode(&body)) require.NoError(t, json.NewDecoder(resp.Body).Decode(&body))
require.Equal(t, "NOT_FOUND", body.Code) require.Equal(t, "NOT_FOUND", body.Code)
} }
// TestMapError_WrappedDomainError verifies wrapped domain errors are recognized.
func TestMapError_WrappedDomainError(t *testing.T) {
wrapped := fmt.Errorf("outer: %w", errorspkg.New(errorspkg.CodeValidationFailed, "bad input"))
status, resp := MapError(wrapped)
require.Equal(t, http.StatusBadRequest, status)
require.Equal(t, errorspkg.CodeValidationFailed.String(), resp.Code)
require.Equal(t, "bad input", resp.Message)
}
// TestTooManyRequestsRetryAfterHeader verifies Retry-After is set as a decimal string.
func TestTooManyRequestsRetryAfterHeader(t *testing.T) {
app := fiber.New()
app.Get("/", func(c fiber.Ctx) error {
return TooManyRequests(c, "slow down", 120)
})
req := httptest.NewRequest(http.MethodGet, "/", nil)
resp, err := app.Test(req)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusTooManyRequests, resp.StatusCode)
require.Equal(t, "120", resp.Header.Get("Retry-After"))
}

@ -82,7 +82,21 @@ func (v *Validator) ValidateVar(field interface{}, tag string) error {
return nil return nil
} }
return errors.Wrap(errors.CodeValidationFailed, "validation failed", err) validationErrors, ok := err.(validator.ValidationErrors)
if !ok {
return errors.Wrap(errors.CodeValidationFailed, "validation failed", err)
}
messages := make([]string, 0, len(validationErrors))
for _, e := range validationErrors {
messages = append(messages, formatValidationError(e))
}
return errors.Wrap(
errors.CodeValidationFailed,
strings.Join(messages, "; "),
nil,
)
} }
// formatValidationError formats a single validation error into a readable message. // formatValidationError formats a single validation error into a readable message.

@ -50,6 +50,7 @@ func TestValidator_ValidateVarReturnsDomainError(t *testing.T) {
var domainErr *errs.DomainError var domainErr *errs.DomainError
require.ErrorAs(t, err, &domainErr) require.ErrorAs(t, err, &domainErr)
require.Equal(t, errs.CodeValidationFailed, domainErr.Code) require.Equal(t, errs.CodeValidationFailed, domainErr.Code)
require.Contains(t, domainErr.Message, "must be a valid email")
} }
// TestValidator_JSONTagNameMapping ensures JSON tag names appear in validation error text. // TestValidator_JSONTagNameMapping ensures JSON tag names appear in validation error text.

Loading…
Cancel
Save