Assist_Design/apps/portal/src/lib/utils/error-handling.ts
barsa c7230f391a Refactor global exception handling and support case management
- 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.
2025-11-26 16:36:06 +09:00

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";