UNPKG

trainingpeaks-sdk

Version:
204 lines (203 loc) 7.81 kB
import { HttpError } from '../adapters/errors/http-errors.js'; import { AuthenticationError, NetworkError, UserError, ValidationError, } from '../domain/errors/domain-errors.js'; import { ERROR_CODES } from '../domain/errors/error-codes.js'; import { SDKError } from '../domain/errors/sdk-error.js'; export class ClientError extends SDKError { constructor(message, code, operation, options = {}) { super(message, code, { ...options.context, originalError: options.originalError, }); this.name = 'ClientError'; this.operation = operation; this.suggestions = options.suggestions || []; this.isRetryable = options.isRetryable || false; this.httpStatus = options.httpStatus; } } export const withClientErrorHandling = async (operation, operationName, context = {}) => { try { return await operation(); } catch (error) { throw transformToClientError(error, operationName, context); } }; export const transformToClientError = (error, operation, context = {}) => { if (error instanceof AuthenticationError) { return new ClientError(error.message, ERROR_CODES.AUTH_FAILED, operation, { suggestions: getSuggestionsForCode(ERROR_CODES.AUTH_FAILED), isRetryable: false, originalError: error, context, }); } if (error instanceof ValidationError) { return new ClientError(error.message, ERROR_CODES.VALIDATION_FAILED, operation, { suggestions: getSuggestionsForCode(ERROR_CODES.VALIDATION_FAILED), isRetryable: false, originalError: error, context, }); } if (error instanceof NetworkError) { return new ClientError(error.message, ERROR_CODES.NETWORK_REQUEST_FAILED, operation, { suggestions: getSuggestionsForCode(ERROR_CODES.NETWORK_TIMEOUT), isRetryable: true, originalError: error, context, }); } if (error instanceof UserError) { return new ClientError(error.message, error.code, operation, { suggestions: getSuggestionsForCode(error.code), isRetryable: false, originalError: error, context, }); } if (error instanceof HttpError) { return new ClientError(error.message, error.code, operation, { suggestions: getSuggestionsForHttpError(error), isRetryable: error.status >= 500 || error.status === 429, httpStatus: error.status, originalError: error, context: { ...context, url: error.url, method: error.method }, }); } if (error instanceof SDKError) { return new ClientError(error.message, error.code, operation, { suggestions: getSuggestionsForCode(error.code), originalError: error, context, }); } return new ClientError(`${operation} failed: ${error.message}`, ERROR_CODES.UNKNOWN_ERROR, operation, { suggestions: [ 'Check network connection', 'Verify API credentials', 'Try again later', ], originalError: error, context, }); }; const getSuggestionsForHttpError = (error) => { switch (error.status) { case 400: return [ 'Check request parameters for valid format', 'Ensure all required fields are included', 'Verify data types match API expectations', ]; case 401: return [ 'Check if your authentication token is valid', 'Try refreshing your authentication token', 'Verify your API credentials are correct', ]; case 403: return [ 'Verify you have permission to access this resource', 'Check if your account has the required subscription plan', 'Contact support if you believe this is an error', ]; case 404: return [ 'Verify the resource ID exists', 'Check if the resource has been deleted', 'Ensure you have access to this resource', ]; case 408: case 504: return [ 'The request timed out - try again', 'Check your network connection', 'Consider increasing timeout settings', ]; case 429: return [ 'You have exceeded the rate limit', 'Wait before making another request', 'Consider implementing exponential backoff', 'Check your subscription plan for higher limits', ]; case 500: case 502: case 503: return [ 'This is a temporary server error', 'Try again in a few moments', 'Check the service status page', 'Contact support if the issue persists', ]; default: return [ 'Check the HTTP status code for specific guidance', 'Verify your request format', 'Try again if this seems like a temporary issue', ]; } }; const getSuggestionsForCode = (code) => { switch (code) { case ERROR_CODES.AUTH_FAILED: case ERROR_CODES.AUTH_TOKEN_INVALID: return [ 'Verify your authentication credentials', 'Check if your token has expired', 'Try re-authenticating', ]; case ERROR_CODES.NETWORK_TIMEOUT: return [ 'Check your network connection', 'Try again with a longer timeout', 'Verify the service is available', ]; case ERROR_CODES.NETWORK_RATE_LIMITED: return [ 'Reduce the frequency of your requests', 'Implement exponential backoff', 'Check your rate limit allowance', ]; case ERROR_CODES.VALIDATION_FAILED: return [ 'Check your input parameters', 'Ensure required fields are provided', 'Verify data formats are correct', ]; default: return ['Check the error code documentation for specific guidance']; } }; export const withClientResponseHandling = async (operation, operationName, errorResponseFactory, context = {}) => { try { return await withClientErrorHandling(operation, operationName, context); } catch (error) { const clientError = error; return errorResponseFactory(clientError.message, clientError.code); } }; export const createUserFriendlyMessage = (error) => { const baseMessage = `Operation "${error.operation}" failed`; if (error.httpStatus) { switch (error.httpStatus) { case 401: return `${baseMessage}: Authentication required. Please check your credentials.`; case 403: return `${baseMessage}: Access denied. You may not have permission for this resource.`; case 404: return `${baseMessage}: Resource not found. Please verify the ID is correct.`; case 429: return `${baseMessage}: Rate limit exceeded. Please wait before trying again.`; case 500: case 502: case 503: return `${baseMessage}: Server error. This is likely temporary - please try again.`; default: return `${baseMessage}: ${error.message}`; } } return `${baseMessage}: ${error.message}`; };