Skip to content

Commit

Permalink
chore(s3): prettify complete multipart
Browse files Browse the repository at this point in the history
  • Loading branch information
AllanZhengYP committed Jun 18, 2023
1 parent e3b0305 commit ab35838
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 32 deletions.
5 changes: 3 additions & 2 deletions packages/core/src/clients/types/aws.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { MetadataBearer } from '@aws-sdk/types';
import { Endpoint } from './core';
import { HttpResponse } from './http';

export type { Credentials } from '@aws-sdk/types';
export type { Credentials, MetadataBearer } from '@aws-sdk/types';

export type SourceData = string | ArrayBuffer | ArrayBufferView;

Expand All @@ -24,4 +25,4 @@ export interface ServiceClientOptions {
*/
export type ErrorParser = (
response?: HttpResponse
) => Promise<Error | undefined>;
) => Promise<(Error & MetadataBearer) | undefined>;
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ const completeMultipartUploadErrorWith200CodeCase: ApiFunctionalTestCase<
'error case',
'completeMultipartUpload with 200 status',
completeMultipartUpload,
{ ...defaultConfig, retryDecider: async () => false },
{ ...defaultConfig, retryDecider: async () => false }, // disable retry
completeMultipartUploadHappyCase[4],
completeMultipartUploadHappyCase[5],
{
Expand All @@ -133,10 +133,7 @@ const completeMultipartUploadErrorWith200CodeCase: ApiFunctionalTestCase<
'</Error>',
},
{
$metadata: expect.objectContaining({
...expectedMetadata,
httpStatusCode: 500,
}),
$metadata: expect.objectContaining(expectedMetadata),
message: 'We encountered an internal error. Please try again.',
name: 'InternalError',
},
Expand Down
67 changes: 42 additions & 25 deletions packages/storage/src/AwsClients/S3/completeMultipartUpload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,21 +78,33 @@ const serializeCompletedPartList = (input: CompletedPart): string => {
return `<Part><ETag>${input.ETag}</ETag><PartNumber>${input.PartNumber}</PartNumber></Part>`;
};

/**
* Parse CompleteMultipartUpload API response payload, which may be empty or error indicating internal
* server error, even when the status code is 200.
*
* Ref: https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html#API_CompleteMultipartUpload_Example_4
*/
const parseXmlBodyOrThrow = async (response: HttpResponse): Promise<any> => {
const parsed = await parseXmlBody(response); // Handles empty body case
if (parsed.Code !== undefined && parsed.Message !== undefined) {
const error = await parseXmlError({
...response,
statusCode: 500, // To workaround the >=300 status code check common to other APIs.
});
error.$metadata.httpStatusCode = response.statusCode;
throw error;
}
return parsed;
};

const completeMultipartUploadDeserializer = async (
response: HttpResponse
): Promise<CompleteMultipartUploadOutput> => {
if (response.statusCode >= 300) {
const error = await parseXmlError(response);
throw error;
} else {
const parsed = await parseXmlBody(response);
if (parsed.Code !== undefined && parsed.Message !== undefined) {
const error = await parseXmlError({
...response,
statusCode: 500 /** indicating server error */,
});
throw error;
}
const parsed = await parseXmlBodyOrThrow(response);
const contents = map(parsed, {
ETag: 'ETag',
Key: 'Key',
Expand All @@ -105,7 +117,27 @@ const completeMultipartUploadDeserializer = async (
}
};

const defaultRetryDecider = defaultConfig.retryDecider;
// CompleteMultiPartUpload API returns 200 status code with empty body or error message.
// This indicates internal server error after the response has been sent to the client.
// Ref: https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html#API_CompleteMultipartUpload_Example_4
const retryWhenErrorWith200StatusCode = async (
response: HttpResponse,
error?: Error
): Promise<boolean> => {
if (response.statusCode === 200) {
if (!response.body) {
return true;
}
const parsed = await parseXmlBody(response);
if (parsed.Code !== undefined && parsed.Message !== undefined) {
return true;
}
return false;
}

const defaultRetryDecider = defaultConfig.retryDecider;
return defaultRetryDecider(response, error);
};

export const completeMultipartUpload = composeServiceApi(
s3TransferHandler,
Expand All @@ -114,21 +146,6 @@ export const completeMultipartUpload = composeServiceApi(
{
...defaultConfig,
responseType: 'text',
// CompleteMultiPartUpload API returns 200 status code with empty body or error message.
// This indicates internal server error after the response has been sent to the client.
// Ref: https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html#API_CompleteMultipartUpload_Example_4
retryDecider: async (response: HttpResponse, error?: Error) => {
if (response.statusCode === 200) {
if (!response.body) {
return true;
}
const parsed = await parseXmlBody(response);
if (parsed.Code !== undefined && parsed.Message !== undefined) {
return true;
}
return false;
}
return defaultRetryDecider(response, error);
},
retryDecider: retryWhenErrorWith200StatusCode,
}
);

0 comments on commit ab35838

Please sign in to comment.