perf: Do not import Stripe lib in getServerErrorFromUnknown (#19578)

* Do not import stripe lib in getServerErrorFromUnknown

* address comment

* remove comment

* add url

* keep the location

* remove space

* add unit test for getServerErrorFromUnknown

* improve
This commit is contained in:
Benny Joo
2025-02-27 15:22:23 +00:00
committed by GitHub
parent de642e32c5
commit 4d45aa1564
3 changed files with 101 additions and 3 deletions
+63
View File
@@ -0,0 +1,63 @@
import { z } from "zod";
// Copied from node_modules/stripe/types/Errors.d.ts because stripe lib doesn't expose these types
// Ref: https://github.com/stripe/stripe-node/blob/master/types/Errors.d.ts#L194
interface StripeInvalidRequestError extends Error {
readonly type: "StripeInvalidRequestError";
readonly rawType: "invalid_request_error";
/**
* A human-readable message giving more details about the error. For card errors, these messages can
* be shown to your users.
*/
readonly message: string;
/**
* For card errors, a short string describing the kind of card error that occurred.
*
* @docs https://stripe.com/docs/error-codes
*/
readonly code?: string;
/**
* A URL to more information about the error code reported.
*
* @docs https://stripe.com/docs/error-codes
*/
readonly doc_url?: string;
/**
* Typically a 4xx or 5xx.
*/
readonly statusCode?: number;
readonly raw: unknown;
readonly headers: {
[key: string]: string;
};
readonly requestId: string;
/**
* The parameter the error relates to if the error is parameter-specific. You can use this to display a
* message near the correct form field, for example.
*/
readonly param?: string;
}
const errorSchema = z.object({
name: z.string(),
message: z.string(),
stack: z.string().optional(),
});
export const stripeInvalidRequestErrorSchema = errorSchema.extend({
type: z.literal("StripeInvalidRequestError"),
rawType: z.literal("invalid_request_error"),
code: z.string().optional(),
doc_url: z.string().optional(),
statusCode: z.number().optional(),
raw: z.unknown(),
headers: z.record(z.string()),
requestId: z.string(),
param: z.string().optional(),
});
@@ -0,0 +1,34 @@
import { expect, it, describe } from "vitest";
import { HttpError } from "../http-error";
import { getServerErrorFromUnknown } from "./getServerErrorFromUnknown";
describe("getServerErrorFromUnknown", () => {
it("should handle a StripeInvalidRequestError", () => {
const stripeError = {
name: "StripeInvalidRequestError",
type: "StripeInvalidRequestError",
rawType: "invalid_request_error",
message: "No such customer: cus_12345; a valid customer ID is required.",
code: "resource_missing",
doc_url: "https://stripe.com/docs/error-codes/resource-missing",
statusCode: 400,
raw: {
error: { type: "invalid_request_error", message: "No such customer" },
},
headers: {
"content-type": "application/json",
"request-id": "req_abc123xyz",
},
requestId: "req_abc123xyz",
param: "customer",
};
Object.setPrototypeOf(stripeError, Error.prototype);
const result = getServerErrorFromUnknown(stripeError);
expect(result).toBeInstanceOf(HttpError);
expect(result.statusCode).toBe(400);
expect(result.message).toBe(stripeError.message);
expect(result.cause).toEqual(stripeError);
expect(result.name).toBe("HttpError");
});
});
@@ -1,8 +1,8 @@
import { Prisma } from "@prisma/client";
import Stripe from "stripe";
import type { ZodIssue } from "zod";
import { ZodError } from "zod";
import { stripeInvalidRequestErrorSchema } from "@calcom/app-store/_utils/stripe.types";
import { ErrorCode } from "@calcom/lib/errorCodes";
import { TRPCError } from "@trpc/server";
@@ -56,8 +56,9 @@ export function getServerErrorFromUnknown(cause: unknown): HttpError {
if (isPrismaError(cause)) {
return getServerErrorFromPrismaError(cause);
}
if (cause instanceof Stripe.errors.StripeInvalidRequestError) {
return getHttpError({ statusCode: 400, cause });
const parsedStripeError = stripeInvalidRequestErrorSchema.safeParse(cause);
if (parsedStripeError.success) {
return getHttpError({ statusCode: 400, cause: parsedStripeError.data });
}
if (cause instanceof HttpError) {
const redactedCause = redactError(cause);