refactor: consolidate error handlers to use getServerErrorFromUnknown (#25114)
* refactor: consolidate error handlers to use getServerErrorFromUnknown - Migrate server-only code to use getServerErrorFromUnknown for better error handling - Add JSDoc documentation to both getErrorFromUnknown and getServerErrorFromUnknown - Update webhook handlers, payment services, email service, and booking service - Keep getErrorFromUnknown for client-side and isomorphic code - Improve error message extraction by using err.cause?.stack instead of err.stack - Fix ESLint warnings: replace 'any' with 'unknown' types, fix hasOwnProperty usage Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * Update packages/app-store/paypal/api/webhook.ts Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * refactor --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
benny@cal.com <sldisek783@gmail.com>
cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
parent
724204843a
commit
e6ba89961c
@@ -4,8 +4,8 @@ import type Stripe from "stripe";
|
||||
|
||||
import stripe from "@calcom/features/ee/payments/server/stripe";
|
||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import { HttpError as HttpCode } from "@calcom/lib/http-error";
|
||||
import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
|
||||
export const config = {
|
||||
@@ -108,13 +108,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
});
|
||||
}
|
||||
} catch (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
const err = getServerErrorFromUnknown(_err);
|
||||
if (!err.message.includes("No credential found with subscription ID")) {
|
||||
console.error(`Webhook Error: ${err.message}`);
|
||||
}
|
||||
res.status(err.statusCode ?? 500).send({
|
||||
res.status(err.statusCode).send({
|
||||
message: err.message,
|
||||
stack: IS_PRODUCTION ? undefined : err.stack,
|
||||
stack: IS_PRODUCTION ? undefined : err.cause?.stack,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import { handlePaymentSuccess } from "@calcom/app-store/_utils/payments/handlePa
|
||||
import { albyCredentialKeysSchema } from "@calcom/app-store/alby/lib";
|
||||
import parseInvoice from "@calcom/app-store/alby/lib/parseInvoice";
|
||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import { HttpError as HttpCode } from "@calcom/lib/http-error";
|
||||
import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
export const config = {
|
||||
@@ -90,11 +90,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
return await handlePaymentSuccess(payment.id, payment.bookingId);
|
||||
} catch (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
const err = getServerErrorFromUnknown(_err);
|
||||
console.error(`Webhook Error: ${err.message}`);
|
||||
return res.status(err.statusCode || 500).send({
|
||||
return res.status(err.statusCode).send({
|
||||
message: err.message,
|
||||
stack: IS_PRODUCTION ? undefined : err.stack,
|
||||
stack: IS_PRODUCTION ? undefined : err.cause?.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import { z } from "zod";
|
||||
|
||||
import { handlePaymentSuccess } from "@calcom/app-store/_utils/payments/handlePaymentSuccess";
|
||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import { HttpError as HttpCode } from "@calcom/lib/http-error";
|
||||
import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown";
|
||||
import { PrismaBookingPaymentRepository as BookingPaymentRepository } from "@calcom/lib/server/repository/PrismaBookingPaymentRepository";
|
||||
|
||||
import appConfig from "../config.json";
|
||||
@@ -90,11 +90,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
await handlePaymentSuccess(payment.id, payment.bookingId);
|
||||
return res.status(200).json({ success: true });
|
||||
} catch (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
const statusCode = err instanceof HttpCode ? err.statusCode : 500;
|
||||
return res.status(statusCode).send({
|
||||
const err = getServerErrorFromUnknown(_err);
|
||||
return res.status(err.statusCode).send({
|
||||
message: err.message,
|
||||
stack: IS_PRODUCTION ? undefined : err.stack,
|
||||
stack: IS_PRODUCTION ? undefined : err.cause?.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import type z from "zod";
|
||||
|
||||
import { handlePaymentSuccess } from "@calcom/app-store/_utils/payments/handlePaymentSuccess";
|
||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import { HttpError as HttpCode } from "@calcom/lib/http-error";
|
||||
import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import type { hitpayCredentialKeysSchema } from "../lib/hitpayCredentialKeysSchema";
|
||||
@@ -111,11 +111,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
return await handlePaymentSuccess(payment.id, payment.bookingId);
|
||||
} catch (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
const err = getServerErrorFromUnknown(_err);
|
||||
console.error(`Webhook Error: ${err.message}`);
|
||||
return res.status(200).send({
|
||||
message: err.message,
|
||||
stack: IS_PRODUCTION ? undefined : err.stack,
|
||||
stack: IS_PRODUCTION ? undefined : err.cause?.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import { handlePaymentSuccess } from "@calcom/app-store/_utils/payments/handlePa
|
||||
import { paypalCredentialKeysSchema } from "@calcom/app-store/paypal/lib";
|
||||
import Paypal from "@calcom/app-store/paypal/lib/Paypal";
|
||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import { HttpError as HttpCode } from "@calcom/lib/http-error";
|
||||
import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
export const config = {
|
||||
@@ -92,11 +92,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
return await handlePaypalPaymentSuccess(parsedPayload, bodyAsString, parseHeaders.data);
|
||||
}
|
||||
} catch (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
const err = getServerErrorFromUnknown(_err);
|
||||
console.error(`Webhook Error: ${err.message}`);
|
||||
res.status(200).send({
|
||||
message: err.message,
|
||||
stack: IS_PRODUCTION ? undefined : err.stack,
|
||||
stack: IS_PRODUCTION ? undefined : err.cause?.stack,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import z from "zod";
|
||||
import { sendAwaitingPaymentEmailAndSMS } from "@calcom/emails/email-manager";
|
||||
import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository";
|
||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import { ErrorWithCode } from "@calcom/lib/errors";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { Booking, Payment, PaymentOption, Prisma } from "@calcom/prisma/client";
|
||||
@@ -368,7 +368,7 @@ export class PaymentService implements IAbstractPaymentService {
|
||||
});
|
||||
return updatedPayment;
|
||||
} catch (e) {
|
||||
const err = getErrorFromUnknown(e);
|
||||
const err = getServerErrorFromUnknown(e);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import queue from "queue";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import { HttpError as HttpCode } from "@calcom/lib/http-error";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
@@ -100,7 +100,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event.data.hasOwnProperty(parameterFilter)) {
|
||||
if (!Object.prototype.hasOwnProperty.call(event.data, parameterFilter)) {
|
||||
res.status(500).json({ message: "Selected param not available" });
|
||||
return;
|
||||
}
|
||||
@@ -141,7 +141,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
);
|
||||
}
|
||||
await q.start();
|
||||
} catch (error) {
|
||||
} catch {
|
||||
throw new Error("Failed to reschedule bookings");
|
||||
}
|
||||
}
|
||||
@@ -155,11 +155,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
return res.status(200).json({ body: req.body });
|
||||
} catch (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
const err = getServerErrorFromUnknown(_err);
|
||||
console.error(`Webhook Error: ${err.message}`);
|
||||
res.status(err.statusCode ?? 500).send({
|
||||
res.status(err.statusCode).send({
|
||||
message: err.message,
|
||||
stack: IS_PRODUCTION ? undefined : err.stack,
|
||||
stack: IS_PRODUCTION ? undefined : err.cause?.stack,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import { z } from "zod";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import isSmsCalEmail from "@calcom/lib/isSmsCalEmail";
|
||||
import { serverConfig } from "@calcom/lib/serverConfig";
|
||||
import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown";
|
||||
import { setTestEmail } from "@calcom/lib/testEmails";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
|
||||
@@ -77,7 +77,7 @@ export default class BaseEmail {
|
||||
payloadWithUnEscapedSubject,
|
||||
(_err, info) => {
|
||||
if (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
const err = getServerErrorFromUnknown(_err);
|
||||
this.printNodeMailerError(err);
|
||||
reject(err);
|
||||
} else {
|
||||
|
||||
@@ -61,7 +61,7 @@ import { groupHostsByGroupId } from "@calcom/lib/bookings/hostGroupUtils";
|
||||
import { shouldIgnoreContactOwner } from "@calcom/lib/bookings/routing/utils";
|
||||
import { DEFAULT_GROUP_ID } from "@calcom/lib/constants";
|
||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
import { getErrorFromUnknown, ErrorWithCode } from "@calcom/lib/errors";
|
||||
import { ErrorWithCode } from "@calcom/lib/errors";
|
||||
import { extractBaseEmail } from "@calcom/lib/extract-base-email";
|
||||
import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId";
|
||||
import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType";
|
||||
@@ -69,6 +69,7 @@ import { HttpError } from "@calcom/lib/http-error";
|
||||
import { criticalLogger } from "@calcom/lib/logger.server";
|
||||
import { getPiiFreeCalendarEvent, getPiiFreeEventType } from "@calcom/lib/piiFreeData";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
|
||||
import { distributedTracing } from "@calcom/lib/tracing/factory";
|
||||
@@ -1844,9 +1845,9 @@ async function handler(
|
||||
};
|
||||
}
|
||||
} catch (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
const err = getServerErrorFromUnknown(_err);
|
||||
tracingLogger.error(`Booking ${eventTypeId} failed`, "Error when saving booking to db", err.message);
|
||||
if (err.code === "P2002") {
|
||||
if (err.cause && typeof err.cause === "object" && "code" in err.cause && err.cause.code === "P2002") {
|
||||
throw new HttpError({
|
||||
statusCode: 409,
|
||||
message: ErrorCode.BookingConflict,
|
||||
|
||||
@@ -14,10 +14,10 @@ import stripe from "@calcom/features/ee/payments/server/stripe";
|
||||
import { getPlatformParams } from "@calcom/features/platform-oauth-client/get-platform-params";
|
||||
import { PlatformOAuthClientRepository } from "@calcom/features/platform-oauth-client/platform-oauth-client.repository";
|
||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import { HttpError as HttpCode } from "@calcom/lib/http-error";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { getServerErrorFromUnknown } from "@calcom/lib/server/getServerErrorFromUnknown";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
@@ -180,11 +180,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
});
|
||||
}
|
||||
} catch (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
const err = getServerErrorFromUnknown(_err);
|
||||
console.error(`Webhook Error: ${err.message}`);
|
||||
res.status(err.statusCode ?? 500).send({
|
||||
res.status(err.statusCode).send({
|
||||
message: err.message,
|
||||
stack: IS_PRODUCTION ? undefined : err.stack,
|
||||
stack: IS_PRODUCTION ? undefined : err.cause?.stack,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
+19
-2
@@ -13,17 +13,34 @@ export class ErrorWithCode extends Error {
|
||||
get(_, prop: string) {
|
||||
if (prop in ErrorCode) {
|
||||
const code = ErrorCode[prop as keyof typeof ErrorCode];
|
||||
return (message?: string, data?: Record<string, any>) => new ErrorWithCode(code, message, data);
|
||||
return (message?: string, data?: Record<string, unknown>) => new ErrorWithCode(code, message, data);
|
||||
}
|
||||
throw new Error(`Unknown error code: ${prop}`);
|
||||
},
|
||||
}) as unknown as Record<
|
||||
keyof typeof ErrorCode,
|
||||
(message?: string, data?: Record<string, any>) => ErrorWithCode
|
||||
(message?: string, data?: Record<string, unknown>) => ErrorWithCode
|
||||
>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts unknown error types to Error objects.
|
||||
*
|
||||
* @deprecated For server-side code, use `getServerErrorFromUnknown` from `@calcom/lib/server/getServerErrorFromUnknown` instead.
|
||||
* This function should only be used in client-side or isomorphic code (React components, shared utilities).
|
||||
*
|
||||
* Use this function when:
|
||||
* - You're in a React component that runs on the client
|
||||
* - You're in shared/isomorphic code that cannot import server-only dependencies
|
||||
* - You only need a basic Error object without HTTP status code mapping
|
||||
*
|
||||
* For server-side error handling (API routes, tRPC handlers, webhooks), use `getServerErrorFromUnknown`
|
||||
* which provides proper HTTP status code mapping, error redaction, and handles Zod/Prisma/Stripe errors.
|
||||
*
|
||||
* @param cause - The unknown error to convert
|
||||
* @returns An Error object with optional statusCode and code properties
|
||||
*/
|
||||
export function getErrorFromUnknown(cause: unknown): Error & { statusCode?: number; code?: string } {
|
||||
if (cause instanceof Error) {
|
||||
return cause;
|
||||
|
||||
@@ -33,6 +33,12 @@ function parseZodErrorIssues(issues: ZodIssue[]): string {
|
||||
.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, tRPC handlers, webhooks, and server-side services.
|
||||
* For client-side code, use getErrorFromUnknown from @calcom/lib/errors instead.
|
||||
*/
|
||||
export function getServerErrorFromUnknown(cause: unknown): HttpError {
|
||||
if (isZodError(cause)) {
|
||||
return new HttpError({
|
||||
|
||||
Reference in New Issue
Block a user