diff --git a/packages/aws-amplify/package.json b/packages/aws-amplify/package.json index 9b0a0c825b5..dfbe9c86727 100644 --- a/packages/aws-amplify/package.json +++ b/packages/aws-amplify/package.json @@ -461,13 +461,13 @@ "name": "[Storage] copy (S3)", "path": "./dist/esm/storage/index.mjs", "import": "{ copy }", - "limit": "13.50 kB" + "limit": "13.61 kB" }, { "name": "[Storage] downloadData (S3)", "path": "./dist/esm/storage/index.mjs", "import": "{ downloadData }", - "limit": "14.20 kB" + "limit": "14.23 kB" }, { "name": "[Storage] getProperties (S3)", diff --git a/packages/storage/__tests__/providers/s3/apis/copy.test.ts b/packages/storage/__tests__/providers/s3/apis/copy.test.ts index e7c61ef8a3a..7e951c9ec09 100644 --- a/packages/storage/__tests__/providers/s3/apis/copy.test.ts +++ b/packages/storage/__tests__/providers/s3/apis/copy.test.ts @@ -3,11 +3,13 @@ import { AWSCredentials } from '@aws-amplify/core/internals/utils'; import { Amplify } from '@aws-amplify/core'; +import { StorageError } from '../../../../src/errors/StorageError'; +import { StorageValidationErrorCode } from '../../../../src/errors/types/validation'; import { copyObject } from '../../../../src/providers/s3/utils/client'; import { copy } from '../../../../src/providers/s3/apis'; import { - CopySourceOptions, - CopyDestinationOptions, + CopySourceOptionsKey, + CopyDestinationOptionsKey, } from '../../../../src/providers/s3/types'; jest.mock('../../../../src/providers/s3/utils/client'); @@ -63,121 +65,177 @@ describe('copy API', () => { }, }); }); - describe('Happy Path Cases:', () => { - beforeEach(() => { - mockCopyObject.mockImplementation(() => { - return { - Metadata: { key: 'value' }, - }; + + describe('Happy Cases', () => { + describe('With key', () => { + beforeEach(() => { + mockCopyObject.mockImplementation(() => { + return { + Metadata: { key: 'value' }, + }; + }); }); + afterEach(() => { + jest.clearAllMocks(); + }); + [ + { + source: { accessLevel: 'guest' }, + destination: { accessLevel: 'guest' }, + expectedSourceKey: `${bucket}/public/${sourceKey}`, + expectedDestinationKey: `public/${destinationKey}`, + }, + { + source: { accessLevel: 'guest' }, + destination: { accessLevel: 'private' }, + expectedSourceKey: `${bucket}/public/${sourceKey}`, + expectedDestinationKey: `private/${defaultIdentityId}/${destinationKey}`, + }, + { + source: { accessLevel: 'guest' }, + destination: { accessLevel: 'protected' }, + expectedSourceKey: `${bucket}/public/${sourceKey}`, + expectedDestinationKey: `protected/${defaultIdentityId}/${destinationKey}`, + }, + { + source: { accessLevel: 'private' }, + destination: { accessLevel: 'guest' }, + expectedSourceKey: `${bucket}/private/${defaultIdentityId}/${sourceKey}`, + expectedDestinationKey: `public/${destinationKey}`, + }, + { + source: { accessLevel: 'private' }, + destination: { accessLevel: 'private' }, + expectedSourceKey: `${bucket}/private/${defaultIdentityId}/${sourceKey}`, + expectedDestinationKey: `private/${defaultIdentityId}/${destinationKey}`, + }, + { + source: { accessLevel: 'private' }, + destination: { accessLevel: 'protected' }, + expectedSourceKey: `${bucket}/private/${defaultIdentityId}/${sourceKey}`, + expectedDestinationKey: `protected/${defaultIdentityId}/${destinationKey}`, + }, + { + source: { accessLevel: 'protected' }, + destination: { accessLevel: 'guest' }, + expectedSourceKey: `${bucket}/protected/${defaultIdentityId}/${sourceKey}`, + expectedDestinationKey: `public/${destinationKey}`, + }, + { + source: { accessLevel: 'protected' }, + destination: { accessLevel: 'private' }, + expectedSourceKey: `${bucket}/protected/${defaultIdentityId}/${sourceKey}`, + expectedDestinationKey: `private/${defaultIdentityId}/${destinationKey}`, + }, + { + source: { accessLevel: 'protected' }, + destination: { accessLevel: 'protected' }, + expectedSourceKey: `${bucket}/protected/${defaultIdentityId}/${sourceKey}`, + expectedDestinationKey: `protected/${defaultIdentityId}/${destinationKey}`, + }, + { + source: { accessLevel: 'protected', targetIdentityId }, + destination: { accessLevel: 'guest' }, + expectedSourceKey: `${bucket}/protected/${targetIdentityId}/${sourceKey}`, + expectedDestinationKey: `public/${destinationKey}`, + }, + { + source: { accessLevel: 'protected', targetIdentityId }, + destination: { accessLevel: 'private' }, + expectedSourceKey: `${bucket}/protected/${targetIdentityId}/${sourceKey}`, + expectedDestinationKey: `private/${defaultIdentityId}/${destinationKey}`, + }, + { + source: { accessLevel: 'protected', targetIdentityId }, + destination: { accessLevel: 'protected' }, + expectedSourceKey: `${bucket}/protected/${targetIdentityId}/${sourceKey}`, + expectedDestinationKey: `protected/${defaultIdentityId}/${destinationKey}`, + }, + ].forEach( + ({ + source, + destination, + expectedSourceKey, + expectedDestinationKey, + }) => { + const targetIdentityIdMsg = source?.targetIdentityId + ? `with targetIdentityId` + : ''; + it(`should copy ${source.accessLevel} ${targetIdentityIdMsg} -> ${destination.accessLevel}`, async () => { + expect( + await copy({ + source: { + ...(source as CopySourceOptionsKey), + key: sourceKey, + }, + destination: { + ...(destination as CopyDestinationOptionsKey), + key: destinationKey, + }, + }), + ).toEqual(copyResult); + expect(copyObject).toHaveBeenCalledTimes(1); + expect(copyObject).toHaveBeenCalledWith(copyObjectClientConfig, { + ...copyObjectClientBaseParams, + CopySource: expectedSourceKey, + Key: expectedDestinationKey, + }); + }); + }, + ); }); - afterEach(() => { - jest.clearAllMocks(); - }); - [ - { - source: { accessLevel: 'guest' }, - destination: { accessLevel: 'guest' }, - expectedSourceKey: `${bucket}/public/${sourceKey}`, - expectedDestinationKey: `public/${destinationKey}`, - }, - { - source: { accessLevel: 'guest' }, - destination: { accessLevel: 'private' }, - expectedSourceKey: `${bucket}/public/${sourceKey}`, - expectedDestinationKey: `private/${defaultIdentityId}/${destinationKey}`, - }, - { - source: { accessLevel: 'guest' }, - destination: { accessLevel: 'protected' }, - expectedSourceKey: `${bucket}/public/${sourceKey}`, - expectedDestinationKey: `protected/${defaultIdentityId}/${destinationKey}`, - }, - { - source: { accessLevel: 'private' }, - destination: { accessLevel: 'guest' }, - expectedSourceKey: `${bucket}/private/${defaultIdentityId}/${sourceKey}`, - expectedDestinationKey: `public/${destinationKey}`, - }, - { - source: { accessLevel: 'private' }, - destination: { accessLevel: 'private' }, - expectedSourceKey: `${bucket}/private/${defaultIdentityId}/${sourceKey}`, - expectedDestinationKey: `private/${defaultIdentityId}/${destinationKey}`, - }, - { - source: { accessLevel: 'private' }, - destination: { accessLevel: 'protected' }, - expectedSourceKey: `${bucket}/private/${defaultIdentityId}/${sourceKey}`, - expectedDestinationKey: `protected/${defaultIdentityId}/${destinationKey}`, - }, - { - source: { accessLevel: 'protected' }, - destination: { accessLevel: 'guest' }, - expectedSourceKey: `${bucket}/protected/${defaultIdentityId}/${sourceKey}`, - expectedDestinationKey: `public/${destinationKey}`, - }, - { - source: { accessLevel: 'protected' }, - destination: { accessLevel: 'private' }, - expectedSourceKey: `${bucket}/protected/${defaultIdentityId}/${sourceKey}`, - expectedDestinationKey: `private/${defaultIdentityId}/${destinationKey}`, - }, - { - source: { accessLevel: 'protected' }, - destination: { accessLevel: 'protected' }, - expectedSourceKey: `${bucket}/protected/${defaultIdentityId}/${sourceKey}`, - expectedDestinationKey: `protected/${defaultIdentityId}/${destinationKey}`, - }, - { - source: { accessLevel: 'protected', targetIdentityId }, - destination: { accessLevel: 'guest' }, - expectedSourceKey: `${bucket}/protected/${targetIdentityId}/${sourceKey}`, - expectedDestinationKey: `public/${destinationKey}`, - }, - { - source: { accessLevel: 'protected', targetIdentityId }, - destination: { accessLevel: 'private' }, - expectedSourceKey: `${bucket}/protected/${targetIdentityId}/${sourceKey}`, - expectedDestinationKey: `private/${defaultIdentityId}/${destinationKey}`, - }, - { - source: { accessLevel: 'protected', targetIdentityId }, - destination: { accessLevel: 'protected' }, - expectedSourceKey: `${bucket}/protected/${targetIdentityId}/${sourceKey}`, - expectedDestinationKey: `protected/${defaultIdentityId}/${destinationKey}`, - }, - ].forEach( - ({ source, destination, expectedSourceKey, expectedDestinationKey }) => { - const targetIdentityIdMsg = source?.targetIdentityId - ? `with targetIdentityId` - : ''; - it(`should copy ${source.accessLevel} ${targetIdentityIdMsg} -> ${destination.accessLevel}`, async () => { - expect.assertions(3); + + describe('With path', () => { + beforeEach(() => { + mockCopyObject.mockImplementation(() => { + return { + Metadata: { key: 'value' }, + }; + }); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + + test.each([ + { + sourcePath: '/sourcePathAsString', + expectedSourcePath: 'sourcePathAsString', + destinationPath: '/destinationPathAsString', + expectedDestinationPath: 'destinationPathAsString', + }, + { + sourcePath: () => '/sourcePathAsFunction', + expectedSourcePath: 'sourcePathAsFunction', + destinationPath: () => '/destinationPathAsFunction', + expectedDestinationPath: 'destinationPathAsFunction', + }, + ])( + 'should copy $sourcePath -> $destinationPath', + async ({ + sourcePath, + expectedSourcePath, + destinationPath, + expectedDestinationPath, + }) => { expect( await copy({ - source: { - ...(source as CopySourceOptions), - key: sourceKey, - }, - destination: { - ...(destination as CopyDestinationOptions), - key: destinationKey, - }, + source: { path: sourcePath }, + destination: { path: destinationPath }, }), - ).toEqual(copyResult); + ).toEqual({ path: expectedDestinationPath }); expect(copyObject).toHaveBeenCalledTimes(1); expect(copyObject).toHaveBeenCalledWith(copyObjectClientConfig, { ...copyObjectClientBaseParams, - CopySource: expectedSourceKey, - Key: expectedDestinationKey, + CopySource: `${bucket}/${expectedSourcePath}`, + Key: expectedDestinationPath, }); - }); - }, - ); + }, + ); + }); }); - describe('Error Path Cases:', () => { + describe('Error Cases:', () => { afterEach(() => { jest.clearAllMocks(); }); @@ -206,5 +264,34 @@ describe('copy API', () => { expect(error.$metadata.httpStatusCode).toBe(404); } }); + + it('should return a path not found error when source uses path and destination uses key', async () => { + expect.assertions(2); + try { + // @ts-expect-error + await copy({ + source: { path: 'sourcePath' }, + destination: { key: 'destinationKey' }, + }); + } catch (error: any) { + expect(error).toBeInstanceOf(StorageError); + // source uses path so destination expects path as well + expect(error.name).toBe(StorageValidationErrorCode.NoDestinationPath); + } + }); + + it('should return a key not found error when source uses key and destination uses path', async () => { + expect.assertions(2); + try { + // @ts-expect-error + await copy({ + source: { key: 'sourcePath' }, + destination: { path: 'destinationKey' }, + }); + } catch (error: any) { + expect(error).toBeInstanceOf(StorageError); + expect(error.name).toBe(StorageValidationErrorCode.NoDestinationKey); + } + }); }); }); diff --git a/packages/storage/src/errors/types/validation.ts b/packages/storage/src/errors/types/validation.ts index a0ea179ce58..c56299878c2 100644 --- a/packages/storage/src/errors/types/validation.ts +++ b/packages/storage/src/errors/types/validation.ts @@ -9,6 +9,8 @@ export enum StorageValidationErrorCode { NoKey = 'NoKey', NoSourceKey = 'NoSourceKey', NoDestinationKey = 'NoDestinationKey', + NoSourcePath = 'NoSourcePath', + NoDestinationPath = 'NoDestinationPath', NoBucket = 'NoBucket', NoRegion = 'NoRegion', InvalidStorageOperationInput = 'InvalidStorageOperationInput', @@ -35,6 +37,12 @@ export const validationErrorMap: AmplifyErrorMap = { [StorageValidationErrorCode.NoDestinationKey]: { message: 'Missing destination key in copy api call.', }, + [StorageValidationErrorCode.NoSourcePath]: { + message: 'Missing source path in copy api call.', + }, + [StorageValidationErrorCode.NoDestinationPath]: { + message: 'Missing destination path in copy api call.', + }, [StorageValidationErrorCode.NoBucket]: { message: 'Missing bucket name while accessing object.', }, diff --git a/packages/storage/src/providers/s3/apis/copy.ts b/packages/storage/src/providers/s3/apis/copy.ts index ca0ae3e8a39..1978d9595b3 100644 --- a/packages/storage/src/providers/s3/apis/copy.ts +++ b/packages/storage/src/providers/s3/apis/copy.ts @@ -3,21 +3,44 @@ import { Amplify } from '@aws-amplify/core'; -import { CopyInput, CopyOutput, S3Exception } from '../types'; -import { StorageValidationErrorCode } from '../../../errors/types/validation'; +import { + CopyInput, + CopyInputKey, + CopyInputPath, + CopyOutput, + CopyOutputKey, + CopyOutputPath, +} from '../types'; import { copy as copyInternal } from './internal/copy'; -/** - * Copy an object from a source object to a new object within the same bucket. Can optionally copy files across - * different level or identityId (if source object's level is 'protected'). - * - * @param input - The CopyInput object. - * @returns Output containing the destination key. - * @throws service: {@link S3Exception} - Thrown when checking for existence of the object - * @throws validation: {@link StorageValidationErrorCode } - Thrown when - * source or destination key are not defined. - */ -export const copy = async (input: CopyInput): Promise => { - return copyInternal(Amplify, input); -}; +interface Copy { + /** + * Copy an object from a source to a destination object within the same bucket. + * + * @param input - The CopyInputPath object. + * @returns Output containing the destination object path. + * @throws service: `S3Exception` - Thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Thrown when + * source or destination path is not defined. + */ + (input: CopyInputPath): Promise; + /** + * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. + * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/copy | path} instead. + * + * Copy an object from a source to a destination object within the same bucket. Can optionally copy files across + * different accessLevel or identityId (if source object's accessLevel is 'protected'). + * + * @param input - The CopyInputKey object. + * @returns Output containing the destination object key. + * @throws service: `S3Exception` - Thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Thrown when + * source or destination key is not defined. + */ + (input: CopyInputKey): Promise; +} + +export const copy: Copy = ( + input: CopyInput, +): Promise => copyInternal(Amplify, input) as Promise; diff --git a/packages/storage/src/providers/s3/apis/internal/copy.ts b/packages/storage/src/providers/s3/apis/internal/copy.ts index feefdd1b5c3..26576323b86 100644 --- a/packages/storage/src/providers/s3/apis/internal/copy.ts +++ b/packages/storage/src/providers/s3/apis/internal/copy.ts @@ -4,18 +4,80 @@ import { AmplifyClassV6 } from '@aws-amplify/core'; import { StorageAction } from '@aws-amplify/core/internals/utils'; -import { CopyInput, CopyOutput } from '../../types'; -import { resolveS3ConfigAndInput } from '../../utils'; +import { + CopyInput, + CopyInputKey, + CopyInputPath, + CopyOutput, + CopyOutputKey, + CopyOutputPath, +} from '../../types'; +import { ResolvedS3Config } from '../../types/options'; +import { + isInputWithPath, + resolveS3ConfigAndInput, + validateStorageOperationInput, +} from '../../utils'; import { StorageValidationErrorCode } from '../../../../errors/types/validation'; import { assertValidationError } from '../../../../errors/utils/assertValidationError'; import { copyObject } from '../../utils/client'; import { getStorageUserAgentValue } from '../../utils/userAgent'; import { logger } from '../../../../utils'; +const isCopyInputWithPath = (input: CopyInput): input is CopyInputPath => + isInputWithPath(input.source); + export const copy = async ( amplify: AmplifyClassV6, input: CopyInput, ): Promise => { + return isCopyInputWithPath(input) + ? copyWithPath(amplify, input) + : copyWithKey(amplify, input); +}; + +const copyWithPath = async ( + amplify: AmplifyClassV6, + input: CopyInputPath, +): Promise => { + const { source, destination } = input; + const { s3Config, bucket, identityId } = + await resolveS3ConfigAndInput(amplify); + + assertValidationError(!!source.path, StorageValidationErrorCode.NoSourcePath); + assertValidationError( + !!destination.path, + StorageValidationErrorCode.NoDestinationPath, + ); + + const { objectKey: sourcePath } = validateStorageOperationInput( + source, + identityId, + ); + const { objectKey: destinationPath } = validateStorageOperationInput( + destination, + identityId, + ); + + const finalCopySource = `${bucket}/${sourcePath}`; + const finalCopyDestination = destinationPath; + logger.debug(`copying "${finalCopySource}" to "${finalCopyDestination}".`); + + await serviceCopy({ + source: finalCopySource, + destination: finalCopyDestination, + bucket, + s3Config, + }); + + return { path: finalCopyDestination }; +}; + +/** @deprecated Use {@link copyWithPath} instead. */ +export const copyWithKey = async ( + amplify: AmplifyClassV6, + input: CopyInputKey, +): Promise => { const { source: { key: sourceKey }, destination: { key: destinationKey }, @@ -41,6 +103,30 @@ export const copy = async ( const finalCopySource = `${bucket}/${sourceKeyPrefix}${sourceKey}`; const finalCopyDestination = `${destinationKeyPrefix}${destinationKey}`; logger.debug(`copying "${finalCopySource}" to "${finalCopyDestination}".`); + + await serviceCopy({ + source: finalCopySource, + destination: finalCopyDestination, + bucket, + s3Config, + }); + + return { + key: destinationKey, + }; +}; + +const serviceCopy = async ({ + source, + destination, + bucket, + s3Config, +}: { + source: string; + destination: string; + bucket: string; + s3Config: ResolvedS3Config; +}) => { await copyObject( { ...s3Config, @@ -48,13 +134,9 @@ export const copy = async ( }, { Bucket: bucket, - CopySource: finalCopySource, - Key: finalCopyDestination, + CopySource: source, + Key: destination, MetadataDirective: 'COPY', // Copies over metadata like contentType as well }, ); - - return { - key: destinationKey, - }; }; diff --git a/packages/storage/src/providers/s3/apis/server/copy.ts b/packages/storage/src/providers/s3/apis/server/copy.ts index 8a4f64d272d..be7f6a3d012 100644 --- a/packages/storage/src/providers/s3/apis/server/copy.ts +++ b/packages/storage/src/providers/s3/apis/server/copy.ts @@ -1,17 +1,58 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 - import { AmplifyServer, getAmplifyServerContext, } from '@aws-amplify/core/internals/adapter-core'; -import { CopyInput, CopyOutput } from '../../types'; +import { + CopyInput, + CopyInputKey, + CopyInputPath, + CopyOutput, + CopyOutputKey, + CopyOutputPath, +} from '../../types'; import { copy as copyInternal } from '../internal/copy'; -export const copy = async ( +interface Copy { + /** + * Copy an object from a source to a destination object within the same bucket. + * + * @param input - The CopyInputPath object. + * @returns Output containing the destination object path. + * @throws service: `S3Exception` - Thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Thrown when + * source or destination path is not defined. + */ + ( + contextSpec: AmplifyServer.ContextSpec, + input: CopyInputPath, + ): Promise; + /** + * @deprecated The `key` and `accessLevel` parameters are deprecated and may be removed in the next major version. + * Please use {@link https://docs.amplify.aws/react/build-a-backend/storage/copy | path} instead. + * + * Copy an object from a source to a destination object within the same bucket. Can optionally copy files across + * different accessLevel or identityId (if source object's accessLevel is 'protected'). + * + * @param input - The CopyInputKey object. + * @returns Output containing the destination object key. + * @throws service: `S3Exception` - Thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Thrown when + * source or destination key is not defined. + */ + ( + contextSpec: AmplifyServer.ContextSpec, + input: CopyInputKey, + ): Promise; +} + +export const copy: Copy = ( contextSpec: AmplifyServer.ContextSpec, input: CopyInput, -): Promise => { - return copyInternal(getAmplifyServerContext(contextSpec).amplify, input); -}; +): Promise => + copyInternal( + getAmplifyServerContext(contextSpec).amplify, + input, + ) as Promise; diff --git a/packages/storage/src/providers/s3/types/index.ts b/packages/storage/src/providers/s3/types/index.ts index 773dde39361..559bb111afa 100644 --- a/packages/storage/src/providers/s3/types/index.ts +++ b/packages/storage/src/providers/s3/types/index.ts @@ -10,8 +10,8 @@ export { RemoveOptions, DownloadDataOptionsPath, DownloadDataOptionsKey, - CopyDestinationOptions, - CopySourceOptions, + CopyDestinationOptionsKey, + CopySourceOptionsKey, } from './options'; export { DownloadDataOutput, @@ -24,10 +24,14 @@ export { ListPaginateOutput, GetPropertiesOutput, CopyOutput, + CopyOutputKey, + CopyOutputPath, RemoveOutput, } from './outputs'; export { CopyInput, + CopyInputKey, + CopyInputPath, GetPropertiesInput, GetUrlInput, ListAllInput, diff --git a/packages/storage/src/providers/s3/types/inputs.ts b/packages/storage/src/providers/s3/types/inputs.ts index 3ec41b7d47a..abdd54a841c 100644 --- a/packages/storage/src/providers/s3/types/inputs.ts +++ b/packages/storage/src/providers/s3/types/inputs.ts @@ -4,7 +4,8 @@ import { StrictUnion } from '@aws-amplify/core/internals/utils'; import { - StorageCopyInput, + StorageCopyInputKey, + StorageCopyInputPath, StorageDownloadDataInputKey, StorageDownloadDataInputPath, StorageGetPropertiesInput, @@ -14,8 +15,8 @@ import { StorageUploadDataInput, } from '../../../types'; import { - CopyDestinationOptions, - CopySourceOptions, + CopyDestinationOptionsKey, + CopySourceOptionsKey, DownloadDataOptionsKey, DownloadDataOptionsPath, GetPropertiesOptions, @@ -30,10 +31,14 @@ import { /** * Input type for S3 copy API. */ -export type CopyInput = StorageCopyInput< - CopySourceOptions, - CopyDestinationOptions +export type CopyInput = CopyInputKey | CopyInputPath; + +/** @deprecated Use {@link CopyInputPath} instead. */ +export type CopyInputKey = StorageCopyInputKey< + CopySourceOptionsKey, + CopyDestinationOptionsKey >; +export type CopyInputPath = StorageCopyInputPath; /** * Input type for S3 getProperties API. diff --git a/packages/storage/src/providers/s3/types/options.ts b/packages/storage/src/providers/s3/types/options.ts index 6356ca81a93..5de24ebd4c9 100644 --- a/packages/storage/src/providers/s3/types/options.ts +++ b/packages/storage/src/providers/s3/types/options.ts @@ -123,11 +123,15 @@ export type UploadDataOptions = WriteOptions & metadata?: Record; }; -export type CopySourceOptions = ReadOptions & { +/** @deprecated This may be removed in the next major version. */ +export type CopySourceOptionsKey = ReadOptions & { + /** @deprecated This may be removed in the next major version. */ key: string; }; -export type CopyDestinationOptions = WriteOptions & { +/** @deprecated This may be removed in the next major version. */ +export type CopyDestinationOptionsKey = WriteOptions & { + /** @deprecated This may be removed in the next major version. */ key: string; }; diff --git a/packages/storage/src/providers/s3/types/outputs.ts b/packages/storage/src/providers/s3/types/outputs.ts index 35b6784af93..c99f442f22a 100644 --- a/packages/storage/src/providers/s3/types/outputs.ts +++ b/packages/storage/src/providers/s3/types/outputs.ts @@ -1,6 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import { StrictUnion } from '@aws-amplify/core/internals/utils'; + import { DownloadTask, StorageDownloadDataOutput, @@ -77,9 +79,12 @@ export type ListPaginateOutput = StorageListOutput & { }; /** - * Output type for S3 copy API. + * @deprecated Use {@link CopyOutputPath} instead. */ -export type CopyOutput = Pick; +export type CopyOutputKey = Pick; +export type CopyOutputPath = Pick; + +export type CopyOutput = StrictUnion; /** * Output type for S3 remove API. diff --git a/packages/storage/src/types/index.ts b/packages/storage/src/types/index.ts index 78569a76f36..db2fac1628d 100644 --- a/packages/storage/src/types/index.ts +++ b/packages/storage/src/types/index.ts @@ -15,7 +15,8 @@ export { StorageDownloadDataInputKey, StorageDownloadDataInputPath, StorageUploadDataInput, - StorageCopyInput, + StorageCopyInputKey, + StorageCopyInputPath, StorageGetUrlInput, StorageUploadDataPayload, } from './inputs'; diff --git a/packages/storage/src/types/inputs.ts b/packages/storage/src/types/inputs.ts index 0514feaba30..57fd33b7c45 100644 --- a/packages/storage/src/types/inputs.ts +++ b/packages/storage/src/types/inputs.ts @@ -63,12 +63,28 @@ export type StorageUploadDataInput = data: StorageUploadDataPayload; }; -export interface StorageCopyInput< +/** @deprecated Use {@link StorageCopyInputPath} instead. */ +export interface StorageCopyInputKey< SourceOptions extends StorageOptions, DestinationOptions extends StorageOptions, > { - source: SourceOptions; - destination: DestinationOptions; + source: SourceOptions & { + path?: never; + }; + destination: DestinationOptions & { + path?: never; + }; +} + +export interface StorageCopyInputPath { + source: StorageOperationInputPath & { + /** @deprecated Use path instead. */ + key?: never; + }; + destination: StorageOperationInputPath & { + /** @deprecated Use path instead. */ + key?: never; + }; } /**