- Replaced multiple global exception filters with a unified exception filter to streamline error handling across the application. - Removed deprecated AuthErrorFilter and GlobalExceptionFilter to reduce redundancy. - Enhanced SupportController to include new endpoints for listing, retrieving, and creating support cases, improving the support case management functionality. - Integrated SalesforceCaseService for better interaction with Salesforce data in support case operations. - Updated support case schemas to align with new requirements and ensure data consistency.
255 lines
6.6 KiB
TypeScript
255 lines
6.6 KiB
TypeScript
/**
|
|
* Unified Error Handling for Portal
|
|
*
|
|
* Clean, simple error handling that uses shared error codes from domain package.
|
|
* Provides consistent error parsing and user-friendly messages.
|
|
*/
|
|
|
|
import { ApiError as ClientApiError, isApiError } from "@/lib/api";
|
|
import {
|
|
ErrorCode,
|
|
ErrorMessages,
|
|
ErrorMetadata,
|
|
matchErrorPattern,
|
|
type ErrorCodeType,
|
|
} from "@customer-portal/domain/common";
|
|
|
|
// ============================================================================
|
|
// Types
|
|
// ============================================================================
|
|
|
|
export interface ParsedError {
|
|
code: ErrorCodeType;
|
|
message: string;
|
|
shouldLogout: boolean;
|
|
shouldRetry: boolean;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Error Parsing
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Parse any error into a structured format with code and user-friendly message.
|
|
* This is the main entry point for error handling.
|
|
*/
|
|
export function parseError(error: unknown): ParsedError {
|
|
// Handle API client errors
|
|
if (isApiError(error)) {
|
|
return parseApiError(error);
|
|
}
|
|
|
|
// Handle network/fetch errors
|
|
if (error instanceof Error) {
|
|
return parseNativeError(error);
|
|
}
|
|
|
|
// Handle string errors
|
|
if (typeof error === "string") {
|
|
const code = matchErrorPattern(error);
|
|
const metadata = ErrorMetadata[code];
|
|
return {
|
|
code,
|
|
message: error,
|
|
shouldLogout: metadata.shouldLogout,
|
|
shouldRetry: metadata.shouldRetry,
|
|
};
|
|
}
|
|
|
|
// Unknown error type
|
|
return {
|
|
code: ErrorCode.UNKNOWN,
|
|
message: ErrorMessages[ErrorCode.UNKNOWN],
|
|
shouldLogout: false,
|
|
shouldRetry: true,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Parse API client error
|
|
*/
|
|
function parseApiError(error: ClientApiError): ParsedError {
|
|
const body = error.body;
|
|
const status = error.response?.status;
|
|
|
|
// Try to extract from standard API error response format
|
|
if (body && typeof body === "object") {
|
|
const bodyObj = body as Record<string, unknown>;
|
|
|
|
// Check for standard { success: false, error: { code, message } } format
|
|
if (
|
|
bodyObj.success === false &&
|
|
bodyObj.error &&
|
|
typeof bodyObj.error === "object"
|
|
) {
|
|
const errorObj = bodyObj.error as Record<string, unknown>;
|
|
const code = typeof errorObj.code === "string" ? errorObj.code : undefined;
|
|
const message = typeof errorObj.message === "string" ? errorObj.message : undefined;
|
|
|
|
if (code && message) {
|
|
const metadata = ErrorMetadata[code as ErrorCodeType] ?? ErrorMetadata[ErrorCode.UNKNOWN];
|
|
return {
|
|
code: code as ErrorCodeType,
|
|
message,
|
|
shouldLogout: metadata.shouldLogout,
|
|
shouldRetry: metadata.shouldRetry,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Try extracting message from body
|
|
const extractedMessage = extractMessageFromBody(body);
|
|
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
|
|
const code = mapStatusToErrorCode(status);
|
|
const metadata = ErrorMetadata[code];
|
|
return {
|
|
code,
|
|
message: error.message || ErrorMessages[code],
|
|
shouldLogout: metadata.shouldLogout,
|
|
shouldRetry: metadata.shouldRetry,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Parse native JavaScript errors (network, timeout, etc.)
|
|
*/
|
|
function parseNativeError(error: Error): ParsedError {
|
|
// Network errors
|
|
if (error.name === "TypeError" && error.message.includes("fetch")) {
|
|
return {
|
|
code: ErrorCode.NETWORK_ERROR,
|
|
message: ErrorMessages[ErrorCode.NETWORK_ERROR],
|
|
shouldLogout: false,
|
|
shouldRetry: true,
|
|
};
|
|
}
|
|
|
|
// Timeout errors
|
|
if (error.name === "AbortError") {
|
|
return {
|
|
code: ErrorCode.TIMEOUT,
|
|
message: ErrorMessages[ErrorCode.TIMEOUT],
|
|
shouldLogout: false,
|
|
shouldRetry: true,
|
|
};
|
|
}
|
|
|
|
// Try pattern matching on error message
|
|
const code = matchErrorPattern(error.message);
|
|
const metadata = ErrorMetadata[code];
|
|
return {
|
|
code,
|
|
message: code === ErrorCode.UNKNOWN ? error.message : ErrorMessages[code],
|
|
shouldLogout: metadata.shouldLogout,
|
|
shouldRetry: metadata.shouldRetry,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
if (!status) return ErrorCode.UNKNOWN;
|
|
|
|
switch (status) {
|
|
case 400:
|
|
return ErrorCode.VALIDATION_FAILED;
|
|
case 401:
|
|
return ErrorCode.SESSION_EXPIRED;
|
|
case 403:
|
|
return ErrorCode.FORBIDDEN;
|
|
case 404:
|
|
return ErrorCode.NOT_FOUND;
|
|
case 409:
|
|
return ErrorCode.ACCOUNT_EXISTS;
|
|
case 429:
|
|
return ErrorCode.RATE_LIMITED;
|
|
case 503:
|
|
return ErrorCode.SERVICE_UNAVAILABLE;
|
|
default:
|
|
return status >= 500 ? ErrorCode.INTERNAL_ERROR : ErrorCode.UNKNOWN;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Convenience Functions
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Get user-friendly error message from any error
|
|
*/
|
|
export function getErrorMessage(error: unknown): string {
|
|
return parseError(error).message;
|
|
}
|
|
|
|
/**
|
|
* Check if error should trigger logout
|
|
*/
|
|
export function shouldLogout(error: unknown): boolean {
|
|
return parseError(error).shouldLogout;
|
|
}
|
|
|
|
/**
|
|
* Check if error can be retried
|
|
*/
|
|
export function canRetry(error: unknown): boolean {
|
|
return parseError(error).shouldRetry;
|
|
}
|
|
|
|
/**
|
|
* Get error code from any error
|
|
*/
|
|
export function getErrorCode(error: unknown): ErrorCodeType {
|
|
return parseError(error).code;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Re-exports from domain package for convenience
|
|
// ============================================================================
|
|
|
|
export {
|
|
ErrorCode,
|
|
ErrorMessages,
|
|
ErrorMetadata,
|
|
matchErrorPattern,
|
|
type ErrorCodeType,
|
|
} from "@customer-portal/domain/common";
|