@orchard9ai/error-handling
Version:
Federated error handling package with go-core-http-toolkit format support and logging integration
156 lines (140 loc) • 3.75 kB
text/typescript
import type { ApiError } from '../types/index.js';
import { sanitizeErrorMessage } from './sanitize.js';
/**
* Parse error from HTTP response
*/
export function parseHttpError(response: Response, responseText?: string): ApiError {
try {
// Try to parse as JSON first
if (responseText) {
const parsed = JSON.parse(responseText);
// Check if it matches our API error format
if (isApiError(parsed)) {
return parsed;
}
// Transform other formats
if (parsed.message) {
return {
error: parsed.message,
code: parsed.code || getCodeFromStatus(response.status)
};
}
}
} catch {
// JSON parsing failed, fall back to status-based error
}
// Generate error from HTTP status
return {
error: getMessageFromStatus(response.status),
code: getCodeFromStatus(response.status)
};
}
/**
* Parse error from fetch error
*/
export function parseFetchError(error: Error): ApiError {
if (error.name === 'AbortError') {
return {
error: 'Request was cancelled',
code: 'REQUEST_CANCELLED'
};
}
if (error.name === 'TypeError' && error.message.includes('fetch')) {
return {
error: 'Network connection failed',
code: 'NETWORK_ERROR',
details: {
originalMessage: error.message
}
};
}
return {
error: sanitizeErrorMessage(error.message || 'An unknown error occurred'),
code: 'UNKNOWN_ERROR',
details: {
name: sanitizeErrorMessage(error.name)
}
};
}
/**
* Parse error from any thrown value
*/
export function parseUnknownError(error: unknown): ApiError {
if (error instanceof Error) {
return parseFetchError(error);
}
if (typeof error === 'string') {
return {
error,
code: 'STRING_ERROR'
};
}
if (typeof error === 'object' && error !== null) {
const obj = error as Record<string, unknown>;
if (isApiError(obj)) {
return obj;
}
return {
error: (obj as any)['message'] || 'An error occurred',
code: (obj as any)['code'] || 'OBJECT_ERROR',
details: (obj as any)['details'] || {}
};
}
return {
error: 'An unknown error occurred',
code: 'UNKNOWN_ERROR',
details: {
type: typeof error,
value: String(error)
}
};
}
/**
* Type guard for API error format
*/
export function isApiError(obj: unknown): obj is ApiError {
return (
typeof obj === 'object' &&
obj !== null &&
'error' in obj &&
typeof (obj as any).error === 'string'
);
}
/**
* Get error code from HTTP status
*/
function getCodeFromStatus(status: number): string {
switch (status) {
case 400: return 'BAD_REQUEST';
case 401: return 'UNAUTHORIZED';
case 403: return 'FORBIDDEN';
case 404: return 'NOT_FOUND';
case 409: return 'CONFLICT';
case 422: return 'VALIDATION_ERROR';
case 429: return 'RATE_LIMIT_EXCEEDED';
case 500: return 'INTERNAL_ERROR';
case 502: return 'BAD_GATEWAY';
case 503: return 'SERVICE_UNAVAILABLE';
case 504: return 'TIMEOUT';
default: return 'HTTP_ERROR';
}
}
/**
* Get error message from HTTP status
*/
function getMessageFromStatus(status: number): string {
switch (status) {
case 400: return 'Bad request';
case 401: return 'Authentication required';
case 403: return 'Access denied';
case 404: return 'Not found';
case 409: return 'Conflict';
case 422: return 'Validation failed';
case 429: return 'Too many requests';
case 500: return 'Internal server error';
case 502: return 'Bad gateway';
case 503: return 'Service unavailable';
case 504: return 'Request timeout';
default: return `HTTP error ${status}`;
}
}