UNPKG

@orchard9ai/error-handling

Version:

Federated error handling package with go-core-http-toolkit format support and logging integration

185 lines (162 loc) 4.75 kB
import type { DisplayError, ApiError, ErrorAction } from '../types/index.js'; import { sanitizeErrorMessage, sanitizeErrorDetails } from './sanitize.js'; /** * Create user-friendly error messages */ export function createUserFriendlyMessage(error: ApiError): string { // Common user-friendly messages const friendlyMessages: Record<string, string> = { 'UNAUTHORIZED': 'Please sign in to continue', 'FORBIDDEN': 'You don\'t have permission to perform this action', 'NOT_FOUND': 'The requested item could not be found', 'VALIDATION_ERROR': 'Please check your input and try again', 'RATE_LIMIT_EXCEEDED': 'You\'re doing that too often. Please wait a moment', 'INTERNAL_ERROR': 'Something went wrong on our end. Please try again', 'SERVICE_UNAVAILABLE': 'This service is temporarily unavailable', 'NETWORK_ERROR': 'Please check your internet connection', 'TIMEOUT': 'The request took too long. Please try again' }; if (error.code && friendlyMessages[error.code]) { return friendlyMessages[error.code]!; } // Try to make the error message more user-friendly return humanizeErrorMessage(sanitizeErrorMessage(error.error)); } /** * Humanize technical error messages */ export function humanizeErrorMessage(message: string): string { // Remove technical jargon const humanized = message .replace(/\b(HTTP|API|JSON|XML)\b/gi, '') .replace(/\b(error|exception|failure)\b:?\s*/gi, '') .replace(/\b(code|status)\b:?\s*\d+/gi, '') .replace(/\bmust\b/gi, 'should') .replace(/\bcannot\b/gi, 'can\'t') .replace(/\binvalid\b/gi, 'incorrect') .trim(); // Capitalize first letter return humanized.charAt(0).toUpperCase() + humanized.slice(1); } /** * Create retry action */ export function createRetryAction(retryFn: () => void): ErrorAction { return { label: 'Try Again', handler: retryFn, type: 'primary' }; } /** * Create refresh action */ export function createRefreshAction(): ErrorAction { return { label: 'Refresh Page', handler: () => window.location.reload(), type: 'secondary' }; } /** * Create contact support action */ export function createSupportAction( supportUrl: string = 'mailto:support@example.com' ): ErrorAction { return { label: 'Contact Support', handler: () => { window.open(supportUrl, '_blank'); }, type: 'secondary' }; } /** * Create dismiss action */ export function createDismissAction(dismissFn: () => void): ErrorAction { return { label: 'Dismiss', handler: dismissFn, type: 'secondary' }; } /** * Format field errors for display */ export function formatFieldErrors(details: Record<string, string>): Record<string, string> { const formatted: Record<string, string> = {}; const sanitized = sanitizeErrorDetails(details); for (const [field, message] of Object.entries(sanitized)) { // Convert snake_case to readable format const readableField = field .split('_') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); // Humanize the error message const readableMessage = humanizeErrorMessage(message); formatted[field] = `${readableField}: ${readableMessage}`; } return formatted; } /** * Get error icon based on type */ export function getErrorIcon(type: DisplayError['type']): string { switch (type) { case 'error': return '❌'; case 'warning': return '⚠️'; case 'info': return 'ℹ️'; default: return '❌'; } } /** * Get error color based on type */ export function getErrorColor(type: DisplayError['type']): string { switch (type) { case 'error': return 'red'; case 'warning': return 'orange'; case 'info': return 'blue'; default: return 'red'; } } /** * Truncate error message for display */ export function truncateErrorMessage(message: string, maxLength: number = 100): string { if (message.length <= maxLength) { return message; } return message.slice(0, maxLength - 3) + '...'; } /** * Create toast-friendly error */ export function createToastError(error: ApiError): { title: string; message: string; type: 'error' | 'warning' | 'info'; } { const message = createUserFriendlyMessage(error); const type = error.code === 'VALIDATION_ERROR' ? 'warning' : 'error'; return { title: 'Error', message: truncateErrorMessage(message, 80), type }; } /** * Check if error should be shown to user */ export function shouldShowToUser(error: ApiError): boolean { // Don't show internal/system errors to users const hiddenCodes = [ 'INTERNAL_ERROR', 'CONFIG_ERROR', 'DATABASE_ERROR', 'UNKNOWN_ERROR' ]; return !hiddenCodes.includes(error.code || ''); }