94 lines
3.2 KiB
Go
94 lines
3.2 KiB
Go
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...)
|
|
}
|