@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
text/typescript
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 || '');
}