Skip to content

Commit

Permalink
feat(s3): implement putObject (#11513)
Browse files Browse the repository at this point in the history
  • Loading branch information
AllanZhengYP authored Jun 17, 2023
1 parent 0c43bb3 commit 9efe09a
Show file tree
Hide file tree
Showing 11 changed files with 702 additions and 37 deletions.
5 changes: 4 additions & 1 deletion packages/storage/__tests__/AwsClients/S3/cases/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

export { default } from './listObjectsV2';
import listObjectsV2Case from './listObjectsV2';
import putObjectCase from './putObject';

export default [...listObjectsV2Case, ...putObjectCase];
39 changes: 6 additions & 33 deletions packages/storage/__tests__/AwsClients/S3/cases/listObjectsV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,12 @@

import { listObjectsV2 } from '../../../../src/AwsClients/S3';
import { ApiFunctionalTestCase } from '../../testUtils/types';

const EMPTY_SHA256 =
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';

const MOCK_REQUEST_ID = 'requestId';
const MOCK_EXTENDED_REQUEST_ID = 'requestId2';
const DEFAULT_RESPONSE_HEADERS = {
'x-amz-id-2': MOCK_EXTENDED_REQUEST_ID,
'x-amz-request-id': MOCK_REQUEST_ID,
};

const expectedMetadata = {
attempts: 1,
requestId: MOCK_REQUEST_ID,
extendedRequestId: MOCK_EXTENDED_REQUEST_ID,
httpStatusCode: 200,
};

const defaultConfig = {
region: 'us-east-1',
credentials: {
accessKeyId: 'key',
secretAccessKey: 'secret',
},
};

const defaultRequiredRequestHeaders = {
authorization: expect.stringContaining('Signature'),
host: 'bucket.s3.us-east-1.amazonaws.com',
'x-amz-content-sha256': EMPTY_SHA256,
'x-amz-date': expect.stringMatching(/^\d{8}T\d{6}Z/),
'x-amz-user-agent': expect.stringContaining('aws-amplify'),
};
import {
defaultConfig,
defaultRequiredRequestHeaders,
DEFAULT_RESPONSE_HEADERS,
expectedMetadata,
} from './shared';

// API Reference: https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html
const listObjectsV2HappyCase: ApiFunctionalTestCase<typeof listObjectsV2> = [
Expand Down
78 changes: 78 additions & 0 deletions packages/storage/__tests__/AwsClients/S3/cases/putObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { putObject } from '../../../../src/AwsClients/S3';
import { ApiFunctionalTestCase } from '../../testUtils/types';
import {
defaultConfig,
DEFAULT_RESPONSE_HEADERS,
expectedMetadata,
} from './shared';

const putObjectRequest = {
Bucket: 'bucket',
Key: 'key',
Body: 'body',
ServerSideEncryption: 'ServerSideEncryption',
SSECustomerAlgorithm: 'SSECustomerAlgorithm',
SSECustomerKey: 'SSECustomerKey',
SSECustomerKeyMD5: 'SSECustomerKeyMD5',
SSEKMSKeyId: 'SSEKMSKeyId',
ACL: 'public-read',
CacheControl: 'CacheControl',
ContentDisposition: 'ContentDisposition',
ContentEncoding: 'ContentEncoding',
ContentType: 'ContentType',
Expires: new Date('2020-01-01'),
Metadata: {
Param1: 'value 1',
},
Tagging: 'Tagging',
};

const expectedPutObjectRequestHeaders = {
'x-amz-server-side-encryption': 'ServerSideEncryption',
'x-amz-server-side-encryption-customer-algorithm': 'SSECustomerAlgorithm',
'x-amz-server-side-encryption-customer-key': 'SSECustomerKey',
'x-amz-server-side-encryption-customer-key-md5': 'SSECustomerKeyMD5',
'x-amz-server-side-encryption-aws-kms-key-id': 'SSEKMSKeyId',
'x-amz-acl': 'public-read',
'cache-control': 'CacheControl',
'content-disposition': 'ContentDisposition',
'content-encoding': 'ContentEncoding',
'content-type': 'ContentType',
expires: 'Wed, 01 Jan 2020 00:00:00 GMT',
'x-amz-tagging': 'Tagging',
'x-amz-meta-param1': 'value 1',
};

const putObjectHappyCase: ApiFunctionalTestCase<typeof putObject> = [
'happy case',
'putObject',
putObject,
defaultConfig,
putObjectRequest,
expect.objectContaining({
url: expect.objectContaining({
href: 'https://bucket.s3.us-east-1.amazonaws.com/key',
}),
headers: expect.objectContaining(expectedPutObjectRequestHeaders),
body: 'body',
}),
{
status: 200,
headers: {
...DEFAULT_RESPONSE_HEADERS,
'x-amz-version-id': 'versionId',
etag: 'etag',
},
body: '',
},
{
$metadata: expect.objectContaining(expectedMetadata),
ETag: 'etag',
VersionId: 'versionId',
},
];

export default [putObjectHappyCase];
35 changes: 35 additions & 0 deletions packages/storage/__tests__/AwsClients/S3/cases/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

export const EMPTY_SHA256 =
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';

export const MOCK_REQUEST_ID = 'requestId';
export const MOCK_EXTENDED_REQUEST_ID = 'requestId2';
export const DEFAULT_RESPONSE_HEADERS = {
'x-amz-id-2': MOCK_EXTENDED_REQUEST_ID,
'x-amz-request-id': MOCK_REQUEST_ID,
};

export const expectedMetadata = {
attempts: 1,
requestId: MOCK_REQUEST_ID,
extendedRequestId: MOCK_EXTENDED_REQUEST_ID,
httpStatusCode: 200,
};

export const defaultConfig = {
region: 'us-east-1',
credentials: {
accessKeyId: 'key',
secretAccessKey: 'secret',
},
};

export const defaultRequiredRequestHeaders = {
authorization: expect.stringContaining('Signature'),
host: 'bucket.s3.us-east-1.amazonaws.com',
'x-amz-content-sha256': EMPTY_SHA256,
'x-amz-date': expect.stringMatching(/^\d{8}T\d{6}Z/),
'x-amz-user-agent': expect.stringContaining('aws-amplify'),
};
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ describe('S3 APIs functional test', () => {
mockBinaryResponse(response as any)
);
try {
// @ts-ignore
const output = await handler(config, input);
if (caseType === 'happy case') {
expect(output).toEqual(outputOrError);
Expand Down
1 change: 1 addition & 0 deletions packages/storage/src/AwsClients/S3/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export {
ListObjectsV2Input,
ListObjectsV2Output,
} from './listObjectsV2';
export { putObject, PutObjectInput, PutObjectOutput } from './putObject';
90 changes: 90 additions & 0 deletions packages/storage/src/AwsClients/S3/putObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import {
Endpoint,
HttpRequest,
HttpResponse,
parseMetadata,
} from '@aws-amplify/core/internals/aws-client-utils';
import { composeServiceApi } from '@aws-amplify/core/internals/aws-client-utils/composers';

import { defaultConfig } from './base';
import type { PutObjectCommandInput, PutObjectCommandOutput } from './types';
import {
map,
parseXmlError,
s3TransferHandler,
serializeObjectConfigsToHeaders,
} from './utils';
import type { S3ProviderPutConfig } from '../../types';

/**
* Reference: {@link S3ProviderPutConfig}
*/
export type PutObjectInput = Pick<
PutObjectCommandInput,
| 'Bucket'
| 'Key'
| 'Body'
| 'ServerSideEncryption'
| 'SSECustomerAlgorithm'
| 'SSECustomerKey'
| 'SSECustomerKeyMD5'
| 'SSEKMSKeyId'
| 'ACL'
| 'CacheControl'
| 'ContentDisposition'
| 'ContentEncoding'
| 'ContentType'
| 'Expires'
| 'Metadata'
| 'Tagging'
>;

export type PutObjectOutput = Pick<
PutObjectCommandOutput,
// PutObject output is not exposed in public API, but only logged in the debug mode
// so we only expose $metadata, ETag and VersionId for debug purpose.
'$metadata' | 'ETag' | 'VersionId'
>;

const putObjectSerializer = (
input: PutObjectInput,
endpoint: Endpoint
): HttpRequest => {
const headers = serializeObjectConfigsToHeaders(input);
const url = new URL(endpoint.url.toString());
url.hostname = `${input.Bucket}.${url.hostname}`;
url.pathname = `/${input.Key}`;
return {
method: 'PUT',
headers,
url,
body: input.Body,
};
};

const putObjectDeserializer = async (
response: HttpResponse
): Promise<PutObjectOutput> => {
if (response.statusCode >= 300) {
const error = await parseXmlError(response);
throw error;
} else {
return {
...map(response.headers, {
ETag: 'etag',
VersionId: 'x-amz-version-id',
}),
$metadata: parseMetadata(response),
};
}
};

export const putObject = composeServiceApi(
s3TransferHandler,
putObjectSerializer,
putObjectDeserializer,
{ ...defaultConfig, responseType: 'text' }
);
Loading

0 comments on commit 9efe09a

Please sign in to comment.