146 lines
3.6 KiB
TypeScript
146 lines
3.6 KiB
TypeScript
|
|
import * as plugins from '../plugins.js';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* S3 error codes mapped to HTTP status codes
|
||
|
|
*/
|
||
|
|
const S3_ERROR_CODES: Record<string, number> = {
|
||
|
|
'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<string, any>;
|
||
|
|
|
||
|
|
constructor(
|
||
|
|
code: string,
|
||
|
|
message: string,
|
||
|
|
detail: Record<string, any> = {}
|
||
|
|
) {
|
||
|
|
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('<?xml')) {
|
||
|
|
return `<?xml version="1.0" encoding="UTF-8"?>\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 }
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|