// @ts-nocheck // We can remove this when we update the prisma client and the typescript version // if we don't add this we get build errors with prisma due to type-nesting import { IS_PRODUCTION, SENTRY_DSN } from "@/lib/constants"; import { responses } from "@/modules/api/v2/lib/response"; import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error"; import * as Sentry from "@sentry/nextjs"; import { ZodCustomIssue, ZodIssue } from "zod"; import { logger } from "@formbricks/logger"; export const handleApiError = (request: Request, err: ApiErrorResponseV2): Response => { logApiError(request, err); switch (err.type) { case "bad_request": return responses.badRequestResponse({ details: err.details }); case "unauthorized": return responses.unauthorizedResponse(); case "forbidden": return responses.forbiddenResponse(); case "not_found": return responses.notFoundResponse({ details: err.details }); case "conflict": return responses.conflictResponse({ details: err.details }); case "unprocessable_entity": return responses.unprocessableEntityResponse({ details: err.details }); case "too_many_requests": return responses.tooManyRequestsResponse(); default: // Replace with a generic error message, because we don't want to expose internal errors to API users. return responses.internalServerErrorResponse({ details: [ { field: "error", issue: "An error occurred while processing your request. Please try again later.", }, ], }); } }; export const formatZodError = (error: { issues: (ZodIssue | ZodCustomIssue)[] }) => { return error.issues.map((issue) => { const issueParams = issue.code === "custom" ? issue.params : undefined; return { field: issue.path.join("."), issue: issue.message ?? "An error occurred while processing your request. Please try again later.", ...(issueParams && { meta: issueParams }), }; }); }; export const logApiRequest = (request: Request, responseStatus: number): void => { const method = request.method; const url = new URL(request.url); const path = url.pathname; const correlationId = request.headers.get("x-request-id") || ""; const startTime = request.headers.get("x-start-time") || ""; const queryParams = Object.fromEntries(url.searchParams.entries()); const sensitiveParams = ["apikey", "token", "secret"]; const safeQueryParams = Object.fromEntries( Object.entries(queryParams).filter(([key]) => !sensitiveParams.includes(key.toLowerCase())) ); logger .withContext({ method, path, responseStatus, duration: `${Date.now() - parseInt(startTime)} ms`, correlationId, queryParams: safeQueryParams, }) .info("API Request Details"); }; export const logApiError = (request: Request, error: ApiErrorResponseV2): void => { const correlationId = request.headers.get("x-request-id") ?? ""; // Send the error to Sentry if the DSN is set and the error type is internal_server_error // This is useful for tracking down issues without overloading Sentry with errors if (SENTRY_DSN && IS_PRODUCTION && error.type === "internal_server_error") { const err = new Error(`API V2 error, id: ${correlationId}`); Sentry.captureException(err, { extra: { details: error.details, type: error.type, correlationId, }, }); } logger .withContext({ correlationId, error, }) .error("API Error Details"); };