Implement DomainHttpException for stable error handling and refactor exception filter

- Introduced `DomainHttpException` to standardize HTTP error responses with explicit domain error codes.
- Refactored `UnifiedExceptionFilter` to extract detailed exception information, including optional explicit error codes.
- Updated `WhmcsErrorHandlerService` to utilize `DomainHttpException` for better error normalization and handling.
- Removed deprecated error pattern matching logic to enforce explicit error codes in the system.
- Enhanced logging in `WhmcsHttpClientService` to differentiate between expected business outcomes and actual errors.
This commit is contained in:
barsa 2025-12-15 13:32:42 +09:00
parent c13a327a07
commit ad6fadb015
6 changed files with 150 additions and 248 deletions

View File

@ -0,0 +1,21 @@
import { HttpException } from "@nestjs/common";
import type { HttpStatus } from "@nestjs/common";
import { ErrorMessages, type ErrorCodeType } from "@customer-portal/domain/common";
/**
* DomainHttpException
*
* Use this when you want to throw an HTTP error with an explicit, stable domain error code.
* The global exception filter will read `code` from the exception response and format the
* standard API error response for the client.
*/
export class DomainHttpException extends HttpException {
constructor(
public readonly code: ErrorCodeType,
status: HttpStatus,
message?: string
) {
// `message` is optional; if omitted we fall back to the shared user-facing message.
super({ code, message: message ?? ErrorMessages[code] }, status);
}
}

View File

@ -14,7 +14,6 @@ import {
ErrorCode, ErrorCode,
ErrorMessages, ErrorMessages,
ErrorMetadata, ErrorMetadata,
matchErrorPattern,
type ErrorCodeType, type ErrorCodeType,
type ApiError, type ApiError,
} from "@customer-portal/domain/common"; } from "@customer-portal/domain/common";
@ -78,65 +77,92 @@ export class UnifiedExceptionFilter implements ExceptionFilter {
} { } {
let status = HttpStatus.INTERNAL_SERVER_ERROR; let status = HttpStatus.INTERNAL_SERVER_ERROR;
let originalMessage = "An unexpected error occurred"; let originalMessage = "An unexpected error occurred";
let explicitCode: ErrorCodeType | undefined;
if (exception instanceof HttpException) { if (exception instanceof HttpException) {
status = exception.getStatus(); status = exception.getStatus();
originalMessage = this.extractExceptionMessage(exception); const extracted = this.extractExceptionDetails(exception);
originalMessage = extracted.message;
explicitCode = extracted.code;
} else if (exception instanceof Error) { } else if (exception instanceof Error) {
originalMessage = exception.message; originalMessage = exception.message;
} }
// Map to error code // Map to error code
const errorCode = this.mapToErrorCode(originalMessage, status); const errorCode = explicitCode ?? this.mapStatusToErrorCode(status);
return { status, errorCode, originalMessage }; return { status, errorCode, originalMessage };
} }
/** /**
* Extract message from HttpException * Extract message (and optionally an explicit error code) from HttpException.
*/ */
private extractExceptionMessage(exception: HttpException): string { private extractExceptionDetails(exception: HttpException): {
message: string;
code?: ErrorCodeType;
} {
const response = exception.getResponse(); const response = exception.getResponse();
if (typeof response === "string") { if (typeof response === "string") {
return response; return { message: response };
} }
if (typeof response === "object" && response !== null) { if (typeof response === "object" && response !== null) {
const responseObj = response as Record<string, unknown>; const responseObj = response as Record<string, unknown>;
const code = this.extractExplicitCode(responseObj);
// Handle NestJS validation errors (array of messages) // Handle NestJS validation errors (array of messages)
if (Array.isArray(responseObj.message)) { if (Array.isArray(responseObj.message)) {
const firstMessage = responseObj.message.find((m): m is string => typeof m === "string"); const firstMessage = responseObj.message.find((m): m is string => typeof m === "string");
if (firstMessage) return firstMessage; if (firstMessage) return { message: firstMessage, code };
} }
// Handle standard message field // Handle standard message field
if (typeof responseObj.message === "string") { if (typeof responseObj.message === "string") {
return responseObj.message; return { message: responseObj.message, code };
} }
// Handle error field // Handle error field
if (typeof responseObj.error === "string") { if (typeof responseObj.error === "string") {
return responseObj.error; return { message: responseObj.error, code };
} }
return { message: exception.message, code };
} }
return exception.message; return { message: exception.message };
} }
/** /**
* Map error message and status to error code * Extract explicit error code from HttpException response bodies.
*/ */
private mapToErrorCode(message: string, status: number): ErrorCodeType { private extractExplicitCode(responseObj: Record<string, unknown>): ErrorCodeType | undefined {
// First, try pattern matching on the message // 1) Preferred: { code: "AUTH_003" }
const patternCode = matchErrorPattern(message); if (typeof responseObj.code === "string" && this.isKnownErrorCode(responseObj.code)) {
if (patternCode !== ErrorCode.UNKNOWN) { return responseObj.code as ErrorCodeType;
return patternCode;
} }
// Fall back to status code mapping // 2) Standard API error format: { error: { code: "AUTH_003" } }
// Cast status to HttpStatus to satisfy TypeScript enum comparison const maybeError = responseObj.error;
if (maybeError && typeof maybeError === "object") {
const errorObj = maybeError as Record<string, unknown>;
if (typeof errorObj.code === "string" && this.isKnownErrorCode(errorObj.code)) {
return errorObj.code as ErrorCodeType;
}
}
return undefined;
}
private isKnownErrorCode(value: string): boolean {
// ErrorMessages is keyed by ErrorCodeType
return Object.prototype.hasOwnProperty.call(ErrorMessages, value);
}
/**
* Map status code to error code (no message parsing).
*/
private mapStatusToErrorCode(status: number): ErrorCodeType {
switch (status as HttpStatus) { switch (status as HttpStatus) {
case HttpStatus.UNAUTHORIZED: case HttpStatus.UNAUTHORIZED:
return ErrorCode.SESSION_EXPIRED; return ErrorCode.SESSION_EXPIRED;

View File

@ -1,11 +1,11 @@
import { Injectable, HttpStatus } from "@nestjs/common";
import { import {
Injectable, ErrorCode,
NotFoundException, type ErrorCodeType,
BadRequestException, type WhmcsErrorResponse,
UnauthorizedException, } from "@customer-portal/domain/common";
} from "@nestjs/common"; import { DomainHttpException } from "@bff/core/http/domain-http.exception.js";
import { getErrorMessage } from "@bff/core/utils/error.util.js"; import { getErrorMessage } from "@bff/core/utils/error.util.js";
import type { WhmcsErrorResponse } from "@customer-portal/domain/common";
/** /**
* Service for handling and normalizing WHMCS API errors * Service for handling and normalizing WHMCS API errors
@ -24,69 +24,72 @@ export class WhmcsErrorHandlerService {
const message = errorResponse.message; const message = errorResponse.message;
const errorCode = errorResponse.errorcode; const errorCode = errorResponse.errorcode;
// Normalize common, expected error responses to domain exceptions const mapped = this.mapProviderErrorToDomain(action, message, errorCode, params);
if (this.isNotFoundError(action, message)) { throw new DomainHttpException(mapped.code, mapped.status);
throw this.createNotFoundException(action, message, params);
}
if (this.isAuthenticationError(message, errorCode)) {
throw new UnauthorizedException(`WHMCS Authentication Error: ${message}`);
}
if (this.isValidationError(message, errorCode)) {
throw new BadRequestException(`WHMCS Validation Error: ${message}`);
}
// Generic WHMCS API error
throw new Error(`WHMCS API Error [${action}]: ${message} (${errorCode || "unknown"})`);
} }
/** /**
* Handle general request errors (network, timeout, etc.) * Handle general request errors (network, timeout, etc.)
*/ */
handleRequestError(error: unknown, action: string, _params: Record<string, unknown>): never { handleRequestError(error: unknown, _action: string, _params: Record<string, unknown>): never {
const message = getErrorMessage(error);
if (this.isTimeoutError(error)) { if (this.isTimeoutError(error)) {
throw new Error(`WHMCS API timeout [${action}]: Request timed out`); throw new DomainHttpException(ErrorCode.TIMEOUT, HttpStatus.GATEWAY_TIMEOUT);
} }
if (this.isNetworkError(error)) { if (this.isNetworkError(error)) {
throw new Error(`WHMCS API network error [${action}]: ${message}`); throw new DomainHttpException(ErrorCode.NETWORK_ERROR, HttpStatus.BAD_GATEWAY);
} }
// Re-throw the original error if it's already a known exception type // If upstream already threw a DomainHttpException or HttpException with code,
if (this.isKnownException(error)) { // let the global exception filter handle it.
throw error; throw error;
} }
// Generic request error private mapProviderErrorToDomain(
throw new Error(`WHMCS API request failed [${action}]: ${message}`); action: string,
message: string,
providerErrorCode: string | undefined,
params: Record<string, unknown>
): { code: ErrorCodeType; status: HttpStatus } {
// 1) ValidateLogin: user credentials are wrong (expected)
if (
action === "ValidateLogin" &&
this.isValidateLoginInvalidCredentials(message, providerErrorCode)
) {
return { code: ErrorCode.INVALID_CREDENTIALS, status: HttpStatus.UNAUTHORIZED };
}
// 2) Not-found style outcomes (expected for some reads)
if (this.isNotFoundError(action, message)) {
return { code: ErrorCode.NOT_FOUND, status: HttpStatus.NOT_FOUND };
}
// 3) WHMCS API key auth failures: external service/config problem (not end-user auth)
if (this.isAuthenticationError(message, providerErrorCode)) {
return { code: ErrorCode.EXTERNAL_SERVICE_ERROR, status: HttpStatus.SERVICE_UNAVAILABLE };
}
// 4) Validation failures: treat as bad request
if (this.isValidationError(message, providerErrorCode)) {
return { code: ErrorCode.VALIDATION_FAILED, status: HttpStatus.BAD_REQUEST };
}
// 5) Default: external service error
void params; // reserved for future mapping detail; keep signature stable
return { code: ErrorCode.EXTERNAL_SERVICE_ERROR, status: HttpStatus.BAD_GATEWAY };
} }
/**
* Check if error indicates a not found condition
*/
private isNotFoundError(action: string, message: string): boolean { private isNotFoundError(action: string, message: string): boolean {
const lowerMessage = message.toLowerCase(); const lowerMessage = message.toLowerCase();
// Client not found errors if (action === "GetClientsDetails" && lowerMessage.includes("client not found")) return true;
if (action === "GetClientsDetails" && lowerMessage.includes("client not found")) {
return true;
}
// Invoice not found errors
if ( if (
(action === "GetInvoice" || action === "UpdateInvoice") && (action === "GetInvoice" || action === "UpdateInvoice") &&
lowerMessage.includes("invoice not found") lowerMessage.includes("invoice not found")
) { ) {
return true; return true;
} }
if (action === "GetClientsProducts" && lowerMessage.includes("no products found")) return true;
// Product not found errors
if (action === "GetClientsProducts" && lowerMessage.includes("no products found")) {
return true;
}
return false; return false;
} }
@ -123,40 +126,19 @@ export class WhmcsErrorHandlerService {
} }
/** /**
* Create appropriate NotFoundException based on action and params * ValidateLogin returns user-facing "invalid credentials" strings (e.g. "Email or Password Invalid").
* Treat these as authentication failures rather than external service errors.
*/ */
private createNotFoundException( private isValidateLoginInvalidCredentials(message: string, errorCode?: string): boolean {
action: string, const lowerMessage = message.toLowerCase();
message: string,
params: Record<string, unknown>
): NotFoundException {
if (action === "GetClientsDetails") {
const emailParam = params["email"];
if (typeof emailParam === "string") {
return new NotFoundException(`Client with email ${emailParam} not found`);
}
const clientIdParam = params["clientid"]; // WHMCS commonly responds with: "Email or Password Invalid"
const identifier = if (lowerMessage.includes("email or password invalid")) return true;
typeof clientIdParam === "string" || typeof clientIdParam === "number" if (lowerMessage.includes("password invalid")) return true;
? clientIdParam if (lowerMessage.includes("invalid login")) return true;
: "unknown"; if (errorCode === "EMAIL_OR_PASSWORD_INVALID") return true;
return new NotFoundException(`Client with ID ${identifier} not found`); return false;
}
if (action === "GetInvoice" || action === "UpdateInvoice") {
const invoiceIdParam = params["invoiceid"];
const identifier =
typeof invoiceIdParam === "string" || typeof invoiceIdParam === "number"
? invoiceIdParam
: "unknown";
return new NotFoundException(`Invoice with ID ${identifier} not found`);
}
// Generic not found error
return new NotFoundException(message);
} }
/** /**
@ -185,33 +167,10 @@ export class WhmcsErrorHandlerService {
); );
} }
/**
* Check if error is already a known NestJS exception
*/
private isKnownException(error: unknown): boolean {
return (
error instanceof NotFoundException ||
error instanceof BadRequestException ||
error instanceof UnauthorizedException
);
}
/** /**
* Get user-friendly error message for client consumption * Get user-friendly error message for client consumption
*/ */
getUserFriendlyMessage(error: unknown): string { getUserFriendlyMessage(error: unknown): string {
if (error instanceof NotFoundException) {
return "The requested resource was not found.";
}
if (error instanceof BadRequestException) {
return "The request contains invalid data.";
}
if (error instanceof UnauthorizedException) {
return "Authentication failed. Please check your credentials.";
}
const message = getErrorMessage(error).toLowerCase(); const message = getErrorMessage(error).toLowerCase();
if (message.includes("timeout")) { if (message.includes("timeout")) {

View File

@ -274,11 +274,13 @@ export class WhmcsHttpClientService {
const errorMessage = this.toDisplayString(message ?? error, "Unknown WHMCS API error"); const errorMessage = this.toDisplayString(message ?? error, "Unknown WHMCS API error");
const errorCode = this.toDisplayString(errorcode, "unknown"); const errorCode = this.toDisplayString(errorcode, "unknown");
this.logger.error(`WHMCS API returned error [${action}]`, { // Many WHMCS "result=error" responses are expected business outcomes (e.g. invalid credentials).
// Log as warning (not error) to avoid spamming error logs; the unified exception filter will
// still emit the request-level log based on the mapped error code.
this.logger.warn(`WHMCS API returned error [${action}]`, {
errorMessage, errorMessage,
errorCode, errorCode,
params: this.sanitizeLogParams(params), params: this.sanitizeLogParams(params),
fullResponse: parsedResponse,
}); });
// Return error response for the orchestrator to handle with proper exception types // Return error response for the orchestrator to handle with proper exception types

View File

@ -10,7 +10,6 @@ import {
ErrorCode, ErrorCode,
ErrorMessages, ErrorMessages,
ErrorMetadata, ErrorMetadata,
matchErrorPattern,
type ErrorCodeType, type ErrorCodeType,
} from "@customer-portal/domain/common"; } from "@customer-portal/domain/common";
@ -46,13 +45,11 @@ export function parseError(error: unknown): ParsedError {
// Handle string errors // Handle string errors
if (typeof error === "string") { if (typeof error === "string") {
const code = matchErrorPattern(error);
const metadata = ErrorMetadata[code];
return { return {
code, code: ErrorCode.UNKNOWN,
message: error, message: error,
shouldLogout: metadata.shouldLogout, shouldLogout: false,
shouldRetry: metadata.shouldRetry, shouldRetry: true,
}; };
} }
@ -77,11 +74,7 @@ function parseApiError(error: ClientApiError): ParsedError {
const bodyObj = body as Record<string, unknown>; const bodyObj = body as Record<string, unknown>;
// Check for standard { success: false, error: { code, message } } format // Check for standard { success: false, error: { code, message } } format
if ( if (bodyObj.success === false && bodyObj.error && typeof bodyObj.error === "object") {
bodyObj.success === false &&
bodyObj.error &&
typeof bodyObj.error === "object"
) {
const errorObj = bodyObj.error as Record<string, unknown>; const errorObj = bodyObj.error as Record<string, unknown>;
const code = typeof errorObj.code === "string" ? errorObj.code : undefined; const code = typeof errorObj.code === "string" ? errorObj.code : undefined;
const message = typeof errorObj.message === "string" ? errorObj.message : undefined; const message = typeof errorObj.message === "string" ? errorObj.message : undefined;
@ -97,18 +90,8 @@ function parseApiError(error: ClientApiError): ParsedError {
} }
} }
// Try extracting message from body // No message-based code inference. If the response doesn't include a structured error code,
const extractedMessage = extractMessageFromBody(body); // we fall back to status-based mapping below.
if (extractedMessage) {
const code = matchErrorPattern(extractedMessage);
const metadata = ErrorMetadata[code];
return {
code,
message: extractedMessage,
shouldLogout: metadata.shouldLogout,
shouldRetry: metadata.shouldRetry,
};
}
} }
// Fall back to status code mapping // Fall back to status code mapping
@ -146,46 +129,14 @@ function parseNativeError(error: Error): ParsedError {
}; };
} }
// Try pattern matching on error message
const code = matchErrorPattern(error.message);
const metadata = ErrorMetadata[code];
return { return {
code, code: ErrorCode.UNKNOWN,
message: code === ErrorCode.UNKNOWN ? error.message : ErrorMessages[code], message: error.message || ErrorMessages[ErrorCode.UNKNOWN],
shouldLogout: metadata.shouldLogout, shouldLogout: false,
shouldRetry: metadata.shouldRetry, shouldRetry: true,
}; };
} }
/**
* Extract error message from response body
*/
function extractMessageFromBody(body: unknown): string | null {
if (!body || typeof body !== "object") {
return null;
}
const bodyObj = body as Record<string, unknown>;
// Check nested error.message (standard format)
if (bodyObj.error && typeof bodyObj.error === "object") {
const errorObj = bodyObj.error as Record<string, unknown>;
if (typeof errorObj.message === "string") {
return errorObj.message;
}
}
// Check top-level message
if (typeof bodyObj.message === "string") {
return bodyObj.message;
}
return null;
}
/**
* Map HTTP status code to error code
*/
function mapStatusToErrorCode(status?: number): ErrorCodeType { function mapStatusToErrorCode(status?: number): ErrorCodeType {
if (!status) return ErrorCode.UNKNOWN; if (!status) return ErrorCode.UNKNOWN;
@ -249,6 +200,5 @@ export {
ErrorCode, ErrorCode,
ErrorMessages, ErrorMessages,
ErrorMetadata, ErrorMetadata,
matchErrorPattern,
type ErrorCodeType, type ErrorCodeType,
} from "@customer-portal/domain/common"; } from "@customer-portal/domain/common";

View File

@ -109,16 +109,19 @@ export const ErrorMessages: Record<ErrorCodeType, string> = {
[ErrorCode.CUSTOMER_NOT_FOUND]: "Customer account not found. Please contact support.", [ErrorCode.CUSTOMER_NOT_FOUND]: "Customer account not found. Please contact support.",
[ErrorCode.ORDER_ALREADY_PROCESSED]: "This order has already been processed.", [ErrorCode.ORDER_ALREADY_PROCESSED]: "This order has already been processed.",
[ErrorCode.INSUFFICIENT_BALANCE]: "Insufficient account balance.", [ErrorCode.INSUFFICIENT_BALANCE]: "Insufficient account balance.",
[ErrorCode.SERVICE_UNAVAILABLE]: "This service is temporarily unavailable. Please try again later.", [ErrorCode.SERVICE_UNAVAILABLE]:
"This service is temporarily unavailable. Please try again later.",
// System // System
[ErrorCode.INTERNAL_ERROR]: "An unexpected error occurred. Please try again later.", [ErrorCode.INTERNAL_ERROR]: "An unexpected error occurred. Please try again later.",
[ErrorCode.EXTERNAL_SERVICE_ERROR]: "An external service is temporarily unavailable. Please try again later.", [ErrorCode.EXTERNAL_SERVICE_ERROR]:
"An external service is temporarily unavailable. Please try again later.",
[ErrorCode.DATABASE_ERROR]: "A system error occurred. Please try again later.", [ErrorCode.DATABASE_ERROR]: "A system error occurred. Please try again later.",
[ErrorCode.CONFIGURATION_ERROR]: "A system configuration error occurred. Please contact support.", [ErrorCode.CONFIGURATION_ERROR]: "A system configuration error occurred. Please contact support.",
// Network // Network
[ErrorCode.NETWORK_ERROR]: "Unable to connect to the server. Please check your internet connection.", [ErrorCode.NETWORK_ERROR]:
"Unable to connect to the server. Please check your internet connection.",
[ErrorCode.TIMEOUT]: "The request timed out. Please try again.", [ErrorCode.TIMEOUT]: "The request timed out. Please try again.",
[ErrorCode.RATE_LIMITED]: "Too many requests. Please wait a moment and try again.", [ErrorCode.RATE_LIMITED]: "Too many requests. Please wait a moment and try again.",
@ -162,7 +165,9 @@ export const ErrorMetadata: Record<ErrorCodeType, ErrorMetadata> = {
severity: "low", severity: "low",
shouldLogout: true, shouldLogout: true,
shouldRetry: false, shouldRetry: false,
logLevel: "info", // Session expiry is an expected flow (browser tabs, refresh loops, etc.) and can be extremely noisy.
// Keep it available for debugging but avoid spamming production logs at info level.
logLevel: "debug",
}, },
[ErrorCode.TOKEN_INVALID]: { [ErrorCode.TOKEN_INVALID]: {
category: "authentication", category: "authentication",
@ -378,68 +383,8 @@ export function canRetryError(code: string): boolean {
return getErrorMetadata(code).shouldRetry; return getErrorMetadata(code).shouldRetry;
} }
// ============================================================================ // NOTE: We intentionally do NOT support matching error messages to codes.
// Pattern Matching for Error Classification // Error codes must be explicit and stable (returned from the API or thrown by server code).
// ============================================================================
interface ErrorPattern {
pattern: RegExp;
code: ErrorCodeType;
}
/**
* Patterns to match error messages to error codes.
* Used when explicit error codes are not available.
*/
export const ErrorPatterns: ErrorPattern[] = [
// Authentication patterns
{ pattern: /invalid.*credentials?|wrong.*password|invalid.*password/i, code: ErrorCode.INVALID_CREDENTIALS },
{ pattern: /account.*locked|locked.*account|too.*many.*attempts/i, code: ErrorCode.ACCOUNT_LOCKED },
{ pattern: /session.*expired|expired.*session/i, code: ErrorCode.SESSION_EXPIRED },
{ pattern: /token.*expired|expired.*token/i, code: ErrorCode.SESSION_EXPIRED },
{ pattern: /token.*revoked|revoked.*token/i, code: ErrorCode.TOKEN_REVOKED },
{ pattern: /invalid.*token|token.*invalid/i, code: ErrorCode.TOKEN_INVALID },
{ pattern: /refresh.*token.*invalid|invalid.*refresh/i, code: ErrorCode.REFRESH_TOKEN_INVALID },
// Authorization patterns
{ pattern: /admin.*required|requires?.*admin/i, code: ErrorCode.ADMIN_REQUIRED },
{ pattern: /forbidden|not.*authorized|unauthorized/i, code: ErrorCode.FORBIDDEN },
{ pattern: /access.*denied|permission.*denied/i, code: ErrorCode.RESOURCE_ACCESS_DENIED },
// Business patterns
{ pattern: /already.*exists|email.*exists|account.*exists/i, code: ErrorCode.ACCOUNT_EXISTS },
{ pattern: /already.*linked/i, code: ErrorCode.ACCOUNT_ALREADY_LINKED },
{ pattern: /customer.*not.*found|account.*not.*found/i, code: ErrorCode.CUSTOMER_NOT_FOUND },
{ pattern: /already.*processed/i, code: ErrorCode.ORDER_ALREADY_PROCESSED },
{ pattern: /insufficient.*balance/i, code: ErrorCode.INSUFFICIENT_BALANCE },
// System patterns
{ pattern: /database|sql|postgres|prisma|connection.*refused/i, code: ErrorCode.DATABASE_ERROR },
{ pattern: /whmcs|salesforce|external.*service/i, code: ErrorCode.EXTERNAL_SERVICE_ERROR },
{ pattern: /configuration.*error|missing.*config/i, code: ErrorCode.CONFIGURATION_ERROR },
// Network patterns
{ pattern: /network.*error|fetch.*failed|econnrefused/i, code: ErrorCode.NETWORK_ERROR },
{ pattern: /timeout|timed?\s*out/i, code: ErrorCode.TIMEOUT },
{ pattern: /too.*many.*requests|rate.*limit/i, code: ErrorCode.RATE_LIMITED },
// Validation patterns (lower priority - checked last)
{ pattern: /not.*found/i, code: ErrorCode.NOT_FOUND },
{ pattern: /validation.*failed|invalid/i, code: ErrorCode.VALIDATION_FAILED },
{ pattern: /required|missing/i, code: ErrorCode.REQUIRED_FIELD_MISSING },
];
/**
* Match an error message to an error code using patterns
*/
export function matchErrorPattern(message: string): ErrorCodeType {
for (const { pattern, code } of ErrorPatterns) {
if (pattern.test(message)) {
return code;
}
}
return ErrorCode.UNKNOWN;
}
// ============================================================================ // ============================================================================
// Zod Schema for Error Response // Zod Schema for Error Response
@ -476,4 +421,3 @@ export function createErrorResponse(
}, },
}; };
} }