UNPKG

trainingpeaks-sdk

Version:
160 lines (159 loc) 6.6 kB
import { ERROR_CODES } from '../../domain/errors/error-codes.js'; import { SDKError } from '../../domain/errors/sdk-error.js'; export class HttpError extends SDKError { constructor(message, code, context, options) { super(message, code, context, options); this.name = 'HttpError'; this.code = code; this.status = context.status; this.statusText = context.statusText; this.url = context.url; this.method = context.method; this.requestId = context.requestId; } } export const createHttpError = (response, context = {}, cause) => { const { status, statusText, data } = response; const { url, method, requestData, requestId } = context; const errorContext = { status, statusText, url, method, requestData, responseData: data, headers: response.headers, requestId, }; switch (status) { case 400: return new HttpError(`Bad Request: ${getErrorMessage(data) || statusText}`, ERROR_CODES.VALIDATION_FAILED, errorContext, cause ? { cause } : undefined); case 401: return new HttpError(`Authentication failed: ${getErrorMessage(data) || statusText}`, ERROR_CODES.AUTH_TOKEN_INVALID, errorContext, cause ? { cause } : undefined); case 403: return new HttpError(`Access forbidden: ${getErrorMessage(data) || statusText}`, ERROR_CODES.AUTH_FAILED, errorContext, cause ? { cause } : undefined); case 404: return new HttpError(`Resource not found: ${getErrorMessage(data) || statusText}`, ERROR_CODES.WORKOUT_NOT_FOUND, errorContext, cause ? { cause } : undefined); case 408: case 504: return new HttpError(`Request timeout: ${getErrorMessage(data) || statusText}`, ERROR_CODES.NETWORK_TIMEOUT, errorContext, cause ? { cause } : undefined); case 409: return new HttpError(`Conflict: ${getErrorMessage(data) || statusText}`, ERROR_CODES.VALIDATION_FAILED, errorContext, cause ? { cause } : undefined); case 422: return new HttpError(`Validation error: ${getErrorMessage(data) || statusText}`, ERROR_CODES.VALIDATION_FAILED, errorContext, cause ? { cause } : undefined); case 429: return new HttpError(`Rate limit exceeded: ${getErrorMessage(data) || statusText}`, ERROR_CODES.NETWORK_RATE_LIMITED, errorContext, cause ? { cause } : undefined); case 500: return new HttpError(`Server error: ${getErrorMessage(data) || statusText}`, ERROR_CODES.NETWORK_SERVER_ERROR, errorContext, cause ? { cause } : undefined); case 502: return new HttpError(`Bad gateway: ${getErrorMessage(data) || statusText}`, ERROR_CODES.NETWORK_RESPONSE_INVALID, errorContext, cause ? { cause } : undefined); case 503: return new HttpError(`Service unavailable: ${getErrorMessage(data) || statusText}`, ERROR_CODES.NETWORK_SERVICE_UNAVAILABLE, errorContext, cause ? { cause } : undefined); default: return new HttpError(`HTTP Error ${status}: ${getErrorMessage(data) || statusText}`, ERROR_CODES.NETWORK_REQUEST_FAILED, errorContext, cause ? { cause } : undefined); } }; const getErrorMessage = (data) => { if (!data || typeof data !== 'object') return null; const errorData = data; return (errorData.message || errorData.error || errorData.detail || errorData.description || null); }; export const isHttpError = (error) => { return error instanceof HttpError; }; export const isClientError = (error) => { return isHttpError(error) && error.status >= 400 && error.status < 500; }; export const isServerError = (error) => { return isHttpError(error) && error.status >= 500; }; export const isRetryableError = (error) => { if (!isHttpError(error)) return false; return (error.status >= 500 || error.status === 408 || error.status === 429 || error.status === 502 || error.status === 503 || error.status === 504); }; export const throwHttpErrorFromResponse = (response, operation, context) => { if (response.error && isHttpError(response.error)) { throw response.error; } const errorMessage = response.error ? response.error instanceof Error ? response.error.message : String(response.error) : `${operation} failed`; const httpErrorResponse = { status: 500, statusText: 'Unknown Error', data: { message: errorMessage }, }; throw createHttpError(httpErrorResponse, context); }; export const throwValidationError = (message, context) => { const httpErrorResponse = { status: 400, statusText: 'Bad Request', data: { message }, }; throw createHttpError(httpErrorResponse, context); }; export const throwMissingDataError = (message, context) => { const httpErrorResponse = { status: 502, statusText: 'Bad Gateway', data: { message }, }; throw createHttpError(httpErrorResponse, context); }; export const throwServerError = (error, fallbackMessage, context) => { if (isHttpError(error)) { throw error; } const httpErrorResponse = { status: 500, statusText: 'Internal Server Error', data: { message: error instanceof Error ? error.message : fallbackMessage, }, }; throw createHttpError(httpErrorResponse, context, error); }; export const throwAuthError = (message, context) => { const httpErrorResponse = { status: 401, statusText: 'Unauthorized', data: { message }, }; throw createHttpError(httpErrorResponse, context); }; export const throwCookieNotFoundError = (cookieName, context) => { const httpErrorResponse = { status: 401, statusText: 'Unauthorized', data: { message: `${cookieName} cookie not found` }, }; throw createHttpError(httpErrorResponse, context); }; export const throwTokenExpiredError = (context) => { const httpErrorResponse = { status: 401, statusText: 'Unauthorized', data: { message: 'Received expired token from TrainingPeaks API' }, }; throw createHttpError(httpErrorResponse, context); }; export const handleRepositoryError = (error, operation, context, logger, params) => { logger.error(`Failed to ${operation}`, { error, params }); if (isHttpError(error)) throw error; throwServerError(error, `Failed to ${operation}`, context); };