diff --git a/packages/aws-amplify/package.json b/packages/aws-amplify/package.json index dfbe9c86727..042b46881be 100644 --- a/packages/aws-amplify/package.json +++ b/packages/aws-amplify/package.json @@ -479,7 +479,7 @@ "name": "[Storage] getUrl (S3)", "path": "./dist/esm/storage/index.mjs", "import": "{ getUrl }", - "limit": "14.50 kB" + "limit": "14.60 kB" }, { "name": "[Storage] list (S3)", diff --git a/packages/storage/__tests__/providers/s3/apis/getProperties.test.ts b/packages/storage/__tests__/providers/s3/apis/getProperties.test.ts index 6ee31b031ed..a2f0df8e886 100644 --- a/packages/storage/__tests__/providers/s3/apis/getProperties.test.ts +++ b/packages/storage/__tests__/providers/s3/apis/getProperties.test.ts @@ -5,7 +5,10 @@ import { headObject } from '../../../../src/providers/s3/utils/client'; import { getProperties } from '../../../../src/providers/s3'; import { AWSCredentials } from '@aws-amplify/core/internals/utils'; import { Amplify } from '@aws-amplify/core'; -import { GetPropertiesOptions } from '../../../../src/providers/s3/types'; +import { + GetPropertiesOptionsKey, + GetPropertiesOptionsPath, +} from '../../../../src/providers/s3/types'; jest.mock('../../../../src/providers/s3/utils/client'); jest.mock('@aws-amplify/core', () => ({ @@ -30,10 +33,12 @@ const credentials: AWSCredentials = { sessionToken: 'sessionToken', secretAccessKey: 'secretAccessKey', }; +const key = 'key'; +const path = 'path'; const targetIdentityId = 'targetIdentityId'; const defaultIdentityId = 'defaultIdentityId'; -describe('getProperties api', () => { +describe('getProperties with key', () => { beforeAll(() => { mockFetchAuthSession.mockResolvedValue({ credentials, @@ -48,9 +53,9 @@ describe('getProperties api', () => { }, }); }); - describe('getProperties happy path ', () => { + describe('Happy cases: With key', () => { const expected = { - key: 'key', + key, size: '100', contentType: 'text/plain', eTag: 'etag', @@ -63,7 +68,6 @@ describe('getProperties api', () => { region: 'region', userAgentValue: expect.any(String), }; - const key = 'key'; beforeEach(() => { mockHeadObject.mockReturnValueOnce({ ContentLength: '100', @@ -77,7 +81,7 @@ describe('getProperties api', () => { afterEach(() => { jest.clearAllMocks(); }); - [ + test.each([ { expectedKey: `public/${key}`, }, @@ -97,30 +101,133 @@ describe('getProperties api', () => { options: { accessLevel: 'protected', targetIdentityId }, expectedKey: `protected/${targetIdentityId}/${key}`, }, - ].forEach(({ options, expectedKey }) => { - const accessLevelMsg = options?.accessLevel ?? 'default'; - const targetIdentityIdMsg = options?.targetIdentityId - ? `and targetIdentityId` - : ''; - it(`should getProperties with ${accessLevelMsg} accessLevel ${targetIdentityIdMsg}`, async () => { + ])( + 'should getProperties with key $expectedKey', + async ({ options, expectedKey }) => { const headObjectOptions = { Bucket: 'bucket', Key: expectedKey, }; - expect.assertions(3); expect( await getProperties({ key, - options: options as GetPropertiesOptions, + options: options as GetPropertiesOptionsKey, }), ).toEqual(expected); expect(headObject).toHaveBeenCalledTimes(1); expect(headObject).toHaveBeenCalledWith(config, headObjectOptions); + }, + ); + }); + + describe('Error cases : With key', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + it('getProperties should return a not found error', async () => { + mockHeadObject.mockRejectedValueOnce( + Object.assign(new Error(), { + $metadata: { httpStatusCode: 404 }, + name: 'NotFound', + }), + ); + expect.assertions(3); + try { + await getProperties({ key }); + } catch (error: any) { + expect(headObject).toHaveBeenCalledTimes(1); + expect(headObject).toHaveBeenCalledWith( + { + credentials, + region: 'region', + userAgentValue: expect.any(String), + }, + { + Bucket: 'bucket', + Key: `public/${key}`, + }, + ); + expect(error.$metadata.httpStatusCode).toBe(404); + } + }); + }); +}); + +describe('Happy cases: With path', () => { + beforeAll(() => { + mockFetchAuthSession.mockResolvedValue({ + credentials, + identityId: defaultIdentityId, + }); + mockGetConfig.mockReturnValue({ + Storage: { + S3: { + bucket, + region, + }, + }, + }); + }); + describe('getProperties with path', () => { + const expected = { + path, + size: '100', + contentType: 'text/plain', + eTag: 'etag', + metadata: { key: 'value' }, + lastModified: 'last-modified', + versionId: 'version-id', + }; + const config = { + credentials, + region: 'region', + useAccelerateEndpoint: true, + userAgentValue: expect.any(String), + }; + beforeEach(() => { + mockHeadObject.mockReturnValueOnce({ + ContentLength: '100', + ContentType: 'text/plain', + ETag: 'etag', + LastModified: 'last-modified', + Metadata: { key: 'value' }, + VersionId: 'version-id', }); }); + afterEach(() => { + jest.clearAllMocks(); + }); + test.each([ + { + testPath: path, + expectedKey: path, + }, + { + testPath: () => path, + expectedKey: path, + }, + ])( + 'should getProperties with path $path and expectedKey $expectedKey', + async ({ testPath, expectedKey }) => { + const headObjectOptions = { + Bucket: 'bucket', + Key: expectedKey, + }; + expect( + await getProperties({ + path: testPath, + options: { + useAccelerateEndpoint: true, + } as GetPropertiesOptionsPath, + }), + ).toEqual(expected); + expect(headObject).toHaveBeenCalledTimes(1); + expect(headObject).toHaveBeenCalledWith(config, headObjectOptions); + }, + ); }); - describe('getProperties error path', () => { + describe('Error cases : With path', () => { afterEach(() => { jest.clearAllMocks(); }); @@ -133,7 +240,7 @@ describe('getProperties api', () => { ); expect.assertions(3); try { - await getProperties({ key: 'keyed' }); + await getProperties({ path }); } catch (error: any) { expect(headObject).toHaveBeenCalledTimes(1); expect(headObject).toHaveBeenCalledWith( @@ -144,7 +251,7 @@ describe('getProperties api', () => { }, { Bucket: 'bucket', - Key: 'public/keyed', + Key: path, }, ); expect(error.$metadata.httpStatusCode).toBe(404); diff --git a/packages/storage/__tests__/providers/s3/apis/getUrl.test.ts b/packages/storage/__tests__/providers/s3/apis/getUrl.test.ts index 1cdf1a59b72..28804743c7d 100644 --- a/packages/storage/__tests__/providers/s3/apis/getUrl.test.ts +++ b/packages/storage/__tests__/providers/s3/apis/getUrl.test.ts @@ -8,7 +8,10 @@ import { getPresignedGetObjectUrl, headObject, } from '../../../../src/providers/s3/utils/client'; -import { GetUrlOptions } from '../../../../src/providers/s3/types'; +import { + GetUrlOptionsKey, + GetUrlOptionsPath, +} from '../../../../src/providers/s3/types'; jest.mock('../../../../src/providers/s3/utils/client'); jest.mock('@aws-amplify/core', () => ({ @@ -35,7 +38,7 @@ const credentials: AWSCredentials = { const targetIdentityId = 'targetIdentityId'; const defaultIdentityId = 'defaultIdentityId'; -describe('getUrl test', () => { +describe('getUrl test with key', () => { beforeAll(() => { mockFetchAuthSession.mockResolvedValue({ credentials, @@ -51,7 +54,7 @@ describe('getUrl test', () => { }); }); - describe('getUrl happy path', () => { + describe('Happy cases: With key', () => { const config = { credentials, region, @@ -76,7 +79,7 @@ describe('getUrl test', () => { afterEach(() => { jest.clearAllMocks(); }); - [ + test.each([ { expectedKey: `public/${key}`, }, @@ -96,23 +99,19 @@ describe('getUrl test', () => { options: { accessLevel: 'protected', targetIdentityId }, expectedKey: `protected/${targetIdentityId}/${key}`, }, - ].forEach(({ options, expectedKey }) => { - const accessLevelMsg = options?.accessLevel ?? 'default'; - const targetIdentityIdMsg = options?.targetIdentityId - ? `and targetIdentityId` - : ''; - it(`should getUrl with ${accessLevelMsg} accessLevel ${targetIdentityIdMsg}`, async () => { + ])( + 'should getUrl with key $expectedKey', + async ({ options, expectedKey }) => { const headObjectOptions = { Bucket: bucket, Key: expectedKey, }; - expect.assertions(4); const result = await getUrl({ key, options: { ...options, validateObjectExistence: true, - } as GetUrlOptions, + } as GetUrlOptionsKey, }); expect(getPresignedGetObjectUrl).toHaveBeenCalledTimes(1); expect(headObject).toHaveBeenCalledTimes(1); @@ -120,10 +119,107 @@ describe('getUrl test', () => { expect(result.url).toEqual({ url: new URL('https://google.com'), }); + }, + ); + }); + describe('Error cases : With key', () => { + afterAll(() => { + jest.clearAllMocks(); + }); + it('should return not found error when the object is not found', async () => { + (headObject as jest.Mock).mockImplementation(() => { + throw Object.assign(new Error(), { + $metadata: { httpStatusCode: 404 }, + name: 'NotFound', + }); }); + expect.assertions(2); + try { + await getUrl({ + key: 'invalid_key', + options: { validateObjectExistence: true }, + }); + } catch (error: any) { + expect(headObject).toHaveBeenCalledTimes(1); + expect(error.$metadata?.httpStatusCode).toBe(404); + } }); }); - describe('getUrl error path', () => { +}); + +describe('getUrl test with path', () => { + beforeAll(() => { + mockFetchAuthSession.mockResolvedValue({ + credentials, + identityId: defaultIdentityId, + }); + mockGetConfig.mockReturnValue({ + Storage: { + S3: { + bucket, + region, + }, + }, + }); + }); + + describe('Happy cases: With path', () => { + const config = { + credentials, + region, + userAgentValue: expect.any(String), + }; + beforeEach(() => { + (headObject as jest.Mock).mockImplementation(() => { + return { + Key: 'path', + ContentLength: '100', + ContentType: 'text/plain', + ETag: 'etag', + LastModified: 'last-modified', + Metadata: { key: 'value' }, + }; + }); + (getPresignedGetObjectUrl as jest.Mock).mockReturnValueOnce({ + url: new URL('https://google.com'), + }); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + + test.each([ + { + path: 'path', + expectedKey: 'path', + }, + { + path: () => 'path', + expectedKey: 'path', + }, + ])( + 'should getUrl with path $path and expectedKey $expectedKey', + async ({ path, expectedKey }) => { + const headObjectOptions = { + Bucket: bucket, + Key: expectedKey, + }; + const result = await getUrl({ + path, + options: { + validateObjectExistence: true, + } as GetUrlOptionsPath, + }); + expect(getPresignedGetObjectUrl).toHaveBeenCalledTimes(1); + expect(headObject).toHaveBeenCalledTimes(1); + expect(headObject).toHaveBeenCalledWith(config, headObjectOptions); + expect(result.url).toEqual({ + url: new URL('https://google.com'), + }); + }, + ); + }); + describe('Error cases : With path', () => { afterAll(() => { jest.clearAllMocks(); }); @@ -137,7 +233,7 @@ describe('getUrl test', () => { expect.assertions(2); try { await getUrl({ - key: 'invalid_key', + path: 'invalid_key', options: { validateObjectExistence: true }, }); } catch (error: any) { diff --git a/packages/storage/src/providers/s3/apis/getProperties.ts b/packages/storage/src/providers/s3/apis/getProperties.ts index 4b98d529f55..227406bc369 100644 --- a/packages/storage/src/providers/s3/apis/getProperties.ts +++ b/packages/storage/src/providers/s3/apis/getProperties.ts @@ -3,22 +3,45 @@ import { Amplify } from '@aws-amplify/core'; -import { GetPropertiesInput, GetPropertiesOutput, S3Exception } from '../types'; -import { StorageValidationErrorCode } from '../../../errors/types/validation'; +import { + GetPropertiesInput, + GetPropertiesInputKey, + GetPropertiesInputPath, + GetPropertiesOutput, + GetPropertiesOutputKey, + GetPropertiesOutputPath, +} from '../types'; import { getProperties as getPropertiesInternal } from './internal/getProperties'; -/** - * Gets the properties of a file. The properties include S3 system metadata and - * the user metadata that was provided when uploading the file. - * - * @param input - The GetPropertiesInput object. - * @returns Requested object properties. - * @throws A {@link S3Exception} when the underlying S3 service returned error. - * @throws A {@link StorageValidationErrorCode} when API call parameters are invalid. - */ -export const getProperties = ( +interface GetProperties { + /** + * Gets the properties of a file. The properties include S3 system metadata and + * the user metadata that was provided when uploading the file. + * + * @param input - The `GetPropertiesInputPath` object. + * @returns Requested object properties. + * @throws An `S3Exception` when the underlying S3 service returned error. + * @throws A `StorageValidationErrorCode` when API call parameters are invalid. + */ + (input: GetPropertiesInputPath): 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/javascript/build-a-backend/storage/get-properties/ | path} instead. + * + * Gets the properties of a file. The properties include S3 system metadata and + * the user metadata that was provided when uploading the file. + * + * @param input - The `GetPropertiesInputKey` object. + * @returns Requested object properties. + * @throws An `S3Exception` when the underlying S3 service returned error. + * @throws A `StorageValidationErrorCode` when API call parameters are invalid. + */ + (input: GetPropertiesInputKey): Promise; +} + +export const getProperties: GetProperties = < + Output extends GetPropertiesOutput, +>( input: GetPropertiesInput, -): Promise => { - return getPropertiesInternal(Amplify, input); -}; +): Promise => getPropertiesInternal(Amplify, input) as Promise; diff --git a/packages/storage/src/providers/s3/apis/getUrl.ts b/packages/storage/src/providers/s3/apis/getUrl.ts index 708e601cc87..a49210063a3 100644 --- a/packages/storage/src/providers/s3/apis/getUrl.ts +++ b/packages/storage/src/providers/s3/apis/getUrl.ts @@ -3,28 +3,54 @@ import { Amplify } from '@aws-amplify/core'; -import { StorageValidationErrorCode } from '../../../errors/types/validation'; -import { GetUrlInput, GetUrlOutput, S3Exception } from '../types'; -import { StorageError } from '../../../errors/StorageError'; +import { + GetUrlInput, + GetUrlInputKey, + GetUrlInputPath, + GetUrlOutput, +} from '../types'; import { getUrl as getUrlInternal } from './internal/getUrl'; -/** - * Get a temporary presigned URL to download the specified S3 object. - * The presigned URL expires when the associated role used to sign the request expires or - * the option `expiresIn` is reached. The `expiresAt` property in the output object indicates when the URL MAY expire. - * - * By default, it will not validate the object that exists in S3. If you set the `options.validateObjectExistence` - * to true, this method will verify the given object already exists in S3 before returning a presigned - * URL, and will throw {@link StorageError} if the object does not exist. - * - * @param input - The GetUrlInput object. - * @returns Presigned URL and timestamp when the URL MAY expire. - * @throws service: {@link S3Exception} - thrown when checking for existence of the object - * @throws validation: {@link StorageValidationErrorCode } - Validation errors - * thrown either username or key are not defined. - * - */ -export const getUrl = (input: GetUrlInput): Promise => { - return getUrlInternal(Amplify, input); -}; +interface GetUrl { + /** + * Get a temporary presigned URL to download the specified S3 object. + * The presigned URL expires when the associated role used to sign the request expires or + * the option `expiresIn` is reached. The `expiresAt` property in the output object indicates when the URL MAY expire. + * + * By default, it will not validate the object that exists in S3. If you set the `options.validateObjectExistence` + * to true, this method will verify the given object already exists in S3 before returning a presigned + * URL, and will throw `StorageError` if the object does not exist. + * + * @param input - The `GetUrlInputPath` object. + * @returns Presigned URL and timestamp when the URL MAY expire. + * @throws service: `S3Exception` - thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Validation errors + * thrown either username or key are not defined. + * + */ + (input: GetUrlInputPath): 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/javascript/build-a-backend/storage/download/#generate-a-download-url | path} instead. + * + * Get a temporary presigned URL to download the specified S3 object. + * The presigned URL expires when the associated role used to sign the request expires or + * the option `expiresIn` is reached. The `expiresAt` property in the output object indicates when the URL MAY expire. + * + * By default, it will not validate the object that exists in S3. If you set the `options.validateObjectExistence` + * to true, this method will verify the given object already exists in S3 before returning a presigned + * URL, and will throw `StorageError` if the object does not exist. + * + * @param input - The `GetUrlInputKey` object. + * @returns Presigned URL and timestamp when the URL MAY expire. + * @throws service: `S3Exception` - thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Validation errors + * thrown either username or key are not defined. + * + */ + (input: GetUrlInputKey): Promise; +} + +export const getUrl: GetUrl = (input: GetUrlInput): Promise => + getUrlInternal(Amplify, input); diff --git a/packages/storage/src/providers/s3/apis/internal/getProperties.ts b/packages/storage/src/providers/s3/apis/internal/getProperties.ts index db854f8635b..e7f20187a36 100644 --- a/packages/storage/src/providers/s3/apis/internal/getProperties.ts +++ b/packages/storage/src/providers/s3/apis/internal/getProperties.ts @@ -5,24 +5,31 @@ import { AmplifyClassV6 } from '@aws-amplify/core'; import { StorageAction } from '@aws-amplify/core/internals/utils'; import { GetPropertiesInput, GetPropertiesOutput } from '../../types'; -import { resolveS3ConfigAndInput } from '../../utils'; +import { + resolveS3ConfigAndInput, + validateStorageOperationInput, +} from '../../utils'; import { headObject } from '../../utils/client'; import { getStorageUserAgentValue } from '../../utils/userAgent'; import { logger } from '../../../../utils'; +import { STORAGE_INPUT_KEY } from '../../utils/constants'; export const getProperties = async ( amplify: AmplifyClassV6, input: GetPropertiesInput, action?: StorageAction, ): Promise => { - const { key, options } = input; - const { s3Config, bucket, keyPrefix } = await resolveS3ConfigAndInput( - amplify, - options, + const { options: getPropertiesOptions } = input; + const { s3Config, bucket, keyPrefix, identityId } = + await resolveS3ConfigAndInput(amplify, getPropertiesOptions); + const { inputType, objectKey } = validateStorageOperationInput( + input, + identityId, ); - const finalKey = `${keyPrefix}${key}`; + const finalKey = + inputType === STORAGE_INPUT_KEY ? keyPrefix + objectKey : objectKey; - logger.debug(`get properties of ${key} from ${finalKey}`); + logger.debug(`get properties of ${objectKey} from ${finalKey}`); const response = await headObject( { ...s3Config, @@ -36,8 +43,7 @@ export const getProperties = async ( }, ); - return { - key, + const result = { contentType: response.ContentType, size: response.ContentLength, eTag: response.ETag, @@ -45,4 +51,8 @@ export const getProperties = async ( metadata: response.Metadata, versionId: response.VersionId, }; + + return inputType === STORAGE_INPUT_KEY + ? { key: objectKey, ...result } + : { path: objectKey, ...result }; }; diff --git a/packages/storage/src/providers/s3/apis/internal/getUrl.ts b/packages/storage/src/providers/s3/apis/internal/getUrl.ts index 15110d535b7..ea2fd60d741 100644 --- a/packages/storage/src/providers/s3/apis/internal/getUrl.ts +++ b/packages/storage/src/providers/s3/apis/internal/getUrl.ts @@ -7,11 +7,15 @@ import { StorageAction } from '@aws-amplify/core/internals/utils'; import { GetUrlInput, GetUrlOutput } from '../../types'; import { StorageValidationErrorCode } from '../../../../errors/types/validation'; import { getPresignedGetObjectUrl } from '../../utils/client'; -import { resolveS3ConfigAndInput } from '../../utils'; +import { + resolveS3ConfigAndInput, + validateStorageOperationInput, +} from '../../utils'; import { assertValidationError } from '../../../../errors/utils/assertValidationError'; import { DEFAULT_PRESIGN_EXPIRATION, MAX_URL_EXPIRATION, + STORAGE_INPUT_KEY, } from '../../utils/constants'; import { getProperties } from './getProperties'; @@ -20,18 +24,32 @@ export const getUrl = async ( amplify: AmplifyClassV6, input: GetUrlInput, ): Promise => { - const { key, options } = input; + const { options: getUrlOptions } = input; + const { s3Config, keyPrefix, bucket, identityId } = + await resolveS3ConfigAndInput(amplify, getUrlOptions); + const { inputType, objectKey } = validateStorageOperationInput( + input, + identityId, + ); - if (options?.validateObjectExistence) { - await getProperties(amplify, { key, options }, StorageAction.GetUrl); - } + const finalKey = + inputType === STORAGE_INPUT_KEY ? keyPrefix + objectKey : objectKey; - const { s3Config, keyPrefix, bucket } = await resolveS3ConfigAndInput( - amplify, - options, - ); + if (getUrlOptions?.validateObjectExistence) { + await getProperties( + amplify, + { + options: getUrlOptions, + ...((inputType === STORAGE_INPUT_KEY + ? { key: input.key } + : { path: input.path }) as GetUrlInput), + }, + StorageAction.GetUrl, + ); + } - let urlExpirationInSec = options?.expiresIn ?? DEFAULT_PRESIGN_EXPIRATION; + let urlExpirationInSec = + getUrlOptions?.expiresIn ?? DEFAULT_PRESIGN_EXPIRATION; const awsCredExpiration = s3Config.credentials?.expiration; if (awsCredExpiration) { const awsCredExpirationInSec = Math.floor( @@ -54,7 +72,7 @@ export const getUrl = async ( }, { Bucket: bucket, - Key: `${keyPrefix}${key}`, + Key: finalKey, }, ), expiresAt: new Date(Date.now() + urlExpirationInSec * 1000), diff --git a/packages/storage/src/providers/s3/apis/server/copy.ts b/packages/storage/src/providers/s3/apis/server/copy.ts index be7f6a3d012..308bd05a89a 100644 --- a/packages/storage/src/providers/s3/apis/server/copy.ts +++ b/packages/storage/src/providers/s3/apis/server/copy.ts @@ -19,6 +19,7 @@ interface Copy { /** * Copy an object from a source to a destination object within the same bucket. * + * @param contextSpec - The isolated server context. * @param input - The CopyInputPath object. * @returns Output containing the destination object path. * @throws service: `S3Exception` - Thrown when checking for existence of the object @@ -36,6 +37,7 @@ interface Copy { * 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 contextSpec - The isolated server context. * @param input - The CopyInputKey object. * @returns Output containing the destination object key. * @throws service: `S3Exception` - Thrown when checking for existence of the object diff --git a/packages/storage/src/providers/s3/apis/server/getProperties.ts b/packages/storage/src/providers/s3/apis/server/getProperties.ts index d56ee3d77f4..a3c7e0c254e 100644 --- a/packages/storage/src/providers/s3/apis/server/getProperties.ts +++ b/packages/storage/src/providers/s3/apis/server/getProperties.ts @@ -6,15 +6,57 @@ import { getAmplifyServerContext, } from '@aws-amplify/core/internals/adapter-core'; -import { GetPropertiesInput, GetPropertiesOutput } from '../../types'; +import { + GetPropertiesInput, + GetPropertiesInputKey, + GetPropertiesInputPath, + GetPropertiesOutput, + GetPropertiesOutputKey, + GetPropertiesOutputPath, +} from '../../types'; import { getProperties as getPropertiesInternal } from '../internal/getProperties'; -export const getProperties = ( +interface GetProperties { + /** + * Gets the properties of a file. The properties include S3 system metadata and + * the user metadata that was provided when uploading the file. + * + * @param contextSpec - The isolated server context. + * @param input - The `GetPropertiesInputPath` object. + * @returns Requested object properties. + * @throws An `S3Exception` when the underlying S3 service returned error. + * @throws A `StorageValidationErrorCode` when API call parameters are invalid. + */ + ( + contextSpec: AmplifyServer.ContextSpec, + input: GetPropertiesInputPath, + ): 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/javascript/build-a-backend/storage/get-properties/ | path} instead. + * + * Gets the properties of a file. The properties include S3 system metadata and + * the user metadata that was provided when uploading the file. + * + * @param contextSpec - The isolated server context. + * @param input - The `GetPropertiesInputKey` object. + * @returns Requested object properties. + * @throws An `S3Exception` when the underlying S3 service returned error. + * @throws A `StorageValidationErrorCode` when API call parameters are invalid. + */ + ( + contextSpec: AmplifyServer.ContextSpec, + input: GetPropertiesInputKey, + ): Promise; +} + +export const getProperties: GetProperties = < + Output extends GetPropertiesOutput, +>( contextSpec: AmplifyServer.ContextSpec, input: GetPropertiesInput, -): Promise => { - return getPropertiesInternal( +): Promise => + getPropertiesInternal( getAmplifyServerContext(contextSpec).amplify, input, - ); -}; + ) as Promise; diff --git a/packages/storage/src/providers/s3/apis/server/getUrl.ts b/packages/storage/src/providers/s3/apis/server/getUrl.ts index 8cdfe2ffb4e..7843b6323cf 100644 --- a/packages/storage/src/providers/s3/apis/server/getUrl.ts +++ b/packages/storage/src/providers/s3/apis/server/getUrl.ts @@ -6,12 +6,63 @@ import { getAmplifyServerContext, } from '@aws-amplify/core/internals/adapter-core'; -import { GetUrlInput, GetUrlOutput } from '../../types'; +import { + GetUrlInput, + GetUrlInputKey, + GetUrlInputPath, + GetUrlOutput, +} from '../../types'; import { getUrl as getUrlInternal } from '../internal/getUrl'; -export const getUrl = async ( +interface GetUrl { + /** + * Get a temporary presigned URL to download the specified S3 object. + * The presigned URL expires when the associated role used to sign the request expires or + * the option `expiresIn` is reached. The `expiresAt` property in the output object indicates when the URL MAY expire. + * + * By default, it will not validate the object that exists in S3. If you set the `options.validateObjectExistence` + * to true, this method will verify the given object already exists in S3 before returning a presigned + * URL, and will throw `StorageError` if the object does not exist. + * + * @param contextSpec - The isolated server context. + * @param input - The `GetUrlInputPath` object. + * @returns Presigned URL and timestamp when the URL MAY expire. + * @throws service: `S3Exception` - thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Validation errors + * thrown either username or key are not defined. + * + */ + ( + contextSpec: AmplifyServer.ContextSpec, + input: GetUrlInputPath, + ): 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/javascript/build-a-backend/storage/download/#generate-a-download-url | path} instead. + * + * Get a temporary presigned URL to download the specified S3 object. + * The presigned URL expires when the associated role used to sign the request expires or + * the option `expiresIn` is reached. The `expiresAt` property in the output object indicates when the URL MAY expire. + * + * By default, it will not validate the object that exists in S3. If you set the `options.validateObjectExistence` + * to true, this method will verify the given object already exists in S3 before returning a presigned + * URL, and will throw `StorageError` if the object does not exist. + * + * @param contextSpec - The isolated server context. + * @param input - The `GetUrlInputKey` object. + * @returns Presigned URL and timestamp when the URL MAY expire. + * @throws service: `S3Exception` - thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Validation errors + * thrown either username or key are not defined. + * + */ + ( + contextSpec: AmplifyServer.ContextSpec, + input: GetUrlInputKey, + ): Promise; +} +export const getUrl: GetUrl = async ( contextSpec: AmplifyServer.ContextSpec, input: GetUrlInput, -): Promise => { - return getUrlInternal(getAmplifyServerContext(contextSpec).amplify, input); -}; +): Promise => + getUrlInternal(getAmplifyServerContext(contextSpec).amplify, input); diff --git a/packages/storage/src/providers/s3/types/index.ts b/packages/storage/src/providers/s3/types/index.ts index 559bb111afa..e8306cd507f 100644 --- a/packages/storage/src/providers/s3/types/index.ts +++ b/packages/storage/src/providers/s3/types/index.ts @@ -2,9 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 export { - GetUrlOptions, + GetUrlOptionsKey, + GetUrlOptionsPath, UploadDataOptions, - GetPropertiesOptions, + GetPropertiesOptionsKey, + GetPropertiesOptionsPath, ListAllOptions, ListPaginateOptions, RemoveOptions, @@ -23,6 +25,8 @@ export { ListAllOutput, ListPaginateOutput, GetPropertiesOutput, + GetPropertiesOutputKey, + GetPropertiesOutputPath, CopyOutput, CopyOutputKey, CopyOutputPath, @@ -33,7 +37,11 @@ export { CopyInputKey, CopyInputPath, GetPropertiesInput, + GetPropertiesInputKey, + GetPropertiesInputPath, GetUrlInput, + GetUrlInputKey, + GetUrlInputPath, ListAllInput, ListPaginateInput, RemoveInput, diff --git a/packages/storage/src/providers/s3/types/inputs.ts b/packages/storage/src/providers/s3/types/inputs.ts index abdd54a841c..22f3cd73095 100644 --- a/packages/storage/src/providers/s3/types/inputs.ts +++ b/packages/storage/src/providers/s3/types/inputs.ts @@ -8,8 +8,10 @@ import { StorageCopyInputPath, StorageDownloadDataInputKey, StorageDownloadDataInputPath, - StorageGetPropertiesInput, - StorageGetUrlInput, + StorageGetPropertiesInputKey, + StorageGetPropertiesInputPath, + StorageGetUrlInputKey, + StorageGetUrlInputPath, StorageListInput, StorageRemoveInput, StorageUploadDataInput, @@ -19,8 +21,10 @@ import { CopySourceOptionsKey, DownloadDataOptionsKey, DownloadDataOptionsPath, - GetPropertiesOptions, - GetUrlOptions, + GetPropertiesOptionsKey, + GetPropertiesOptionsPath, + GetUrlOptionsKey, + GetUrlOptionsPath, ListAllOptions, ListPaginateOptions, RemoveOptions, @@ -43,13 +47,24 @@ export type CopyInputPath = StorageCopyInputPath; /** * Input type for S3 getProperties API. */ -export type GetPropertiesInput = - StorageGetPropertiesInput; +export type GetPropertiesInput = StrictUnion< + GetPropertiesInputKey | GetPropertiesInputPath +>; + +/** @deprecated Use {@link GetPropertiesInputPath} instead. */ +export type GetPropertiesInputKey = + StorageGetPropertiesInputKey; +export type GetPropertiesInputPath = + StorageGetPropertiesInputPath; /** * Input type for S3 getUrl API. */ -export type GetUrlInput = StorageGetUrlInput; +export type GetUrlInput = StrictUnion; + +/** @deprecated Use {@link GetUrlInputPath} instead. */ +export type GetUrlInputKey = StorageGetUrlInputKey; +export type GetUrlInputPath = StorageGetUrlInputPath; /** * Input type for S3 list API. Lists all bucket objects. diff --git a/packages/storage/src/providers/s3/types/options.ts b/packages/storage/src/providers/s3/types/options.ts index 7990f798aba..92f32ef0187 100644 --- a/packages/storage/src/providers/s3/types/options.ts +++ b/packages/storage/src/providers/s3/types/options.ts @@ -57,7 +57,9 @@ interface TransferOptions { /** * Input options type for S3 getProperties API. */ -export type GetPropertiesOptions = ReadOptions & CommonOptions; +/** @deprecated Use {@link GetPropertiesOptionsPath} instead. */ +export type GetPropertiesOptionsKey = ReadOptions & CommonOptions; +export type GetPropertiesOptionsPath = CommonOptions; /** * Input options type for S3 getProperties API. @@ -81,19 +83,22 @@ export type ListPaginateOptions = StorageListPaginateOptions & /** * Input options type for S3 getUrl API. */ -export type GetUrlOptions = ReadOptions & - CommonOptions & { - /** - * Whether to head object to make sure the object existence before downloading. - * @default false - */ - validateObjectExistence?: boolean; - /** - * Number of seconds till the URL expires. - * @default 900 (15 minutes) - */ - expiresIn?: number; - }; +export type GetUrlOptions = CommonOptions & { + /** + * Whether to head object to make sure the object existence before downloading. + * @default false + */ + validateObjectExistence?: boolean; + /** + * Number of seconds till the URL expires. + * @default 900 (15 minutes) + */ + expiresIn?: number; +}; + +/** @deprecated Use {@link GetUrlOptionsPath} instead. */ +export type GetUrlOptionsKey = ReadOptions & GetUrlOptions; +export type GetUrlOptionsPath = GetUrlOptions; /** * Input options type for S3 downloadData API. diff --git a/packages/storage/src/providers/s3/types/outputs.ts b/packages/storage/src/providers/s3/types/outputs.ts index c99f442f22a..d9aba46f502 100644 --- a/packages/storage/src/providers/s3/types/outputs.ts +++ b/packages/storage/src/providers/s3/types/outputs.ts @@ -61,10 +61,16 @@ export type GetUrlOutput = StorageGetUrlOutput; */ export type UploadDataOutput = UploadTask; +/** @deprecated Use {@link GetPropertiesOutputPath} instead. */ +export type GetPropertiesOutputKey = ItemKey; +export type GetPropertiesOutputPath = ItemPath; + /** * Output type for S3 getProperties API. */ -export type GetPropertiesOutput = ItemKey; +export type GetPropertiesOutput = + | GetPropertiesOutputKey + | GetPropertiesOutputPath; /** * Output type for S3 list API. Lists all bucket objects. diff --git a/packages/storage/src/types/index.ts b/packages/storage/src/types/index.ts index db2fac1628d..3722b7df804 100644 --- a/packages/storage/src/types/index.ts +++ b/packages/storage/src/types/index.ts @@ -10,14 +10,16 @@ export { export { StorageOperationInput, StorageListInput, - StorageGetPropertiesInput, + StorageGetPropertiesInputKey, + StorageGetPropertiesInputPath, StorageRemoveInput, StorageDownloadDataInputKey, StorageDownloadDataInputPath, StorageUploadDataInput, StorageCopyInputKey, StorageCopyInputPath, - StorageGetUrlInput, + StorageGetUrlInputKey, + StorageGetUrlInputPath, StorageUploadDataPayload, } from './inputs'; export { diff --git a/packages/storage/src/types/inputs.ts b/packages/storage/src/types/inputs.ts index 57fd33b7c45..117b11aba19 100644 --- a/packages/storage/src/types/inputs.ts +++ b/packages/storage/src/types/inputs.ts @@ -40,8 +40,12 @@ export interface StorageOperationInput { options?: Options; } -export type StorageGetPropertiesInput = - StorageOperationInput; +/** @deprecated Use {@link StorageGetPropertiesInputPath} instead. */ +export type StorageGetPropertiesInputKey = + StorageOperationInputKey & StorageOperationInput; + +export type StorageGetPropertiesInputPath = StorageOperationInputPath & + StorageOperationOptionsInput; export interface StorageRemoveInput { key: string; @@ -55,8 +59,12 @@ export interface StorageListInput< options?: Options; } -export type StorageGetUrlInput = - StorageOperationInput; +/** @deprecated Use {@link StorageGetUrlInputPath} instead. */ +export type StorageGetUrlInputKey = + StorageOperationInputKey & StorageOperationInput; + +export type StorageGetUrlInputPath = StorageOperationInputPath & + StorageOperationOptionsInput; export type StorageUploadDataInput = StorageOperationInput & {