import type { ZodIssue } from "zod"; import { ZodError } from "zod"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { ErrorWithCode } from "@calcom/lib/errors"; import { Prisma } from "@calcom/prisma/client"; import { HttpError } from "../http-error"; import { redactError } from "../redactError"; import { stripeInvalidRequestErrorSchema } from "../stripe-error"; import { TracedError } from "../tracing/error"; function hasName(cause: unknown): cause is { name: string } { return !!cause && typeof cause === "object" && "name" in cause; } function isZodError(cause: unknown): cause is ZodError { return cause instanceof ZodError || (hasName(cause) && cause.name === "ZodError"); } // Fallback to code check when instanceof fails due to different Prisma client instances function isPrismaError(cause: unknown): cause is Prisma.PrismaClientKnownRequestError { return ( cause instanceof Prisma.PrismaClientKnownRequestError || (cause instanceof Error && "code" in cause && typeof cause.code === "string" && cause.code.startsWith("P")) ); } function parseZodErrorIssues(issues: ZodIssue[]): string { return issues .map((i) => i.code === "invalid_union" ? i.unionErrors.map((ue) => parseZodErrorIssues(ue.issues)).join("; ") : i.code === "unrecognized_keys" ? i.message : `${i.path.length ? `${i.code} in '${i.path}': ` : ""}${i.message}` ) .join("; "); } /** * Converts unknown error types to HttpError with proper status code mapping and error redaction. * SERVER-ONLY: This function imports Prisma and Stripe schemas and should only be used in server-side code. * Use in API routes, webhooks, and server-side services. * For client-side code, use getErrorFromUnknown from @calcom/lib/errors instead. * * NOTE: This function does NOT handle TRPCError. Callers that need to handle TRPCError should do so * explicitly before calling this function (see onErrorHandler.ts and defaultResponder.ts for examples). */ export function getServerErrorFromUnknown(cause: unknown): HttpError { let traceId: string | undefined; let tracedData: Record | undefined; if (cause instanceof TracedError) { traceId = cause.traceId; tracedData = cause.data; cause = cause.originalError; } if (isZodError(cause)) { return new HttpError({ statusCode: 400, message: parseZodErrorIssues(cause.issues), cause, data: traceId ? { ...tracedData, traceId } : undefined, }); } if (cause instanceof SyntaxError) { return new HttpError({ statusCode: 500, message: "Unexpected error, please reach out for our customer support.", cause, data: traceId ? { ...tracedData, traceId } : undefined, }); } if (isPrismaError(cause)) { return getServerErrorFromPrismaError(cause, traceId, tracedData); } const parsedStripeError = stripeInvalidRequestErrorSchema.safeParse(cause); if (parsedStripeError.success) { const stripeErrorObj = new Error(parsedStripeError.data.message || "Stripe error"); stripeErrorObj.name = parsedStripeError.data.type || "StripeInvalidRequestError"; return getHttpError({ statusCode: 400, cause: stripeErrorObj, traceId, tracedData }); } if (cause instanceof ErrorWithCode) { const statusCode = getHttpStatusCode(cause); return new HttpError({ statusCode, message: cause.message ?? "", data: traceId ? { ...cause.data, ...tracedData, traceId } : cause.data, cause, }); } if (cause instanceof HttpError) { const originalData = cause.data; return new HttpError({ statusCode: cause.statusCode, message: cause.message ?? "", cause: cause.cause, url: cause.url, method: cause.method, data: traceId ? { ...originalData, ...tracedData, traceId } : originalData, }); } if (cause instanceof Error) { const statusCode = getHttpStatusCode(cause); return getHttpError({ statusCode, cause, traceId, tracedData }); } if (typeof cause === "string") { return new HttpError({ statusCode: 500, message: cause, data: traceId ? { ...tracedData, traceId } : undefined, }); } return new HttpError({ statusCode: 500, message: `Unhandled error of type '${typeof cause}'. Please reach out for our customer support.`, data: traceId ? { ...tracedData, traceId } : tracedData, }); } export function getHttpStatusCode(cause: Error | ErrorWithCode): number { const errorCode = cause instanceof ErrorWithCode ? cause.code : cause.message; switch (errorCode) { // Generic HTTP error codes case ErrorCode.BadRequest: return 400; case ErrorCode.Unauthorized: return 401; case ErrorCode.Forbidden: return 403; case ErrorCode.NotFound: return 404; case ErrorCode.InternalServerError: return 500; // Domain-specific error codes // 400 Bad Request case ErrorCode.RequestBodyWithouEnd: case ErrorCode.MissingPaymentCredential: case ErrorCode.MissingPaymentAppId: case ErrorCode.AvailabilityNotFoundInSchedule: case ErrorCode.CancelledBookingsCannotBeRescheduled: case ErrorCode.BookingTimeOutOfBounds: case ErrorCode.BookingNotAllowedByRestrictionSchedule: case ErrorCode.BookerLimitExceeded: case ErrorCode.BookerLimitExceededReschedule: case ErrorCode.EventTypeNoHosts: case ErrorCode.RequestBodyInvalid: case ErrorCode.ChargeCardFailure: case ErrorCode.CollectCardFailure: return 400; // 409 Conflict case ErrorCode.NoAvailableUsersFound: case ErrorCode.FixedHostsUnavailableForBooking: case ErrorCode.RoundRobinHostsUnavailableForBooking: case ErrorCode.AlreadySignedUpForBooking: case ErrorCode.BookingSeatsFull: case ErrorCode.NotEnoughAvailableSeats: case ErrorCode.BookingConflict: case ErrorCode.PaymentCreationFailure: return 409; // 404 Not Found case ErrorCode.EventTypeNotFound: case ErrorCode.BookingNotFound: case ErrorCode.RestrictionScheduleNotFound: return 404; case ErrorCode.UnableToSubscribeToThePlatform: case ErrorCode.UpdatingOauthClientError: case ErrorCode.CreatingOauthClientError: default: return 500; } } function getHttpError({ statusCode, cause, traceId, tracedData, }: { statusCode: number; cause: T; traceId?: string; tracedData?: Record; }) { const redacted = redactError(cause); return new HttpError({ statusCode, message: redacted.message, cause: redacted, data: traceId ? { ...tracedData, traceId } : undefined, }); } function getServerErrorFromPrismaError( cause: Prisma.PrismaClientKnownRequestError, traceId?: string, tracedData?: Record ) { if (cause.code === "P2025") { return getHttpError({ statusCode: 404, cause, traceId, tracedData }); } return getHttpError({ statusCode: 400, cause, traceId, tracedData }); } export { isPrismaError };