import * as plugins from '../plugins.js'; /** * S3 error codes mapped to HTTP status codes */ const S3_ERROR_CODES: Record = { 'AccessDenied': 403, 'BadDigest': 400, 'BadRequest': 400, 'BucketAlreadyExists': 409, 'BucketAlreadyOwnedByYou': 409, 'BucketNotEmpty': 409, 'CredentialsNotSupported': 400, 'EntityTooSmall': 400, 'EntityTooLarge': 400, 'ExpiredToken': 400, 'IncompleteBody': 400, 'IncorrectNumberOfFilesInPostRequest': 400, 'InlineDataTooLarge': 400, 'InternalError': 500, 'InvalidArgument': 400, 'InvalidBucketName': 400, 'InvalidDigest': 400, 'InvalidLocationConstraint': 400, 'InvalidPart': 400, 'InvalidPartOrder': 400, 'InvalidRange': 416, 'InvalidRequest': 400, 'InvalidSecurity': 403, 'InvalidSOAPRequest': 400, 'InvalidStorageClass': 400, 'InvalidTargetBucketForLogging': 400, 'InvalidToken': 400, 'InvalidURI': 400, 'KeyTooLongError': 400, 'MalformedACLError': 400, 'MalformedPOSTRequest': 400, 'MalformedXML': 400, 'MaxMessageLengthExceeded': 400, 'MaxPostPreDataLengthExceededError': 400, 'MetadataTooLarge': 400, 'MethodNotAllowed': 405, 'MissingContentLength': 411, 'MissingRequestBodyError': 400, 'MissingSecurityElement': 400, 'MissingSecurityHeader': 400, 'NoLoggingStatusForKey': 400, 'NoSuchBucket': 404, 'NoSuchKey': 404, 'NoSuchLifecycleConfiguration': 404, 'NoSuchUpload': 404, 'NoSuchVersion': 404, 'NotImplemented': 501, 'NotSignedUp': 403, 'OperationAborted': 409, 'PermanentRedirect': 301, 'PreconditionFailed': 412, 'Redirect': 307, 'RequestIsNotMultiPartContent': 400, 'RequestTimeout': 400, 'RequestTimeTooSkewed': 403, 'RequestTorrentOfBucketError': 400, 'SignatureDoesNotMatch': 403, 'ServiceUnavailable': 503, 'SlowDown': 503, 'TemporaryRedirect': 307, 'TokenRefreshRequired': 400, 'TooManyBuckets': 400, 'UnexpectedContent': 400, 'UnresolvableGrantByEmailAddress': 400, 'UserKeyMustBeSpecified': 400, }; /** * S3-compatible error class that formats errors as XML responses */ export class S3Error extends Error { public status: number; public code: string; public detail: Record; constructor( code: string, message: string, detail: Record = {} ) { super(message); this.name = 'S3Error'; this.code = code; this.status = S3_ERROR_CODES[code] || 500; this.detail = detail; // Maintain proper stack trace if (Error.captureStackTrace) { Error.captureStackTrace(this, S3Error); } } /** * Convert error to S3-compatible XML format */ public toXML(): string { const smartXmlInstance = new plugins.SmartXml(); const errorObj: any = { Error: { Code: this.code, Message: this.message, ...this.detail, }, }; const xml = smartXmlInstance.createXmlFromObject(errorObj); // Ensure XML declaration if (!xml.startsWith('\n${xml}`; } return xml; } /** * Create S3Error from a generic Error */ public static fromError(err: any): S3Error { if (err instanceof S3Error) { return err; } // Map common errors if (err.code === 'ENOENT') { return new S3Error('NoSuchKey', 'The specified key does not exist.'); } if (err.code === 'EACCES') { return new S3Error('AccessDenied', 'Access Denied'); } // Default to internal error return new S3Error( 'InternalError', 'We encountered an internal error. Please try again.', { OriginalError: err.message } ); } }