UNPKG

@mbc-cqrs-serverless/core

Version:
392 lines 12.8 kB
"use strict"; /** * AWS Error Factory * * Provides standardized factory functions for creating AWS SDK errors * used in integration tests. This eliminates code duplication across * test files and ensures consistent error structure. * * Usage: * import { createDynamoDBError, createS3Error } from './utilities/aws-error-factory' * * const error = createDynamoDBError('ProvisionedThroughputExceededException', { * message: 'Rate exceeded', * httpStatusCode: 400, * throttling: true, * }) */ Object.defineProperty(exports, "__esModule", { value: true }); exports.createDynamoDBError = createDynamoDBError; exports.createS3Error = createS3Error; exports.createSQSError = createSQSError; exports.createSNSError = createSNSError; exports.createSFNError = createSFNError; exports.createSESError = createSESError; exports.createNetworkError = createNetworkError; exports.createTimeoutError = createTimeoutError; exports.createHttpStatusError = createHttpStatusError; exports.isRetriableAWSError = isRetriableAWSError; exports.isThrottlingError = isThrottlingError; exports.isNetworkError = isNetworkError; /** * Creates a base AWS error with the standard structure */ function createBaseAWSError(name, options = {}) { const { message = name, httpStatusCode = 400, requestId = `test-request-${Date.now()}`, throttling, fault = httpStatusCode >= 500 ? 'server' : 'client', retryAfterSeconds, code, } = options; const error = new Error(message); error.name = name; error.$metadata = { httpStatusCode, requestId, }; error.$fault = fault; if (throttling !== undefined) { error.$retryable = { throttling }; } if (retryAfterSeconds !== undefined) { error.retryAfterSeconds = retryAfterSeconds; } if (code !== undefined) { error.code = code; } return error; } /** * Creates a DynamoDB-specific error */ function createDynamoDBError(name, options = {}) { const defaultOptions = {}; // Set default properties based on error type switch (name) { case 'ProvisionedThroughputExceededException': case 'ThrottlingException': case 'RequestLimitExceeded': defaultOptions.throttling = true; defaultOptions.httpStatusCode = 400; break; case 'InternalServerError': defaultOptions.httpStatusCode = 500; defaultOptions.fault = 'server'; break; case 'ServiceUnavailable': defaultOptions.httpStatusCode = 503; defaultOptions.fault = 'server'; break; case 'ValidationException': case 'ConditionalCheckFailedException': case 'ResourceNotFoundException': defaultOptions.httpStatusCode = 400; defaultOptions.fault = 'client'; break; case 'TransactionCanceledException': case 'TransactionConflictException': defaultOptions.httpStatusCode = 400; break; case 'ItemCollectionSizeLimitExceededException': defaultOptions.httpStatusCode = 400; break; case 'AccessDeniedException': defaultOptions.httpStatusCode = 403; break; } const error = createBaseAWSError(name, { ...defaultOptions, ...options }); error.$service = 'DynamoDB'; return error; } /** * Creates an S3-specific error */ function createS3Error(name, options = {}) { const defaultOptions = {}; switch (name) { case 'NoSuchKey': case 'NoSuchBucket': case 'NoSuchUpload': defaultOptions.httpStatusCode = 404; break; case 'AccessDenied': case 'InvalidAccessKeyId': case 'SignatureDoesNotMatch': defaultOptions.httpStatusCode = 403; break; case 'EntityTooLarge': defaultOptions.httpStatusCode = 400; break; case 'SlowDown': defaultOptions.httpStatusCode = 503; defaultOptions.throttling = true; break; case 'InternalError': defaultOptions.httpStatusCode = 500; defaultOptions.fault = 'server'; break; case 'ServiceUnavailable': defaultOptions.httpStatusCode = 503; defaultOptions.fault = 'server'; break; case 'BucketNotEmpty': case 'BucketAlreadyExists': case 'BucketAlreadyOwnedByYou': defaultOptions.httpStatusCode = 409; break; } const error = createBaseAWSError(name, { ...defaultOptions, ...options }); error.$service = 'S3'; return error; } /** * Creates an SQS-specific error */ function createSQSError(name, options = {}) { const defaultOptions = {}; switch (name) { case 'QueueDoesNotExist': defaultOptions.httpStatusCode = 400; break; case 'ThrottlingException': case 'OverLimit': defaultOptions.throttling = true; defaultOptions.httpStatusCode = 400; break; case 'AccessDeniedException': defaultOptions.httpStatusCode = 403; break; case 'InvalidMessageContents': case 'BatchEntryIdsNotDistinct': case 'BatchRequestTooLong': case 'EmptyBatchRequest': case 'InvalidBatchEntryId': case 'TooManyEntriesInBatchRequest': defaultOptions.httpStatusCode = 400; break; case 'MessageNotInflight': case 'ReceiptHandleIsInvalid': defaultOptions.httpStatusCode = 400; break; } const error = createBaseAWSError(name, { ...defaultOptions, ...options }); error.$service = 'SQS'; return error; } /** * Creates an SNS-specific error */ function createSNSError(name, options = {}) { const defaultOptions = {}; switch (name) { case 'NotFoundException': defaultOptions.httpStatusCode = 404; break; case 'TopicLimitExceededException': case 'SubscriptionLimitExceededException': case 'ThrottledException': defaultOptions.throttling = true; defaultOptions.httpStatusCode = 400; break; case 'AuthorizationErrorException': defaultOptions.httpStatusCode = 403; break; case 'InternalErrorException': defaultOptions.httpStatusCode = 500; defaultOptions.fault = 'server'; break; case 'InvalidParameterException': case 'InvalidParameterValueException': defaultOptions.httpStatusCode = 400; break; } const error = createBaseAWSError(name, { ...defaultOptions, ...options }); error.$service = 'SNS'; return error; } /** * Creates a Step Functions-specific error */ function createSFNError(name, options = {}) { const defaultOptions = {}; switch (name) { case 'ExecutionDoesNotExist': case 'StateMachineDoesNotExist': case 'TaskDoesNotExist': case 'ActivityDoesNotExist': case 'ResourceNotFound': defaultOptions.httpStatusCode = 400; break; case 'ExecutionAlreadyExists': defaultOptions.httpStatusCode = 400; break; case 'ThrottlingException': case 'ExecutionLimitExceeded': case 'StateMachineLimitExceeded': case 'ActivityLimitExceeded': case 'ServiceQuotaExceededException': defaultOptions.throttling = true; defaultOptions.httpStatusCode = 400; break; case 'InvalidArn': case 'InvalidDefinition': case 'InvalidExecutionInput': case 'InvalidName': case 'InvalidToken': defaultOptions.httpStatusCode = 400; break; case 'TaskTimedOut': defaultOptions.httpStatusCode = 400; break; } const error = createBaseAWSError(name, { ...defaultOptions, ...options }); error.$service = 'StepFunctions'; return error; } /** * Creates an SES-specific error */ function createSESError(name, options = {}) { const defaultOptions = {}; switch (name) { case 'MessageRejected': defaultOptions.httpStatusCode = 400; break; case 'MailFromDomainNotVerifiedException': defaultOptions.httpStatusCode = 400; break; case 'ConfigurationSetDoesNotExistException': case 'NotFoundException': defaultOptions.httpStatusCode = 404; break; case 'AccountSendingPausedException': defaultOptions.httpStatusCode = 400; break; case 'LimitExceededException': case 'TooManyRequestsException': defaultOptions.throttling = true; defaultOptions.httpStatusCode = 429; break; case 'BadRequestException': defaultOptions.httpStatusCode = 400; break; } const error = createBaseAWSError(name, { ...defaultOptions, ...options }); error.$service = 'SES'; return error; } /** * Creates a network-level error */ function createNetworkError(code, message) { const defaultMessages = { ECONNRESET: 'read ECONNRESET', ECONNREFUSED: 'connect ECONNREFUSED', ETIMEDOUT: 'connect ETIMEDOUT', ENETUNREACH: 'network unreachable', ENOTFOUND: 'getaddrinfo ENOTFOUND', EPIPE: 'write EPIPE', EAI_AGAIN: 'getaddrinfo EAI_AGAIN', }; const error = new Error(message || defaultMessages[code]); error.code = code; return error; } /** * Creates a timeout error */ function createTimeoutError(message = 'Request timeout') { const error = new Error(message); error.name = 'TimeoutError'; error.code = 'ETIMEDOUT'; return error; } // ============================================================================ // Generic HTTP Status Errors // ============================================================================ /** * Creates an error for a specific HTTP status code */ function createHttpStatusError(statusCode, options = {}) { const statusMessages = { 400: 'Bad Request', 401: 'Unauthorized', 403: 'Forbidden', 404: 'Not Found', 408: 'Request Timeout', 429: 'Too Many Requests', 500: 'Internal Server Error', 502: 'Bad Gateway', 503: 'Service Unavailable', 504: 'Gateway Timeout', }; const name = options.name || statusMessages[statusCode] || 'UnknownError'; const message = options.message || statusMessages[statusCode] || 'Error'; return createBaseAWSError(name, { message, httpStatusCode: statusCode, requestId: options.requestId, fault: statusCode >= 500 ? 'server' : 'client', throttling: statusCode === 429, }); } // ============================================================================ // Utility Functions // ============================================================================ /** * Checks if an error is retriable based on AWS SDK v3 patterns */ function isRetriableAWSError(error) { // Check $retryable flag if (error.$retryable) { return true; } // Check HTTP status codes const statusCode = error.$metadata?.httpStatusCode; if (statusCode) { // 5xx errors are retriable if (statusCode >= 500 && statusCode < 600) { return true; } // 429 Too Many Requests if (statusCode === 429) { return true; } } // Check error names const retriableErrorNames = [ 'ProvisionedThroughputExceededException', 'ThrottlingException', 'InternalServerError', 'ServiceUnavailable', 'RequestLimitExceeded', 'SlowDown', 'TooManyRequestsException', ]; return retriableErrorNames.includes(error.name); } /** * Checks if an error is a throttling error */ function isThrottlingError(error) { return error.$retryable?.throttling === true; } /** * Checks if an error is a network error */ function isNetworkError(error) { const networkErrorCodes = [ 'ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'ENETUNREACH', 'ENOTFOUND', 'EPIPE', 'EAI_AGAIN', ]; if (error.code && networkErrorCodes.includes(error.code)) { return true; } const message = error.message.toLowerCase(); return (message.includes('socket hang up') || message.includes('network error') || message.includes('connection reset') || message.includes('connection refused')); } //# sourceMappingURL=aws-error-factory.js.map