package handlers import ( "encoding/json" "errors" "fmt" "log/slog" "strings" "github.com/gofiber/fiber/v2" "wucher/internal/shared/pkg/apperrorsx" "wucher/internal/transport/http/jsonapi" ) // bodyParseError maps a request-body JSON parse error to a precise app error. It returns // a non-nil *AppError only for a data-type mismatch (INVALID_DATA_TYPE, pointing at the // offending field); for any other parse error it returns nil so the caller can fall back // to its module-specific invalid-json error. func bodyParseError(err error) *apperrorsx.AppError { var typeErr *json.UnmarshalTypeError if !errors.As(err, &typeErr) { return nil } appErr := apperrorsx.New(apperrorsx.ErrInvalidDataType).WithCause(err) if typeErr.Field != "" { appErr.Source = &jsonapi.ErrorSource{Pointer: "/" + strings.ReplaceAll(typeErr.Field, ".", "/")} appErr.Message = fmt.Sprintf("field %q expects type %s", typeErr.Field, typeErr.Type.String()) } else { appErr.Message = fmt.Sprintf("expected type %s", typeErr.Type.String()) } return appErr } // writeBodyParseError writes a precise INVALID_DATA_TYPE error for a data-type mismatch, // or falls back to the given module invalid-json def for any other parse error. func writeBodyParseError(c *fiber.Ctx, err error, fallback apperrorsx.Def) error { if appErr := bodyParseError(err); appErr != nil { return apperrorsx.Write(c, appErr) } return apperrorsx.Write(c, apperrorsx.New(fallback).WithCause(err)) } func writeInternalHandlerError(c *fiber.Ctx, operation string, err error) error { return writeInternalHandlerErrorWithStatus(c, fiber.StatusInternalServerError, operation, err) } func writeHandlerErrorDef(c *fiber.Ctx, operation string, def apperrorsx.Def, err error) error { logHandlerError(c, operation, def.Status, def.Code, def.ErrorCode, err) appErr := apperrorsx.New(def) if err != nil { appErr.Cause = err } return apperrorsx.Write(c, appErr) } func writeHandlerErrorDefWithStatus(c *fiber.Ctx, operation string, status int, def apperrorsx.Def, err error) error { logHandlerError(c, operation, status, def.Code, def.ErrorCode, err) appErr := apperrorsx.New(def) appErr.Status = status if err != nil { appErr.Cause = err } return apperrorsx.Write(c, appErr) } func writeInternalHandlerErrorWithStatus(c *fiber.Ctx, status int, operation string, err error) error { logHandlerError(c, operation, status, apperrorsx.CodeInternalServerError, apperrorsx.ErrInternal.ErrorCode, err) appErr := apperrorsx.New(apperrorsx.ErrInternal) appErr.Status = status return apperrorsx.Write(c, appErr) } func safeInternalDetail() string { return apperrorsx.ErrInternal.Message } func logHandlerError(c *fiber.Ctx, operation string, status int, code, errorCode string, err error) { args := []any{ slog.String("request_id", requestIDFromCtx(c)), slog.String("operation", strings.TrimSpace(operation)), slog.Int("status", status), slog.String("code", strings.TrimSpace(code)), slog.String("error_code", strings.TrimSpace(errorCode)), slog.String("path", c.Path()), slog.Any("cause", err), } if status >= fiber.StatusInternalServerError { slog.ErrorContext(c.UserContext(), "handler error", args...) return } slog.WarnContext(c.UserContext(), "handler error", args...) }