trainingpeaks-sdk
Version:
TypeScript SDK for TrainingPeaks API integration
160 lines (159 loc) • 6.6 kB
JavaScript
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);
};