From 9e015a8d572067872ce53a04495a2a88e5b51d10 Mon Sep 17 00:00:00 2001 From: Ashwin Kumar Date: Mon, 23 Sep 2024 16:48:56 -0700 Subject: [PATCH] refactor: breakup serializeHelpers and deserializeHelpers into individual files --- .../buildStorageServiceError.ts | 27 +++ .../createStringEnumDeserializer.ts | 45 ++++ .../s3/deserialization/deserializeBoolean.ts | 12 + .../s3/deserialization/deserializeMetadata.ts | 22 ++ .../s3/deserialization/deserializeNumber.ts | 10 + .../deserialization/deserializeTimestamp.ts | 19 ++ .../s3/deserialization/emptyArrayGuard.ts | 21 ++ .../s3/deserialization/index.ts | 12 + .../serviceClients/s3/deserialization/map.ts | 67 ++++++ .../serviceClients/s3/deserializeHelpers.ts | 205 ------------------ .../serde/CreateDeleteObjectDeserializer.ts | 2 +- .../serde/CreateDeleteObjectSerializer.ts | 2 +- .../s3/serialization/assignStringVariables.ts | 18 ++ .../serviceClients/s3/serialization/index.ts | 8 + .../serializeObjectConfigsToHeaders.ts | 46 ++++ .../serializePathnameObjectKey.ts | 17 ++ .../validateS3RequiredParameter.ts | 23 ++ .../serviceClients/s3/serializeHelpers.ts | 98 --------- 18 files changed, 349 insertions(+), 305 deletions(-) create mode 100644 packages/storage/src/foundation/factories/serviceClients/s3/deserialization/buildStorageServiceError.ts create mode 100644 packages/storage/src/foundation/factories/serviceClients/s3/deserialization/createStringEnumDeserializer.ts create mode 100644 packages/storage/src/foundation/factories/serviceClients/s3/deserialization/deserializeBoolean.ts create mode 100644 packages/storage/src/foundation/factories/serviceClients/s3/deserialization/deserializeMetadata.ts create mode 100644 packages/storage/src/foundation/factories/serviceClients/s3/deserialization/deserializeNumber.ts create mode 100644 packages/storage/src/foundation/factories/serviceClients/s3/deserialization/deserializeTimestamp.ts create mode 100644 packages/storage/src/foundation/factories/serviceClients/s3/deserialization/emptyArrayGuard.ts create mode 100644 packages/storage/src/foundation/factories/serviceClients/s3/deserialization/index.ts create mode 100644 packages/storage/src/foundation/factories/serviceClients/s3/deserialization/map.ts delete mode 100644 packages/storage/src/foundation/factories/serviceClients/s3/deserializeHelpers.ts create mode 100644 packages/storage/src/foundation/factories/serviceClients/s3/serialization/assignStringVariables.ts create mode 100644 packages/storage/src/foundation/factories/serviceClients/s3/serialization/index.ts create mode 100644 packages/storage/src/foundation/factories/serviceClients/s3/serialization/serializeObjectConfigsToHeaders.ts create mode 100644 packages/storage/src/foundation/factories/serviceClients/s3/serialization/serializePathnameObjectKey.ts create mode 100644 packages/storage/src/foundation/factories/serviceClients/s3/serialization/validateS3RequiredParameter.ts delete mode 100644 packages/storage/src/foundation/factories/serviceClients/s3/serializeHelpers.ts diff --git a/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/buildStorageServiceError.ts b/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/buildStorageServiceError.ts new file mode 100644 index 00000000000..e1e7abc9005 --- /dev/null +++ b/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/buildStorageServiceError.ts @@ -0,0 +1,27 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { ServiceError } from '@aws-amplify/core/internals/utils'; + +import { StorageError } from '../../../../../errors/StorageError'; + +/** + * Internal-only method to create a new StorageError from a service error. + * + * @internal + */ +export const buildStorageServiceError = ( + error: Error, + statusCode: number, +): ServiceError => { + const storageError = new StorageError({ + name: error.name, + message: error.message, + }); + if (statusCode === 404) { + storageError.recoverySuggestion = + 'Please add the object with this key to the bucket as the key is not found.'; + } + + return storageError; +}; diff --git a/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/createStringEnumDeserializer.ts b/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/createStringEnumDeserializer.ts new file mode 100644 index 00000000000..b15c8d5921f --- /dev/null +++ b/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/createStringEnumDeserializer.ts @@ -0,0 +1,45 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { StorageError } from '../../../../../errors/StorageError'; + +/** + * Create a function deserializing a string to an enum value. If the string is not a valid enum value, it throws a + * StorageError. + * + * @example + * ```typescript + * const deserializeStringEnum = createStringEnumDeserializer(['a', 'b', 'c'] as const, 'FieldName'); + * const deserializedArray = ['a', 'b', 'c'].map(deserializeStringEnum); + * // deserializedArray = ['a', 'b', 'c'] + * + * const invalidValue = deserializeStringEnum('d'); + * // Throws InvalidFieldName: Invalid FieldName: d + * ``` + * + * @internal + */ +export const createStringEnumDeserializer = ( + enumValues: T, + fieldName: string, +) => { + const deserializeStringEnum = ( + value: any, + ): T extends (infer E)[] ? E : never => { + const parsedEnumValue = value + ? (enumValues.find(enumValue => enumValue === value) as any) + : undefined; + if (!parsedEnumValue) { + throw new StorageError({ + name: `Invalid${fieldName}`, + message: `Invalid ${fieldName}: ${value}`, + recoverySuggestion: + 'This is likely to be a bug. Please reach out to library authors.', + }); + } + + return parsedEnumValue; + }; + + return deserializeStringEnum; +}; diff --git a/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/deserializeBoolean.ts b/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/deserializeBoolean.ts new file mode 100644 index 00000000000..57fc181bc5c --- /dev/null +++ b/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/deserializeBoolean.ts @@ -0,0 +1,12 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** + * Deserializes a string to a boolean. Returns undefined if input is undefined. Returns true if input is 'true', + * otherwise false. + * + * @internal + */ +export const deserializeBoolean = (value?: string): boolean | undefined => { + return value ? value === 'true' : undefined; +}; diff --git a/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/deserializeMetadata.ts b/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/deserializeMetadata.ts new file mode 100644 index 00000000000..286d09c9642 --- /dev/null +++ b/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/deserializeMetadata.ts @@ -0,0 +1,22 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Headers } from '@aws-amplify/core/internals/aws-client-utils'; + +/** + * @internal + */ +export const deserializeMetadata = ( + headers: Headers, +): Record => { + const objectMetadataHeaderPrefix = 'x-amz-meta-'; + const deserialized = Object.keys(headers) + .filter(header => header.startsWith(objectMetadataHeaderPrefix)) + .reduce((acc, header) => { + acc[header.replace(objectMetadataHeaderPrefix, '')] = headers[header]; + + return acc; + }, {} as any); + + return Object.keys(deserialized).length > 0 ? deserialized : undefined; +}; diff --git a/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/deserializeNumber.ts b/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/deserializeNumber.ts new file mode 100644 index 00000000000..6ff02ba40c2 --- /dev/null +++ b/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/deserializeNumber.ts @@ -0,0 +1,10 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** + * Deserializes a string to a number. Returns undefined if input is undefined. + * + * @internal + */ +export const deserializeNumber = (value?: string): number | undefined => + value ? Number(value) : undefined; diff --git a/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/deserializeTimestamp.ts b/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/deserializeTimestamp.ts new file mode 100644 index 00000000000..a4a05cf040d --- /dev/null +++ b/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/deserializeTimestamp.ts @@ -0,0 +1,19 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** + * Deserializes a string to a Date. Returns undefined if input is undefined. + * It supports epoch timestamp; rfc3339(cannot have a UTC, fractional precision supported); rfc7231(section 7.1.1.1) + * + * @see https://www.epoch101.com/ + * @see https://datatracker.ietf.org/doc/html/rfc3339.html#section-5.6 + * @see https://datatracker.ietf.org/doc/html/rfc7231.html#section-7.1.1.1 + * + * @note For bundle size consideration, we use Date constructor to parse the timestamp string. There might be slight + * difference among browsers. + * + * @internal + */ +export const deserializeTimestamp = (value: string): Date | undefined => { + return value ? new Date(value) : undefined; +}; diff --git a/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/emptyArrayGuard.ts b/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/emptyArrayGuard.ts new file mode 100644 index 00000000000..d7bf17d7ab5 --- /dev/null +++ b/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/emptyArrayGuard.ts @@ -0,0 +1,21 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** + * Function that makes sure the deserializer receives non-empty array. + * + * @internal + */ +export const emptyArrayGuard = ( + value: any, + deserializer: (value: any[]) => T, +): T => { + if (value === '') { + return [] as any as T; + } + const valueArray = (Array.isArray(value) ? value : [value]).filter( + e => e != null, + ); + + return deserializer(valueArray); +}; diff --git a/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/index.ts b/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/index.ts new file mode 100644 index 00000000000..387fda6e168 --- /dev/null +++ b/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/index.ts @@ -0,0 +1,12 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// TODO(ashwinkumar6): remove duplicate storage/src/providers/s3/utils/client/utils/deserializeHelpers.ts +export { buildStorageServiceError } from './buildStorageServiceError'; +export { createStringEnumDeserializer } from './createStringEnumDeserializer'; +export { deserializeBoolean } from './deserializeBoolean'; +export { deserializeMetadata } from './deserializeMetadata'; +export { deserializeNumber } from './deserializeNumber'; +export { deserializeTimestamp } from './deserializeTimestamp'; +export { emptyArrayGuard } from './emptyArrayGuard'; +export { map } from './map'; diff --git a/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/map.ts b/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/map.ts new file mode 100644 index 00000000000..cfa1d069fc8 --- /dev/null +++ b/packages/storage/src/foundation/factories/serviceClients/s3/deserialization/map.ts @@ -0,0 +1,67 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +type PropertyNameWithStringValue = string; + +type PropertyNameWithSubsequentDeserializer = [string, (arg: any) => T]; + +type Instruction = + | PropertyNameWithStringValue + | PropertyNameWithSubsequentDeserializer; + +type InferInstructionResultType> = + | (T extends PropertyNameWithSubsequentDeserializer ? R : string) + | never; + +/** + * Maps an object to a new object using the provided instructions. + * The instructions are a map of the returning mapped object's property names to a single instruction of how to map the + * value from the original object to the new object. There are two types of instructions: + * + * 1. A string representing the property name of the original object to map to the new object. The value mapped from + * the original object will be the same as the value in the new object, and it can ONLY be string. + * + * 2. An array of two elements. The first element is the property name of the original object to map to the new object. + * The second element is a function that takes the value from the original object and returns the value to be mapped to + * the new object. The function can return any type. + * + * Example: + * ```typescript + * const input = { + * Foo: 'foo', + * BarList: [{value: 'bar1'}, {value: 'bar2'}] + * } + * const output = map(input, { + * someFoo: 'Foo', + * bar: ['BarList', (barList) => barList.map(bar => bar.value)] + * baz: 'Baz' // Baz does not exist in input, so it will not be in the output. + * }); + * // output = { someFoo: 'foo', bar: ['bar1', 'bar2'] } + * ``` + * + * @param obj The object containing the data to compose mapped object. + * @param instructions The instructions mapping the object values to the new object. + * @returns A new object with the mapped values. + * + * @internal + */ +export const map = >>( + obj: Record, + instructions: Instructions, +): { + [K in keyof Instructions]: InferInstructionResultType; +} => { + const result = {} as Record; + for (const [key, instruction] of Object.entries(instructions)) { + const [accessor, deserializer] = Array.isArray(instruction) + ? instruction + : [instruction]; + if (Object.prototype.hasOwnProperty.call(obj, accessor)) { + result[key as keyof Instructions] = deserializer + ? deserializer(obj[accessor]) + : String(obj[accessor]); + } + } + + return result; +}; diff --git a/packages/storage/src/foundation/factories/serviceClients/s3/deserializeHelpers.ts b/packages/storage/src/foundation/factories/serviceClients/s3/deserializeHelpers.ts deleted file mode 100644 index 391617bd349..00000000000 --- a/packages/storage/src/foundation/factories/serviceClients/s3/deserializeHelpers.ts +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -// TODO(ashwinkumar6): remove duplicate storage/src/providers/s3/utils/client/utils/deserializeHelpers.ts -import { Headers } from '@aws-amplify/core/internals/aws-client-utils'; -import { ServiceError } from '@aws-amplify/core/internals/utils'; - -import { StorageError } from '../../../../errors/StorageError'; - -type PropertyNameWithStringValue = string; -type PropertyNameWithSubsequentDeserializer = [string, (arg: any) => T]; -type Instruction = - | PropertyNameWithStringValue - | PropertyNameWithSubsequentDeserializer; - -type InferInstructionResultType> = - | (T extends PropertyNameWithSubsequentDeserializer ? R : string) - | never; - -/** - * Maps an object to a new object using the provided instructions. - * The instructions are a map of the returning mapped object's property names to a single instruction of how to map the - * value from the original object to the new object. There are two types of instructions: - * - * 1. A string representing the property name of the original object to map to the new object. The value mapped from - * the original object will be the same as the value in the new object, and it can ONLY be string. - * - * 2. An array of two elements. The first element is the property name of the original object to map to the new object. - * The second element is a function that takes the value from the original object and returns the value to be mapped to - * the new object. The function can return any type. - * - * Example: - * ```typescript - * const input = { - * Foo: 'foo', - * BarList: [{value: 'bar1'}, {value: 'bar2'}] - * } - * const output = map(input, { - * someFoo: 'Foo', - * bar: ['BarList', (barList) => barList.map(bar => bar.value)] - * baz: 'Baz' // Baz does not exist in input, so it will not be in the output. - * }); - * // output = { someFoo: 'foo', bar: ['bar1', 'bar2'] } - * ``` - * - * @param obj The object containing the data to compose mapped object. - * @param instructions The instructions mapping the object values to the new object. - * @returns A new object with the mapped values. - * - * @internal - */ -export const map = >>( - obj: Record, - instructions: Instructions, -): { - [K in keyof Instructions]: InferInstructionResultType; -} => { - const result = {} as Record; - for (const [key, instruction] of Object.entries(instructions)) { - const [accessor, deserializer] = Array.isArray(instruction) - ? instruction - : [instruction]; - if (Object.prototype.hasOwnProperty.call(obj, accessor)) { - result[key as keyof Instructions] = deserializer - ? deserializer(obj[accessor]) - : String(obj[accessor]); - } - } - - return result; -}; - -/** - * Deserializes a string to a number. Returns undefined if input is undefined. - * - * @internal - */ -export const deserializeNumber = (value?: string): number | undefined => - value ? Number(value) : undefined; - -/** - * Deserializes a string to a boolean. Returns undefined if input is undefined. Returns true if input is 'true', - * otherwise false. - * - * @internal - */ -export const deserializeBoolean = (value?: string): boolean | undefined => { - return value ? value === 'true' : undefined; -}; - -/** - * Deserializes a string to a Date. Returns undefined if input is undefined. - * It supports epoch timestamp; rfc3339(cannot have a UTC, fractional precision supported); rfc7231(section 7.1.1.1) - * - * @see https://www.epoch101.com/ - * @see https://datatracker.ietf.org/doc/html/rfc3339.html#section-5.6 - * @see https://datatracker.ietf.org/doc/html/rfc7231.html#section-7.1.1.1 - * - * @note For bundle size consideration, we use Date constructor to parse the timestamp string. There might be slight - * difference among browsers. - * - * @internal - */ -export const deserializeTimestamp = (value: string): Date | undefined => { - return value ? new Date(value) : undefined; -}; - -/** - * Create a function deserializing a string to an enum value. If the string is not a valid enum value, it throws a - * StorageError. - * - * @example - * ```typescript - * const deserializeStringEnum = createStringEnumDeserializer(['a', 'b', 'c'] as const, 'FieldName'); - * const deserializedArray = ['a', 'b', 'c'].map(deserializeStringEnum); - * // deserializedArray = ['a', 'b', 'c'] - * - * const invalidValue = deserializeStringEnum('d'); - * // Throws InvalidFieldName: Invalid FieldName: d - * ``` - * - * @internal - */ -export const createStringEnumDeserializer = ( - enumValues: T, - fieldName: string, -) => { - const deserializeStringEnum = ( - value: any, - ): T extends (infer E)[] ? E : never => { - const parsedEnumValue = value - ? (enumValues.find(enumValue => enumValue === value) as any) - : undefined; - if (!parsedEnumValue) { - throw new StorageError({ - name: `Invalid${fieldName}`, - message: `Invalid ${fieldName}: ${value}`, - recoverySuggestion: - 'This is likely to be a bug. Please reach out to library authors.', - }); - } - - return parsedEnumValue; - }; - - return deserializeStringEnum; -}; - -/** - * Function that makes sure the deserializer receives non-empty array. - * - * @internal - */ -export const emptyArrayGuard = ( - value: any, - deserializer: (value: any[]) => T, -): T => { - if (value === '') { - return [] as any as T; - } - const valueArray = (Array.isArray(value) ? value : [value]).filter( - e => e != null, - ); - - return deserializer(valueArray); -}; - -/** - * @internal - */ -export const deserializeMetadata = ( - headers: Headers, -): Record => { - const objectMetadataHeaderPrefix = 'x-amz-meta-'; - const deserialized = Object.keys(headers) - .filter(header => header.startsWith(objectMetadataHeaderPrefix)) - .reduce((acc, header) => { - acc[header.replace(objectMetadataHeaderPrefix, '')] = headers[header]; - - return acc; - }, {} as any); - - return Object.keys(deserialized).length > 0 ? deserialized : undefined; -}; - -/** - * Internal-only method to create a new StorageError from a service error. - * - * @internal - */ -export const buildStorageServiceError = ( - error: Error, - statusCode: number, -): ServiceError => { - const storageError = new StorageError({ - name: error.name, - message: error.message, - }); - if (statusCode === 404) { - storageError.recoverySuggestion = - 'Please add the object with this key to the bucket as the key is not found.'; - } - - return storageError; -}; diff --git a/packages/storage/src/foundation/factories/serviceClients/s3/s3data/shared/serde/CreateDeleteObjectDeserializer.ts b/packages/storage/src/foundation/factories/serviceClients/s3/s3data/shared/serde/CreateDeleteObjectDeserializer.ts index f53c96ffed4..fa548c88365 100644 --- a/packages/storage/src/foundation/factories/serviceClients/s3/s3data/shared/serde/CreateDeleteObjectDeserializer.ts +++ b/packages/storage/src/foundation/factories/serviceClients/s3/s3data/shared/serde/CreateDeleteObjectDeserializer.ts @@ -10,7 +10,7 @@ import { buildStorageServiceError, deserializeBoolean, map, -} from '../../../deserializeHelpers'; +} from '../../../deserialization'; import { parseXmlError } from '../../../parsePayload'; import type { DeleteObjectCommandOutput } from '../../types'; diff --git a/packages/storage/src/foundation/factories/serviceClients/s3/s3data/shared/serde/CreateDeleteObjectSerializer.ts b/packages/storage/src/foundation/factories/serviceClients/s3/s3data/shared/serde/CreateDeleteObjectSerializer.ts index d44c727f94d..50e0ab44bad 100644 --- a/packages/storage/src/foundation/factories/serviceClients/s3/s3data/shared/serde/CreateDeleteObjectSerializer.ts +++ b/packages/storage/src/foundation/factories/serviceClients/s3/s3data/shared/serde/CreateDeleteObjectSerializer.ts @@ -10,7 +10,7 @@ import { AmplifyUrl } from '@aws-amplify/core/internals/utils'; import { serializePathnameObjectKey, validateS3RequiredParameter, -} from '../../../serializeHelpers'; +} from '../../../serialization'; import type { DeleteObjectCommandInput } from '../../types'; type DeleteObjectInput = Pick; diff --git a/packages/storage/src/foundation/factories/serviceClients/s3/serialization/assignStringVariables.ts b/packages/storage/src/foundation/factories/serviceClients/s3/serialization/assignStringVariables.ts new file mode 100644 index 00000000000..32b6140f9e1 --- /dev/null +++ b/packages/storage/src/foundation/factories/serviceClients/s3/serialization/assignStringVariables.ts @@ -0,0 +1,18 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** + * @internal + */ +export const assignStringVariables = ( + values: Record, +): Record => { + const queryParams: Record = {}; + for (const [key, value] of Object.entries(values)) { + if (value != null) { + queryParams[key] = value.toString(); + } + } + + return queryParams; +}; diff --git a/packages/storage/src/foundation/factories/serviceClients/s3/serialization/index.ts b/packages/storage/src/foundation/factories/serviceClients/s3/serialization/index.ts new file mode 100644 index 00000000000..430c393403d --- /dev/null +++ b/packages/storage/src/foundation/factories/serviceClients/s3/serialization/index.ts @@ -0,0 +1,8 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// TODO(ashwinkumar6): remove duplicate storage/src/providers/s3/utils/client/utils/serializeHelpers.ts +export { assignStringVariables } from './assignStringVariables'; +export { serializeObjectConfigsToHeaders } from './serializeObjectConfigsToHeaders'; +export { serializePathnameObjectKey } from './serializePathnameObjectKey'; +export { validateS3RequiredParameter } from './validateS3RequiredParameter'; diff --git a/packages/storage/src/foundation/factories/serviceClients/s3/serialization/serializeObjectConfigsToHeaders.ts b/packages/storage/src/foundation/factories/serviceClients/s3/serialization/serializeObjectConfigsToHeaders.ts new file mode 100644 index 00000000000..0f7ea019f7b --- /dev/null +++ b/packages/storage/src/foundation/factories/serviceClients/s3/serialization/serializeObjectConfigsToHeaders.ts @@ -0,0 +1,46 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { assignStringVariables } from './assignStringVariables'; + +// Object configuration options when uploading an object. +interface ObjectConfigs { + ACL?: string; + CacheControl?: string; + ContentDisposition?: string; + ContentEncoding?: string; + ContentLanguage?: string; + ContentType?: string; + Expires?: Date; + Tagging?: string; + Metadata?: Record; +} + +/** + * Serailize the parameters for configuring the S3 object. Currently used by + * `putObject` and `createMultipartUpload` API. + * + * @internal + */ +export const serializeObjectConfigsToHeaders = (input: ObjectConfigs) => ({ + ...assignStringVariables({ + 'x-amz-acl': input.ACL, + 'cache-control': input.CacheControl, + 'content-disposition': input.ContentDisposition, + 'content-language': input.ContentLanguage, + 'content-encoding': input.ContentEncoding, + 'content-type': input.ContentType, + expires: input.Expires?.toUTCString(), + 'x-amz-tagging': input.Tagging, + ...serializeMetadata(input.Metadata), + }), +}); + +const serializeMetadata = ( + metadata: Record = {}, +): Record => + Object.keys(metadata).reduce((acc: any, suffix: string) => { + acc[`x-amz-meta-${suffix.toLowerCase()}`] = metadata[suffix]; + + return acc; + }, {}); diff --git a/packages/storage/src/foundation/factories/serviceClients/s3/serialization/serializePathnameObjectKey.ts b/packages/storage/src/foundation/factories/serviceClients/s3/serialization/serializePathnameObjectKey.ts new file mode 100644 index 00000000000..5c61b406d31 --- /dev/null +++ b/packages/storage/src/foundation/factories/serviceClients/s3/serialization/serializePathnameObjectKey.ts @@ -0,0 +1,17 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { extendedEncodeURIComponent } from '@aws-amplify/core/internals/aws-client-utils'; + +/** + * Serialize the object key to a URL pathname. + * @see https://github.com/aws/aws-sdk-js-v3/blob/7ed7101dcc4e81038b6c7f581162b959e6b33a04/clients/client-s3/src/protocols/Aws_restXml.ts#L1108 + * + * @internal + */ +export const serializePathnameObjectKey = (url: URL, key: string) => { + return ( + url.pathname.replace(/\/$/, '') + + `/${key.split('/').map(extendedEncodeURIComponent).join('/')}` + ); +}; diff --git a/packages/storage/src/foundation/factories/serviceClients/s3/serialization/validateS3RequiredParameter.ts b/packages/storage/src/foundation/factories/serviceClients/s3/serialization/validateS3RequiredParameter.ts new file mode 100644 index 00000000000..ca8815676cc --- /dev/null +++ b/packages/storage/src/foundation/factories/serviceClients/s3/serialization/validateS3RequiredParameter.ts @@ -0,0 +1,23 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { AmplifyErrorCode } from '@aws-amplify/core/internals/utils'; + +import { StorageError } from '../../../../../errors/StorageError'; + +export function validateS3RequiredParameter( + assertion: boolean, + paramName: string, +): asserts assertion { + if (!assertion) { + throw new StorageError({ + name: AmplifyErrorCode.Unknown, + message: 'An unknown error has occurred.', + underlyingError: new TypeError( + `Expected a non-null value for S3 parameter ${paramName}`, + ), + recoverySuggestion: + 'This is likely to be a bug. Please reach out to library authors.', + }); + } +} diff --git a/packages/storage/src/foundation/factories/serviceClients/s3/serializeHelpers.ts b/packages/storage/src/foundation/factories/serviceClients/s3/serializeHelpers.ts deleted file mode 100644 index 298ad2a4164..00000000000 --- a/packages/storage/src/foundation/factories/serviceClients/s3/serializeHelpers.ts +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -// TODO(ashwinkumar6): remove duplicate storage/src/providers/s3/utils/client/utils/serializeHelpers.ts -import { extendedEncodeURIComponent } from '@aws-amplify/core/internals/aws-client-utils'; -import { AmplifyErrorCode } from '@aws-amplify/core/internals/utils'; - -import { StorageError } from '../../../../errors/StorageError'; - -/** - * @internal - */ -export const assignStringVariables = ( - values: Record, -): Record => { - const queryParams: Record = {}; - for (const [key, value] of Object.entries(values)) { - if (value != null) { - queryParams[key] = value.toString(); - } - } - - return queryParams; -}; - -// Object configuration options when uploading an object. -interface ObjectConfigs { - ACL?: string; - CacheControl?: string; - ContentDisposition?: string; - ContentEncoding?: string; - ContentLanguage?: string; - ContentType?: string; - Expires?: Date; - Tagging?: string; - Metadata?: Record; -} - -/** - * Serailize the parameters for configuring the S3 object. Currently used by - * `putObject` and `createMultipartUpload` API. - * - * @internal - */ -export const serializeObjectConfigsToHeaders = async ( - input: ObjectConfigs, -) => ({ - ...assignStringVariables({ - 'x-amz-acl': input.ACL, - 'cache-control': input.CacheControl, - 'content-disposition': input.ContentDisposition, - 'content-language': input.ContentLanguage, - 'content-encoding': input.ContentEncoding, - 'content-type': input.ContentType, - expires: input.Expires?.toUTCString(), - 'x-amz-tagging': input.Tagging, - ...serializeMetadata(input.Metadata), - }), -}); - -const serializeMetadata = ( - metadata: Record = {}, -): Record => - Object.keys(metadata).reduce((acc: any, suffix: string) => { - acc[`x-amz-meta-${suffix.toLowerCase()}`] = metadata[suffix]; - - return acc; - }, {}); - -/** - * Serialize the object key to a URL pathname. - * @see https://github.com/aws/aws-sdk-js-v3/blob/7ed7101dcc4e81038b6c7f581162b959e6b33a04/clients/client-s3/src/protocols/Aws_restXml.ts#L1108 - * - * @internal - */ -export const serializePathnameObjectKey = (url: URL, key: string) => { - return ( - url.pathname.replace(/\/$/, '') + - `/${key.split('/').map(extendedEncodeURIComponent).join('/')}` - ); -}; - -export function validateS3RequiredParameter( - assertion: boolean, - paramName: string, -): asserts assertion { - if (!assertion) { - throw new StorageError({ - name: AmplifyErrorCode.Unknown, - message: 'An unknown error has occurred.', - underlyingError: new TypeError( - `Expected a non-null value for S3 parameter ${paramName}`, - ), - recoverySuggestion: - 'This is likely to be a bug. Please reach out to library authors.', - }); - } -}