diff --git a/canaries/integration_test/main_test.dart b/canaries/integration_test/main_test.dart index 1afe1e1c17..c6781dbf26 100644 --- a/canaries/integration_test/main_test.dart +++ b/canaries/integration_test/main_test.dart @@ -30,14 +30,14 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); const data = 'hello, world'; - const dataKey = 'hello'; + const path = StoragePath.fromString('public/hello'); final event = AnalyticsEvent('hello'); Future performUnauthenticatedActions() async { // Upload data to Storage await Amplify.Storage.uploadData( data: HttpPayload.string(data), - key: dataKey, + path: path, ).result; // Record Analytics event @@ -63,19 +63,15 @@ void main() { Future performAuthenticatedActions() async { // Retrieve guest data final guestData = await Amplify.Storage.downloadData( - key: dataKey, - options: const StorageDownloadDataOptions( - accessLevel: StorageAccessLevel.guest, - ), + path: path, ).result; expect(utf8.decode(guestData.bytes), data); // Upload data to Storage await Amplify.Storage.uploadData( data: HttpPayload.string(data), - key: dataKey, - options: const StorageUploadDataOptions( - accessLevel: StorageAccessLevel.private, + path: StoragePath.fromIdentityId( + (String identityId) => 'private/$identityId/hello', ), ).result; diff --git a/packages/amplify_core/lib/src/category/amplify_storage_category.dart b/packages/amplify_core/lib/src/category/amplify_storage_category.dart index e8cd626200..416446d12e 100644 --- a/packages/amplify_core/lib/src/category/amplify_storage_category.dart +++ b/packages/amplify_core/lib/src/category/amplify_storage_category.dart @@ -9,9 +9,6 @@ part of 'amplify_categories.dart'; /// /// It comes with default, built-in support for Amazon S3 service /// leveraging Amplify Auth Category for authorization. -/// -/// The Amplify CLI helps you to create and configure the storage category -/// and auth category. /// {@endtemplate} class StorageCategory extends AmplifyCategory { @override @@ -37,7 +34,7 @@ class StorageCategory extends AmplifyCategory { /// returns a [StorageListOperation]. /// {@endtemplate} StorageListOperation list({ - String? path, + required StoragePath path, StorageListOptions? options, }) { return identifyCall( @@ -50,7 +47,7 @@ class StorageCategory extends AmplifyCategory { } /// {@template amplify_core.amplify_storage_category.get_properties} - /// Retrieves properties of the object specified by [key] with optional + /// Retrieves properties of the object specified by [path] with optional /// [StorageGetPropertiesOptions]. And returns a /// [StorageGetPropertiesOperation]. /// @@ -58,40 +55,40 @@ class StorageCategory extends AmplifyCategory { /// was uploaded. /// {@endtemplate} StorageGetPropertiesOperation getProperties({ - required String key, + required StoragePath path, StorageGetPropertiesOptions? options, }) { return identifyCall( StorageCategoryMethod.getProperties, () => defaultPlugin.getProperties( - key: key, + path: path, options: options, ), ); } /// {@template amplify_core.amplify_storage_category.get_url} - /// Generates a downloadable url for the object specified by [key] with + /// Generates a downloadable url for the object specified by [path] with /// [StorageGetUrlOptions], and returns a [StorageGetUrlOperation]. /// /// The url is presigned by the aws_signature_v4, and is enforced with scheme /// `https`. /// {@endtemplate} StorageGetUrlOperation getUrl({ - required String key, + required StoragePath path, StorageGetUrlOptions? options, }) { return identifyCall( StorageCategoryMethod.getUrl, () => defaultPlugin.getUrl( - key: key, + path: path, options: options, ), ); } /// {@template amplify_core.amplify_storage_category.download_data} - /// Downloads bytes of object specified by [key] into memory with optional + /// Downloads bytes of object specified by [path] into memory with optional /// [onProgress] and [StorageDownloadDataOptions], and returns a /// [StorageDownloadDataOperation]. /// @@ -99,14 +96,14 @@ class StorageCategory extends AmplifyCategory { /// memory leaks. /// {@endtemplate} StorageDownloadDataOperation downloadData({ - required String key, + required StoragePath path, void Function(StorageTransferProgress)? onProgress, StorageDownloadDataOptions? options, }) { return identifyCall( StorageCategoryMethod.downloadData, () => defaultPlugin.downloadData( - key: key, + path: path, onProgress: onProgress, options: options, ), @@ -114,12 +111,12 @@ class StorageCategory extends AmplifyCategory { } /// {@template amplify_core.amplify_storage_category.download_file} - /// Downloads the object specified by [key] to [localFile] with optional + /// Downloads the object specified by [path] to [localFile] with optional /// [onProgress] and [StorageDownloadFileOptions], and returns a /// [StorageDownloadFileOperation]. /// {@endtemplate} StorageDownloadFileOperation downloadFile({ - required String key, + required StoragePath path, required AWSFile localFile, void Function(StorageTransferProgress)? onProgress, StorageDownloadFileOptions? options, @@ -127,7 +124,7 @@ class StorageCategory extends AmplifyCategory { return identifyCall( StorageCategoryMethod.downloadFile, () => defaultPlugin.downloadFile( - key: key, + path: path, localFile: localFile, onProgress: onProgress, options: options, @@ -137,21 +134,21 @@ class StorageCategory extends AmplifyCategory { /// {@template amplify_core.amplify_storage_category.upload_data} /// Uploads [data] as a [StorageDataPayload] with optional - /// [onProgress] and [StorageUploadDataOptions] to object specified by [key], + /// [onProgress] and [StorageUploadDataOptions] to object specified by [path], /// and returns a [StorageUploadDataOperation]. /// /// See [StorageDataPayload] for supported data formats. /// {@endtemplate} StorageUploadDataOperation uploadData({ required StorageDataPayload data, - required String key, + required StoragePath path, void Function(StorageTransferProgress)? onProgress, StorageUploadDataOptions? options, }) { return identifyCall( StorageCategoryMethod.uploadData, () => defaultPlugin.uploadData( - key: key, + path: path, data: data, onProgress: onProgress, options: options, @@ -161,7 +158,7 @@ class StorageCategory extends AmplifyCategory { /// {@template amplify_core.amplify_storage_category.upload_file} /// Uploads data from [localFile] with optional [onProgress] and - /// [StorageUploadFileOptions] to object specified by [key], and returns a + /// [StorageUploadFileOptions] to object specified by [path], and returns a /// [StorageUploadFileOperation]. /// /// [AWSFile] provides various adapters to read file content from file @@ -169,14 +166,14 @@ class StorageCategory extends AmplifyCategory { /// {@endtemplate} StorageUploadFileOperation uploadFile({ required AWSFile localFile, - required String key, + required StoragePath path, void Function(StorageTransferProgress)? onProgress, StorageUploadFileOptions? options, }) { return identifyCall( StorageCategoryMethod.uploadFile, () => defaultPlugin.uploadFile( - key: key, + path: path, localFile: localFile, onProgress: onProgress, options: options, @@ -190,12 +187,11 @@ class StorageCategory extends AmplifyCategory { /// {@endtemplate} /// /// {@template amplify_core.amplify_storage_category.copy_source} - /// The `source` should be readable to the API call originator following - /// corresponding [StorageAccessLevel]. + /// The `source` should be readable to the API call originator. /// {@endtemplate} StorageCopyOperation copy({ - required StorageItemWithAccessLevel source, - required StorageItemWithAccessLevel destination, + required StoragePath source, + required StoragePath destination, StorageCopyOptions? options, }) { return identifyCall( @@ -208,65 +204,32 @@ class StorageCategory extends AmplifyCategory { ); } - /// {@template amplify_core.amplify_storage_category.move} - /// Moves [source] to [destination] with optional [StorageMoveOptions], - /// and returns a [StorageMoveOperation]. - /// - /// This API performs two consecutive S3 service calls: - /// 1. copy the source object to destination objection - /// 2. delete the source object - /// - /// {@macro amplify_core.amplify_storage_category.copy_source} - /// {@endtemplate} - @Deprecated( - 'This API will be removed in the next major version. ' - 'This API calls Amplify.Storage.copy() to create a copy of the file in the ' - 'destination directory and then calls Amplify.Storage.remove() to remove ' - 'the source file. ' - 'Please use Amplify.Storage.copy() and Amplify.Storage.remove() directly ' - 'instead.', - ) - StorageMoveOperation move({ - required StorageItemWithAccessLevel source, - required StorageItemWithAccessLevel destination, - StorageMoveOptions? options, - }) { - return identifyCall( - StorageCategoryMethod.move, - () => defaultPlugin.move( - source: source, - destination: destination, - options: options, - ), - ); - } - /// {@template amplify_core.amplify_storage_category.remove} - /// Removes an object specified by [key] with optional [StorageRemoveOptions], + /// Removes an object specified by [path] with optional [StorageRemoveOptions], /// and returns a [StorageRemoveOperation]. /// {@endtemplate} StorageRemoveOperation remove({ - required String key, + required StoragePath path, StorageRemoveOptions? options, }) { return identifyCall( StorageCategoryMethod.remove, - () => defaultPlugin.remove(key: key, options: options), + () => defaultPlugin.remove(path: path, options: options), ); } /// {@template amplify_core.amplify_storage_category.remove_many} - /// Removes multiple objects specified by [keys] with optional + /// Removes multiple objects specified by [paths] with optional /// [StorageRemoveManyOptions], and returns a [StorageRemoveManyOperation]. /// {@endtemplate} StorageRemoveManyOperation removeMany({ - required List keys, + required List paths, StorageRemoveManyOptions? options, }) { return identifyCall( StorageCategoryMethod.removeMany, () => defaultPlugin.removeMany( - keys: keys, + paths: paths, options: options, ), ); diff --git a/packages/amplify_core/lib/src/plugin/amplify_storage_plugin_interface.dart b/packages/amplify_core/lib/src/plugin/amplify_storage_plugin_interface.dart index d863a37106..c4d874d8a0 100644 --- a/packages/amplify_core/lib/src/plugin/amplify_storage_plugin_interface.dart +++ b/packages/amplify_core/lib/src/plugin/amplify_storage_plugin_interface.dart @@ -16,7 +16,7 @@ abstract class StoragePluginInterface extends AmplifyPluginInterface { /// {@macro amplify_core.amplify_storage_category.list} StorageListOperation list({ - String? path, + required StoragePath path, StorageListOptions? options, }) { throw UnimplementedError('list() has not been implemented.'); @@ -24,7 +24,7 @@ abstract class StoragePluginInterface extends AmplifyPluginInterface { /// {@macro amplify_core.amplify_storage_category.get_properties} StorageGetPropertiesOperation getProperties({ - required String key, + required StoragePath path, StorageGetPropertiesOptions? options, }) { throw UnimplementedError('getProperties() has not been implemented.'); @@ -32,7 +32,7 @@ abstract class StoragePluginInterface extends AmplifyPluginInterface { /// {@macro amplify_core.amplify_storage_category.get_url} StorageGetUrlOperation getUrl({ - required String key, + required StoragePath path, StorageGetUrlOptions? options, }) { throw UnimplementedError('getUrl() has not been implemented.'); @@ -40,7 +40,7 @@ abstract class StoragePluginInterface extends AmplifyPluginInterface { /// {@macro amplify_core.amplify_storage_category.download_data} StorageDownloadDataOperation downloadData({ - required String key, + required StoragePath path, void Function(StorageTransferProgress)? onProgress, StorageDownloadDataOptions? options, }) { @@ -49,7 +49,7 @@ abstract class StoragePluginInterface extends AmplifyPluginInterface { /// {@macro amplify_core.amplify_storage_category.download_file} StorageDownloadFileOperation downloadFile({ - required String key, + required StoragePath path, required AWSFile localFile, void Function(StorageTransferProgress)? onProgress, StorageDownloadFileOptions? options, @@ -59,7 +59,7 @@ abstract class StoragePluginInterface extends AmplifyPluginInterface { /// {@macro amplify_core.amplify_storage_category.upload_data} StorageUploadDataOperation uploadData({ - required String key, + required StoragePath path, required StorageDataPayload data, void Function(StorageTransferProgress)? onProgress, StorageUploadDataOptions? options, @@ -69,7 +69,7 @@ abstract class StoragePluginInterface extends AmplifyPluginInterface { /// {@macro amplify_core.amplify_storage_category.upload_file} StorageUploadFileOperation uploadFile({ - required String key, + required StoragePath path, required AWSFile localFile, void Function(StorageTransferProgress)? onProgress, StorageUploadFileOptions? options, @@ -79,25 +79,16 @@ abstract class StoragePluginInterface extends AmplifyPluginInterface { /// {@macro amplify_core.amplify_storage_category.copy} StorageCopyOperation copy({ - required StorageItemWithAccessLevel source, - required StorageItemWithAccessLevel destination, + required StoragePath source, + required StoragePath destination, StorageCopyOptions? options, }) { throw UnimplementedError('copy() has not been implemented.'); } - /// {@macro amplify_core.amplify_storage_category.move} - StorageMoveOperation move({ - required StorageItemWithAccessLevel source, - required StorageItemWithAccessLevel destination, - StorageMoveOptions? options, - }) { - throw UnimplementedError('move() has not been implemented.'); - } - /// {@macro amplify_core.amplify_storage_category.remove} StorageRemoveOperation remove({ - required String key, + required StoragePath path, StorageRemoveOptions? options, }) { throw UnimplementedError('remove() has not been implemented.'); @@ -105,7 +96,7 @@ abstract class StoragePluginInterface extends AmplifyPluginInterface { /// {@macro amplify_core.amplify_storage_category.remove_many} StorageRemoveManyOperation removeMany({ - required List keys, + required List paths, StorageRemoveManyOptions? options, }) { throw UnimplementedError('removeMany() has not been implemented.'); diff --git a/packages/amplify_core/lib/src/types/exception/amplify_exception.dart b/packages/amplify_core/lib/src/types/exception/amplify_exception.dart index 666facb221..fa5a7ea65a 100644 --- a/packages/amplify_core/lib/src/types/exception/amplify_exception.dart +++ b/packages/amplify_core/lib/src/types/exception/amplify_exception.dart @@ -24,6 +24,7 @@ part 'storage/http_status_exception.dart'; part 'storage/key_not_found_exception.dart'; part 'storage/local_file_not_found_exception.dart'; part 'storage/operation_canceled_exception.dart'; +part 'storage/path_validation_exception.dart'; part 'storage/storage_exception.dart'; part 'unknown_exception.dart'; diff --git a/packages/amplify_core/lib/src/types/exception/storage/path_validation_exception.dart b/packages/amplify_core/lib/src/types/exception/storage/path_validation_exception.dart new file mode 100644 index 0000000000..8f5b23aaca --- /dev/null +++ b/packages/amplify_core/lib/src/types/exception/storage/path_validation_exception.dart @@ -0,0 +1,19 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +part of '../amplify_exception.dart'; + +/// {@template amplify_core.storage.path_validation_exception} +/// Exception thrown when the [StoragePath] is invalid. +/// {@endtemplate} +class StoragePathValidationException extends StorageException { + /// {@macro amplify_core.storage.path_validation_exception} + const StoragePathValidationException( + super.message, { + super.recoverySuggestion, + super.underlyingException, + }); + + @override + String get runtimeTypeName => 'StoragePathValidationException'; +} diff --git a/packages/amplify_core/lib/src/types/storage/base/storage_operation.dart b/packages/amplify_core/lib/src/types/storage/base/storage_operation.dart index a52f9e3f0a..dd68d273d0 100644 --- a/packages/amplify_core/lib/src/types/storage/base/storage_operation.dart +++ b/packages/amplify_core/lib/src/types/storage/base/storage_operation.dart @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import 'package:amplify_core/amplify_core.dart'; +import 'package:meta/meta.dart'; abstract class StorageOperation { StorageOperation({ @@ -9,6 +10,7 @@ abstract class StorageOperation { required this.result, }) : operationId = uuid(); + @internal final Request request; final String operationId; final Future result; diff --git a/packages/amplify_core/lib/src/types/storage/base/storage_operation_options.dart b/packages/amplify_core/lib/src/types/storage/base/storage_operation_options.dart deleted file mode 100644 index 14d87654ae..0000000000 --- a/packages/amplify_core/lib/src/types/storage/base/storage_operation_options.dart +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import 'package:amplify_core/src/types/storage/access_level.dart'; - -abstract class StorageOperationOptions { - const StorageOperationOptions({ - this.accessLevel, - }); - - final StorageAccessLevel? accessLevel; -} diff --git a/packages/amplify_core/lib/src/types/storage/copy_request.dart b/packages/amplify_core/lib/src/types/storage/copy_request.dart index d7bc04cee8..0bfddaa1bd 100644 --- a/packages/amplify_core/lib/src/types/storage/copy_request.dart +++ b/packages/amplify_core/lib/src/types/storage/copy_request.dart @@ -15,10 +15,10 @@ class StorageCopyRequest { }); /// Copy source. - final StorageItemWithAccessLevel source; + final StoragePath source; /// Copy destination. - final StorageItemWithAccessLevel destination; + final StoragePath destination; /// Configurable options of the [StorageCopyRequest]. final StorageCopyOptions? options; diff --git a/packages/amplify_core/lib/src/types/storage/download_data_options.dart b/packages/amplify_core/lib/src/types/storage/download_data_options.dart index 59e35e964f..25c59523f1 100644 --- a/packages/amplify_core/lib/src/types/storage/download_data_options.dart +++ b/packages/amplify_core/lib/src/types/storage/download_data_options.dart @@ -1,20 +1,18 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import 'package:amplify_core/src/types/storage/base/storage_operation_options.dart'; import 'package:aws_common/aws_common.dart'; /// {@template amplify_core.storage.download_data_options} /// Configurable options for `Amplify.Storage.downloadData`. /// {@endtemplate} -class StorageDownloadDataOptions extends StorageOperationOptions +class StorageDownloadDataOptions with AWSEquatable, AWSSerializable>, AWSDebuggable { /// {@macro amplify_core.storage.download_data_options} const StorageDownloadDataOptions({ - super.accessLevel, this.pluginOptions, }); @@ -22,14 +20,13 @@ class StorageDownloadDataOptions extends StorageOperationOptions final StorageDownloadDataPluginOptions? pluginOptions; @override - List get props => [accessLevel, pluginOptions]; + List get props => [pluginOptions]; @override String get runtimeTypeName => 'StorageDownloadDataOptions'; @override Map toJson() => { - 'accessLevel': accessLevel?.name, 'pluginOptions': pluginOptions?.toJson(), }; } diff --git a/packages/amplify_core/lib/src/types/storage/download_data_request.dart b/packages/amplify_core/lib/src/types/storage/download_data_request.dart index 3f5bcb9525..5a2ca3c730 100644 --- a/packages/amplify_core/lib/src/types/storage/download_data_request.dart +++ b/packages/amplify_core/lib/src/types/storage/download_data_request.dart @@ -9,12 +9,12 @@ import 'package:amplify_core/amplify_core.dart'; class StorageDownloadDataRequest { /// {@macro amplify_core.storage.download_data_request} const StorageDownloadDataRequest({ - required this.key, + required this.path, this.options, }); - /// Key of the object to download. - final String key; + /// Path of the object to download. + final StoragePath path; /// Configurable options of the [StorageDownloadDataRequest]. final StorageDownloadDataOptions? options; diff --git a/packages/amplify_core/lib/src/types/storage/download_file_options.dart b/packages/amplify_core/lib/src/types/storage/download_file_options.dart index 7153e701df..8681667131 100644 --- a/packages/amplify_core/lib/src/types/storage/download_file_options.dart +++ b/packages/amplify_core/lib/src/types/storage/download_file_options.dart @@ -1,20 +1,18 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import 'package:amplify_core/src/types/storage/base/storage_operation_options.dart'; import 'package:aws_common/aws_common.dart'; /// {@template amplify_core.storage.download_file_options} /// Configurable options for `Amplify.Storage.downloadFile`. /// {@endtemplate} -class StorageDownloadFileOptions extends StorageOperationOptions +class StorageDownloadFileOptions with AWSEquatable, AWSSerializable>, AWSDebuggable { /// {@macro amplify_core.storage.download_file_options} const StorageDownloadFileOptions({ - super.accessLevel, this.pluginOptions, }); @@ -22,14 +20,13 @@ class StorageDownloadFileOptions extends StorageOperationOptions final StorageDownloadFilePluginOptions? pluginOptions; @override - List get props => [accessLevel, pluginOptions]; + List get props => [pluginOptions]; @override String get runtimeTypeName => 'StorageDownloadFileOptions'; @override Map toJson() => { - 'accessLevel': accessLevel?.name, 'pluginOptions': pluginOptions?.toJson(), }; } diff --git a/packages/amplify_core/lib/src/types/storage/download_file_request.dart b/packages/amplify_core/lib/src/types/storage/download_file_request.dart index 55cd835b13..c2457e02c5 100644 --- a/packages/amplify_core/lib/src/types/storage/download_file_request.dart +++ b/packages/amplify_core/lib/src/types/storage/download_file_request.dart @@ -9,13 +9,13 @@ import 'package:amplify_core/amplify_core.dart'; class StorageDownloadFileRequest { /// {@macro amplify_core.storage.download_file_request} const StorageDownloadFileRequest({ - required this.key, + required this.path, required this.localFile, this.options, }); - /// Key of the object to download. - final String key; + /// Path of the object to download. + final StoragePath path; /// Reference to the local file that download the object to. final AWSFile localFile; diff --git a/packages/amplify_core/lib/src/types/storage/get_properties_options.dart b/packages/amplify_core/lib/src/types/storage/get_properties_options.dart index 325c3f0d20..bef609f7a7 100644 --- a/packages/amplify_core/lib/src/types/storage/get_properties_options.dart +++ b/packages/amplify_core/lib/src/types/storage/get_properties_options.dart @@ -1,20 +1,18 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import 'package:amplify_core/src/types/storage/base/storage_operation_options.dart'; import 'package:aws_common/aws_common.dart'; /// {@template amplify_core.storage.get_properties_options} /// Configurable options for `Amplify.Storage.getProperties`. /// {@endtemplate} -class StorageGetPropertiesOptions extends StorageOperationOptions +class StorageGetPropertiesOptions with AWSEquatable, AWSSerializable>, AWSDebuggable { /// {@macro amplify_core.storage.get_properties_options} const StorageGetPropertiesOptions({ - super.accessLevel, this.pluginOptions, }); @@ -22,14 +20,13 @@ class StorageGetPropertiesOptions extends StorageOperationOptions final StorageGetPropertiesPluginOptions? pluginOptions; @override - List get props => [accessLevel, pluginOptions]; + List get props => [pluginOptions]; @override String get runtimeTypeName => 'StorageGetPropertiesOptions'; @override Map toJson() => { - 'accessLevel': accessLevel?.name, 'pluginOptions': pluginOptions?.toJson(), }; } diff --git a/packages/amplify_core/lib/src/types/storage/get_properties_request.dart b/packages/amplify_core/lib/src/types/storage/get_properties_request.dart index 2583ef55a6..7a99f20d3c 100644 --- a/packages/amplify_core/lib/src/types/storage/get_properties_request.dart +++ b/packages/amplify_core/lib/src/types/storage/get_properties_request.dart @@ -9,12 +9,12 @@ import 'package:amplify_core/amplify_core.dart'; class StorageGetPropertiesRequest { /// {@macro amplify_core.storage.get_properties_request} const StorageGetPropertiesRequest({ - required this.key, + required this.path, this.options, }); - /// Key of the object to get properties for. - final String key; + // Path of the object to get properties of. + final StoragePath path; /// Configurable options of the [StorageGetPropertiesRequest]. final StorageGetPropertiesOptions? options; diff --git a/packages/amplify_core/lib/src/types/storage/get_url_options.dart b/packages/amplify_core/lib/src/types/storage/get_url_options.dart index f7163d6c55..3f4078839e 100644 --- a/packages/amplify_core/lib/src/types/storage/get_url_options.dart +++ b/packages/amplify_core/lib/src/types/storage/get_url_options.dart @@ -1,20 +1,18 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import 'package:amplify_core/src/types/storage/base/storage_operation_options.dart'; import 'package:aws_common/aws_common.dart'; /// {@template amplify_core.storage.get_url_options} /// Configurable options for `Amplify.Storage.getUrl`. /// {@endtemplate} -class StorageGetUrlOptions extends StorageOperationOptions +class StorageGetUrlOptions with AWSEquatable, AWSSerializable>, AWSDebuggable { /// {@macro amplify_core.storage.get_url_options} const StorageGetUrlOptions({ - super.accessLevel, this.pluginOptions, }); @@ -22,14 +20,13 @@ class StorageGetUrlOptions extends StorageOperationOptions final StorageGetUrlPluginOptions? pluginOptions; @override - List get props => [accessLevel, pluginOptions]; + List get props => [pluginOptions]; @override String get runtimeTypeName => 'StorageGetUrlOptions'; @override Map toJson() => { - 'accessLevel': accessLevel?.name, 'pluginOptions': pluginOptions?.toJson(), }; } diff --git a/packages/amplify_core/lib/src/types/storage/get_url_request.dart b/packages/amplify_core/lib/src/types/storage/get_url_request.dart index 785239ab01..cc579dc70f 100644 --- a/packages/amplify_core/lib/src/types/storage/get_url_request.dart +++ b/packages/amplify_core/lib/src/types/storage/get_url_request.dart @@ -9,12 +9,12 @@ import 'package:amplify_core/amplify_core.dart'; class StorageGetUrlRequest { /// {@macro amplify_core.storage.get_url_request} const StorageGetUrlRequest({ - required this.key, + required this.path, this.options, }); - /// Key of the object to get url for. - final String key; + // Path of the object to get the URL of. + final StoragePath path; /// Configurable options of the [StorageGetUrlRequest]. final StorageGetUrlOptions? options; diff --git a/packages/amplify_core/lib/src/types/storage/list_options.dart b/packages/amplify_core/lib/src/types/storage/list_options.dart index 82b29851a4..c046f90d55 100644 --- a/packages/amplify_core/lib/src/types/storage/list_options.dart +++ b/packages/amplify_core/lib/src/types/storage/list_options.dart @@ -1,20 +1,18 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import 'package:amplify_core/src/types/storage/base/storage_operation_options.dart'; import 'package:aws_common/aws_common.dart'; /// {@template amplify_core.storage.list_options} /// Configurable options for `Amplify.Storage.list`. /// {@endtemplate} -class StorageListOptions extends StorageOperationOptions +class StorageListOptions with AWSEquatable, AWSSerializable>, AWSDebuggable { /// {@macro amplify_core.storage.list_options} const StorageListOptions({ - super.accessLevel, this.pageSize = 1000, this.nextToken, this.pluginOptions, @@ -30,14 +28,13 @@ class StorageListOptions extends StorageOperationOptions final StorageListPluginOptions? pluginOptions; @override - List get props => [accessLevel, pageSize, nextToken, pluginOptions]; + List get props => [pageSize, nextToken, pluginOptions]; @override String get runtimeTypeName => 'StorageListOptions'; @override Map toJson() => { - 'accessLevel': accessLevel?.name, 'pageSize': pageSize, 'nextToken': nextToken, 'pluginOptions': pluginOptions?.toJson(), diff --git a/packages/amplify_core/lib/src/types/storage/list_request.dart b/packages/amplify_core/lib/src/types/storage/list_request.dart index 9513b69dba..c3965152cc 100644 --- a/packages/amplify_core/lib/src/types/storage/list_request.dart +++ b/packages/amplify_core/lib/src/types/storage/list_request.dart @@ -9,12 +9,12 @@ import 'package:amplify_core/amplify_core.dart'; class StorageListRequest { /// {@macro amplify_core.storage.list_request} const StorageListRequest({ - this.path, + required this.path, this.options, }); /// Path to list objects under. - final String? path; + final StoragePath path; /// Configurable options of the [StorageListRequest]. final StorageListOptions? options; diff --git a/packages/amplify_core/lib/src/types/storage/move_operation.dart b/packages/amplify_core/lib/src/types/storage/move_operation.dart deleted file mode 100644 index b81471ecb7..0000000000 --- a/packages/amplify_core/lib/src/types/storage/move_operation.dart +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import 'package:amplify_core/amplify_core.dart'; -import 'package:amplify_core/src/types/storage/base/storage_operation.dart'; - -/// {@template amplify_core.storage.move_operation} -/// Presents a storage move operation. -/// {@endtemplate} -class StorageMoveOperation - extends StorageOperation { - /// {@macro amplify_core.storage.move_operation} - StorageMoveOperation({ - required super.request, - required super.result, - }); -} diff --git a/packages/amplify_core/lib/src/types/storage/move_options.dart b/packages/amplify_core/lib/src/types/storage/move_options.dart deleted file mode 100644 index 3ee9e92f39..0000000000 --- a/packages/amplify_core/lib/src/types/storage/move_options.dart +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import 'package:aws_common/aws_common.dart'; - -/// {@template amplify_core.storage.move_options} -/// Configurable options for `Amplify.Storage.move`. -/// {@endtemplate} -class StorageMoveOptions - with - AWSEquatable, - AWSSerializable>, - AWSDebuggable { - /// {@macro amplify_core.storage.move_options} - const StorageMoveOptions({this.pluginOptions}); - - /// {@macro amplify_core.storage.move_plugin_options} - final StorageMovePluginOptions? pluginOptions; - - @override - List get props => [pluginOptions]; - - @override - String get runtimeTypeName => 'StorageMoveOptions'; - - @override - Map toJson() => { - 'pluginOptions': pluginOptions?.toJson(), - }; -} - -/// {@template amplify_core.storage.move_plugin_options} -/// Plugin-specific options for `Amplify.Storage.move`. -/// {@endtemplate} -abstract class StorageMovePluginOptions - with - AWSEquatable, - AWSSerializable>, - AWSDebuggable { - /// {@macro amplify_core.storage.move_plugin_options} - const StorageMovePluginOptions(); -} diff --git a/packages/amplify_core/lib/src/types/storage/move_request.dart b/packages/amplify_core/lib/src/types/storage/move_request.dart deleted file mode 100644 index 907aea5652..0000000000 --- a/packages/amplify_core/lib/src/types/storage/move_request.dart +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import 'package:amplify_core/amplify_core.dart'; - -/// {@template amplify_core.storage.move_request} -/// Presents a storage copy request. -/// {@endtemplate} -class StorageMoveRequest { - /// {@macro amplify_core.storage.move_request} - const StorageMoveRequest({ - required this.source, - required this.destination, - this.options, - }); - - /// Move source object. - final StorageItemWithAccessLevel source; - - /// Move destination object. - final StorageItemWithAccessLevel destination; - - /// Configurable options of the [StorageMoveRequest]. - final StorageMoveOptions? options; -} diff --git a/packages/amplify_core/lib/src/types/storage/move_result.dart b/packages/amplify_core/lib/src/types/storage/move_result.dart deleted file mode 100644 index caba63f3ae..0000000000 --- a/packages/amplify_core/lib/src/types/storage/move_result.dart +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import 'package:amplify_core/amplify_core.dart'; - -/// {@template amplify_core.storage.move_result} -/// Presents the result of a [StorageCopyOperation]. -/// {@endtemplate} -class StorageMoveResult { - /// {@macro amplify_core.storage.move_result} - const StorageMoveResult({ - required this.movedItem, - }); - - /// The result object of the [StorageCopyOperation]. - final Item movedItem; -} diff --git a/packages/amplify_core/lib/src/types/storage/remove_many_options.dart b/packages/amplify_core/lib/src/types/storage/remove_many_options.dart index 02944ef13a..d9e32131fe 100644 --- a/packages/amplify_core/lib/src/types/storage/remove_many_options.dart +++ b/packages/amplify_core/lib/src/types/storage/remove_many_options.dart @@ -1,20 +1,18 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import 'package:amplify_core/src/types/storage/base/storage_operation_options.dart'; import 'package:aws_common/aws_common.dart'; /// {@template amplify_core.storage.remove_many_options} /// Configurable options for `Amplify.Storage.removeMany`. /// {@endtemplate} -class StorageRemoveManyOptions extends StorageOperationOptions +class StorageRemoveManyOptions with AWSEquatable, AWSSerializable>, AWSDebuggable { /// {@macro amplify_core.storage.remove_many_options} const StorageRemoveManyOptions({ - super.accessLevel, this.pluginOptions, }); @@ -22,14 +20,13 @@ class StorageRemoveManyOptions extends StorageOperationOptions final StorageRemoveManyPluginOptions? pluginOptions; @override - List get props => [accessLevel, pluginOptions]; + List get props => [pluginOptions]; @override String get runtimeTypeName => 'StorageRemoveManyOptions'; @override Map toJson() => { - 'accessLevel': accessLevel?.name, 'pluginOptions': pluginOptions?.toJson(), }; } diff --git a/packages/amplify_core/lib/src/types/storage/remove_many_request.dart b/packages/amplify_core/lib/src/types/storage/remove_many_request.dart index adf0823b4d..bb73279686 100644 --- a/packages/amplify_core/lib/src/types/storage/remove_many_request.dart +++ b/packages/amplify_core/lib/src/types/storage/remove_many_request.dart @@ -9,12 +9,12 @@ import 'package:amplify_core/amplify_core.dart'; class StorageRemoveManyRequest { /// {@macro amplify_core.storage.remove_many_request} const StorageRemoveManyRequest({ - required this.keys, + required this.paths, this.options, }); /// Object keys to be removed. - final List keys; + final List paths; /// Configurable options of the [StorageRemoveManyRequest]. final StorageRemoveManyOptions? options; diff --git a/packages/amplify_core/lib/src/types/storage/remove_options.dart b/packages/amplify_core/lib/src/types/storage/remove_options.dart index 2cf3b53f16..f898e77190 100644 --- a/packages/amplify_core/lib/src/types/storage/remove_options.dart +++ b/packages/amplify_core/lib/src/types/storage/remove_options.dart @@ -1,20 +1,18 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import 'package:amplify_core/src/types/storage/base/storage_operation_options.dart'; import 'package:aws_common/aws_common.dart'; /// {@template amplify_core.storage.remove_options} /// Configurable options for `Amplify.Storage.remove`. /// {@endtemplate} -class StorageRemoveOptions extends StorageOperationOptions +class StorageRemoveOptions with AWSEquatable, AWSSerializable>, AWSDebuggable { /// {@macro amplify_core.storage.remove_options} const StorageRemoveOptions({ - super.accessLevel, this.pluginOptions, }); @@ -22,14 +20,13 @@ class StorageRemoveOptions extends StorageOperationOptions final StorageRemovePluginOptions? pluginOptions; @override - List get props => [accessLevel, pluginOptions]; + List get props => [pluginOptions]; @override String get runtimeTypeName => 'StorageRemoveOptions'; @override Map toJson() => { - 'accessLevel': accessLevel?.name, 'pluginOptions': pluginOptions?.toJson(), }; } diff --git a/packages/amplify_core/lib/src/types/storage/remove_request.dart b/packages/amplify_core/lib/src/types/storage/remove_request.dart index c436484a99..f9a110388d 100644 --- a/packages/amplify_core/lib/src/types/storage/remove_request.dart +++ b/packages/amplify_core/lib/src/types/storage/remove_request.dart @@ -9,12 +9,12 @@ import 'package:amplify_core/amplify_core.dart'; class StorageRemoveRequest { /// {@macro amplify_core.storage.remove_request} const StorageRemoveRequest({ - required this.key, + required this.path, this.options, }); /// The object key to be removed. - final String key; + final StoragePath path; /// Configurable options of the [StorageRemoveRequest]. final StorageRemoveOptions? options; diff --git a/packages/amplify_core/lib/src/types/storage/storage_item.dart b/packages/amplify_core/lib/src/types/storage/storage_item.dart index 9fa9aef80c..6675967eeb 100644 --- a/packages/amplify_core/lib/src/types/storage/storage_item.dart +++ b/packages/amplify_core/lib/src/types/storage/storage_item.dart @@ -1,38 +1,22 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import 'package:amplify_core/src/types/storage/access_level.dart'; - /// {@template amplify_core.storage.storage_item} /// Presents a storage object. /// {@endtemplate} class StorageItem { /// {@macro amplify_core.storage.storage_item} const StorageItem({ - required this.key, + required this.path, this.size, this.lastModified, this.eTag, this.metadata = const {}, }); - final String key; + final String path; final int? size; final DateTime? lastModified; final String? eTag; final Map metadata; } - -/// {@template amplify_core.storage.storage_item_access_level} -/// Presents a storage object with its access level information. -/// {@endtemplate} -class StorageItemWithAccessLevel { - /// {@macro amplify_core.storage.storage_item_access_level} - const StorageItemWithAccessLevel({ - required this.storageItem, - required this.accessLevel, - }); - - final Item storageItem; - final StorageAccessLevel accessLevel; -} diff --git a/packages/amplify_core/lib/src/types/storage/storage_path.dart b/packages/amplify_core/lib/src/types/storage/storage_path.dart new file mode 100644 index 0000000000..9f9b7f3f67 --- /dev/null +++ b/packages/amplify_core/lib/src/types/storage/storage_path.dart @@ -0,0 +1,47 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:amplify_core/src/types/storage/storage_path_from_identity_id.dart'; +import 'package:meta/meta.dart'; + +/// The full remote path of a storage item. +/// +/// ### Examples +/// #### Static Path +/// {@macro amplify_core.storage.storage_path.from_string.example} + +/// +/// #### Dynamic Path with the current user's IdentityId +/// {@macro amplify_core.storage.storage_path.with_identity_id.example} +class StoragePath { + /// Creates a [StoragePath] from a static string. + /// + /// To create a [StoragePath] from the current user's identity Id, see + /// [StoragePath.fromIdentityId] + /// + /// ### Example + /// {@template amplify_core.storage.storage_path.from_string.example} + /// ``` + /// const p = StoragePath.fromString('path/to/object.png'); + /// ``` + /// {@endtemplate} + const StoragePath.fromString(String path) : _path = path; + + /// {@macro amplify_core.storage.storage_path_from_identity_id} + /// + /// ### Example + /// {@template amplify_core.storage.storage_path.with_identity_id.example} + /// ``` + /// const p = StoragePath.fromIdentityId((String identityId) => 'users/$identityId/object.png'); + /// ``` + /// {@endtemplate} + factory StoragePath.fromIdentityId( + String Function(String identityId) pathBuilder, + ) => + StoragePathFromIdentityId(pathBuilder); + + final String _path; + + @internal + String resolvePath({String? identityId}) => _path; +} diff --git a/packages/amplify_core/lib/src/types/storage/storage_path_from_identity_id.dart b/packages/amplify_core/lib/src/types/storage/storage_path_from_identity_id.dart new file mode 100644 index 0000000000..f8b50a642b --- /dev/null +++ b/packages/amplify_core/lib/src/types/storage/storage_path_from_identity_id.dart @@ -0,0 +1,25 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:amplify_core/amplify_core.dart'; +import 'package:meta/meta.dart'; + +/// {@template amplify_core.storage.storage_path_from_identity_id} +/// Creates a [StoragePath] from the current user's identityId. +/// {@endtemplate} +class StoragePathFromIdentityId implements StoragePath { + /// {@macro amplify_core.storage.storage_path_from_identity_id} + const StoragePathFromIdentityId(this._pathBuilder); + final String Function(String identityId) _pathBuilder; + + @internal + @override + String resolvePath({ + String? identityId, + }) { + // identityId has to be optional in order for this to be a valid override. + // This API is internal, and is only invoked with non-null values + assert(identityId != null, 'identityId must be defined.'); + return _pathBuilder(identityId!); + } +} diff --git a/packages/amplify_core/lib/src/types/storage/storage_types.dart b/packages/amplify_core/lib/src/types/storage/storage_types.dart index efab18581c..ec4a373aa4 100644 --- a/packages/amplify_core/lib/src/types/storage/storage_types.dart +++ b/packages/amplify_core/lib/src/types/storage/storage_types.dart @@ -37,10 +37,6 @@ export 'list_operation.dart'; export 'list_options.dart'; export 'list_request.dart'; export 'list_result.dart'; -export 'move_operation.dart'; -export 'move_options.dart'; -export 'move_request.dart'; -export 'move_result.dart'; export 'remove_many_operation.dart'; export 'remove_many_options.dart'; export 'remove_many_request.dart'; @@ -50,6 +46,7 @@ export 'remove_options.dart'; export 'remove_request.dart'; export 'remove_result.dart'; export 'storage_item.dart'; +export 'storage_path.dart'; export 'transfer_progress.dart'; export 'upload_data_operation.dart'; export 'upload_data_options.dart'; diff --git a/packages/amplify_core/lib/src/types/storage/upload_data_options.dart b/packages/amplify_core/lib/src/types/storage/upload_data_options.dart index be3e502131..eb997552de 100644 --- a/packages/amplify_core/lib/src/types/storage/upload_data_options.dart +++ b/packages/amplify_core/lib/src/types/storage/upload_data_options.dart @@ -1,20 +1,18 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import 'package:amplify_core/src/types/storage/base/storage_operation_options.dart'; import 'package:aws_common/aws_common.dart'; /// {@template amplify_core.storage.upload_data_options} /// Configurable options for `Amplify.Storage.uploadData`. /// {@endtemplate} -class StorageUploadDataOptions extends StorageOperationOptions +class StorageUploadDataOptions with AWSEquatable, AWSSerializable>, AWSDebuggable { /// {@macro amplify_core.storage.upload_data_options} const StorageUploadDataOptions({ - super.accessLevel, this.metadata = const {}, this.pluginOptions, }); @@ -26,14 +24,13 @@ class StorageUploadDataOptions extends StorageOperationOptions final StorageUploadDataPluginOptions? pluginOptions; @override - List get props => [accessLevel, metadata, pluginOptions]; + List get props => [metadata, pluginOptions]; @override String get runtimeTypeName => 'StorageUploadDataOptions'; @override Map toJson() => { - 'accessLevel': accessLevel?.name, 'metadata': metadata, 'pluginOptions': pluginOptions?.toJson(), }; diff --git a/packages/amplify_core/lib/src/types/storage/upload_data_request.dart b/packages/amplify_core/lib/src/types/storage/upload_data_request.dart index b21b9501cf..d218a3542a 100644 --- a/packages/amplify_core/lib/src/types/storage/upload_data_request.dart +++ b/packages/amplify_core/lib/src/types/storage/upload_data_request.dart @@ -9,13 +9,13 @@ import 'package:amplify_core/amplify_core.dart'; class StorageUploadDataRequest { /// {@macro amplify_core.storage.upload_data_request} const StorageUploadDataRequest({ + required this.path, required this.data, - required this.key, this.options, }); - /// The key of the object upload to. - final String key; + /// Path to upload the data to. + final StoragePath path; /// The data payload to upload. final StorageDataPayload data; diff --git a/packages/amplify_core/lib/src/types/storage/upload_file_options.dart b/packages/amplify_core/lib/src/types/storage/upload_file_options.dart index e8718f2ab4..bad1529468 100644 --- a/packages/amplify_core/lib/src/types/storage/upload_file_options.dart +++ b/packages/amplify_core/lib/src/types/storage/upload_file_options.dart @@ -1,20 +1,18 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import 'package:amplify_core/src/types/storage/base/storage_operation_options.dart'; import 'package:aws_common/aws_common.dart'; /// {@template amplify_core.storage.upload_file_options} /// Configurable options for `Amplify.Storage.uploadFile`. /// {@endtemplate} -class StorageUploadFileOptions extends StorageOperationOptions +class StorageUploadFileOptions with AWSEquatable, AWSSerializable>, AWSDebuggable { /// {@macro amplify_core.storage.upload_file_options} const StorageUploadFileOptions({ - super.accessLevel, this.metadata = const {}, this.pluginOptions, }); @@ -26,14 +24,13 @@ class StorageUploadFileOptions extends StorageOperationOptions final StorageUploadFilePluginOptions? pluginOptions; @override - List get props => [accessLevel, metadata, pluginOptions]; + List get props => [metadata, pluginOptions]; @override String get runtimeTypeName => 'StorageUploadFileOptions'; @override Map toJson() => { - 'accessLevel': accessLevel?.name, 'metadata': metadata, 'pluginOptions': pluginOptions?.toJson(), }; diff --git a/packages/amplify_core/lib/src/types/storage/upload_file_request.dart b/packages/amplify_core/lib/src/types/storage/upload_file_request.dart index 110fa32321..1d17a7baad 100644 --- a/packages/amplify_core/lib/src/types/storage/upload_file_request.dart +++ b/packages/amplify_core/lib/src/types/storage/upload_file_request.dart @@ -9,13 +9,13 @@ import 'package:amplify_core/amplify_core.dart'; class StorageUploadFileRequest { /// {@macro amplify_core.storage.upload_file_request} const StorageUploadFileRequest({ + required this.path, required this.localFile, - required this.key, this.options, }); - /// The key of the object upload to. - final String key; + /// Path to upload the file to. + final StoragePath path; /// The local file to upload. final AWSFile localFile; diff --git a/packages/storage/amplify_storage_s3/example/integration_test/content_type_infer/content_type_infer_html.dart b/packages/storage/amplify_storage_s3/example/integration_test/content_type_infer/content_type_infer_html.dart index 19857efdd4..c1d4e7fd5d 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/content_type_infer/content_type_infer_html.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/content_type_infer/content_type_infer_html.dart @@ -38,10 +38,13 @@ void testContentTypeInferTest({ group('content type inferring within upload', () { tearDownAll(() async { await Amplify.Storage.removeMany( - keys: testUploadKeys, - options: const StorageRemoveManyOptions( - accessLevel: StorageAccessLevel.private, - ), + paths: testUploadKeys + .map( + (key) => StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$key', + ), + ) + .toList(), ).result; }); @@ -54,9 +57,10 @@ void testContentTypeInferTest({ final result = await s3Plugin .uploadFile( localFile: file, - key: testUploadKeys[index], + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/${testUploadKeys[index]}', + ), options: const StorageUploadFileOptions( - accessLevel: StorageAccessLevel.private, pluginOptions: S3UploadFilePluginOptions( getProperties: true, ), diff --git a/packages/storage/amplify_storage_s3/example/integration_test/content_type_infer/content_type_infer_io.dart b/packages/storage/amplify_storage_s3/example/integration_test/content_type_infer/content_type_infer_io.dart index 13ae33562f..8f91dc82d6 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/content_type_infer/content_type_infer_io.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/content_type_infer/content_type_infer_io.dart @@ -34,10 +34,13 @@ void testContentTypeInferTest({ tearDownAll(() async { await Amplify.Storage.removeMany( - keys: testUploadKeys, - options: const StorageRemoveManyOptions( - accessLevel: StorageAccessLevel.private, - ), + paths: testUploadKeys + .map( + (key) => StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$key', + ), + ) + .toList(), ).result; }); @@ -51,9 +54,11 @@ void testContentTypeInferTest({ final result = await s3Plugin .uploadFile( localFile: file, - key: testUploadKeys[index], + path: StoragePath.fromIdentityId( + (identityId) => + 'private/$identityId/${testUploadKeys[index]}', + ), options: const StorageUploadFileOptions( - accessLevel: StorageAccessLevel.private, pluginOptions: S3UploadFilePluginOptions( getProperties: true, ), diff --git a/packages/storage/amplify_storage_s3/example/integration_test/issues/get_url_special_characters_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/issues/get_url_special_characters_test.dart index 4acf149a10..2d4e6f026b 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/issues/get_url_special_characters_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/issues/get_url_special_characters_test.dart @@ -41,10 +41,12 @@ void main() { final key = 'Test - ${DateTime.now()}'; await Amplify.Storage.uploadData( data: S3DataPayload.bytes('hello'.codeUnits), - key: key, + path: StoragePath.fromString('public/$key'), ).result; - final getUrlResult = await Amplify.Storage.getUrl(key: key).result; + final getUrlResult = await Amplify.Storage.getUrl( + path: StoragePath.fromString('public/$key'), + ).result; final uri = getUrlResult.url; final client = AWSHttpClient() diff --git a/packages/storage/amplify_storage_s3/example/integration_test/transfer_acceleration/test_acceleration_config.dart b/packages/storage/amplify_storage_s3/example/integration_test/transfer_acceleration/test_acceleration_config.dart index d15b322cf7..1f93b46a8d 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/transfer_acceleration/test_acceleration_config.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/transfer_acceleration/test_acceleration_config.dart @@ -5,14 +5,12 @@ import 'package:amplify_core/amplify_core.dart'; class TestTransferAccelerationConfig { const TestTransferAccelerationConfig({ - required this.targetKey, - required this.targetAccessLevel, + required this.targetPath, required this.uploadSource, required this.referenceBytes, }); - final String targetKey; - final StorageAccessLevel targetAccessLevel; + final StoragePath targetPath; final T uploadSource; final List referenceBytes; } diff --git a/packages/storage/amplify_storage_s3/example/integration_test/transfer_acceleration/transfer_acceleration.dart b/packages/storage/amplify_storage_s3/example/integration_test/transfer_acceleration/transfer_acceleration.dart index dff163d947..20e27f76e3 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/transfer_acceleration/transfer_acceleration.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/transfer_acceleration/transfer_acceleration.dart @@ -16,9 +16,7 @@ void testTransferAcceleration({ tearDownAll(() async { for (final dataPayload in dataPayloads) { await Amplify.Storage.remove( - key: dataPayload.targetKey, - options: - StorageRemoveOptions(accessLevel: dataPayload.targetAccessLevel), + path: dataPayload.targetPath, ).result; } }); @@ -29,11 +27,10 @@ void testTransferAcceleration({ final dataPayload = entry.value; final operation = Amplify.Storage.uploadData( data: dataPayload.uploadSource, - key: dataPayload.targetKey, - options: StorageUploadDataOptions( - accessLevel: dataPayload.targetAccessLevel, + path: dataPayload.targetPath, + options: const StorageUploadDataOptions( pluginOptions: - const S3UploadDataPluginOptions(useAccelerateEndpoint: true), + S3UploadDataPluginOptions(useAccelerateEndpoint: true), ), ); @@ -50,10 +47,9 @@ void testTransferAcceleration({ test('S3DataPayload ${entry.key}', () async { final dataPayload = entry.value; final result = await Amplify.Storage.getUrl( - key: dataPayload.targetKey, - options: StorageGetUrlOptions( - accessLevel: dataPayload.targetAccessLevel, - pluginOptions: const S3GetUrlPluginOptions( + path: dataPayload.targetPath, + options: const StorageGetUrlOptions( + pluginOptions: S3GetUrlPluginOptions( expiresIn: Duration(minutes: 5), useAccelerateEndpoint: true, ), @@ -70,10 +66,9 @@ void testTransferAcceleration({ test('S3DataPayload ${entry.key}', () async { final dataPayload = entry.value; final result = await Amplify.Storage.downloadData( - key: dataPayload.targetKey, - options: StorageDownloadDataOptions( - accessLevel: dataPayload.targetAccessLevel, - pluginOptions: const S3DownloadDataPluginOptions( + path: dataPayload.targetPath, + options: const StorageDownloadDataOptions( + pluginOptions: S3DownloadDataPluginOptions( useAccelerateEndpoint: true, ), ), @@ -91,8 +86,7 @@ void testTransferAcceleration({ tearDownAll(() async { for (final awsFile in awsFiles) { await Amplify.Storage.remove( - key: awsFile.targetKey, - options: StorageRemoveOptions(accessLevel: awsFile.targetAccessLevel), + path: awsFile.targetPath, ).result; } }); @@ -103,10 +97,9 @@ void testTransferAcceleration({ final awsFile = entry.value; final operation = Amplify.Storage.uploadFile( localFile: awsFile.uploadSource, - key: awsFile.targetKey, - options: StorageUploadFileOptions( - accessLevel: awsFile.targetAccessLevel, - pluginOptions: const S3UploadFilePluginOptions( + path: awsFile.targetPath, + options: const StorageUploadFileOptions( + pluginOptions: S3UploadFilePluginOptions( useAccelerateEndpoint: true, ), ), @@ -127,10 +120,9 @@ void testTransferAcceleration({ () async { final awsFile = entry.value; final result = await Amplify.Storage.getUrl( - key: awsFile.targetKey, - options: StorageGetUrlOptions( - accessLevel: awsFile.targetAccessLevel, - pluginOptions: const S3GetUrlPluginOptions( + path: awsFile.targetPath, + options: const StorageGetUrlOptions( + pluginOptions: S3GetUrlPluginOptions( expiresIn: Duration(minutes: 5), useAccelerateEndpoint: true, ), @@ -152,9 +144,8 @@ void testTransferAcceleration({ const end = 5 * 1024 + 12; final awsFile = entry.value; final result = await Amplify.Storage.downloadData( - key: awsFile.targetKey, + path: awsFile.targetPath, options: StorageDownloadDataOptions( - accessLevel: awsFile.targetAccessLevel, pluginOptions: S3DownloadDataPluginOptions( useAccelerateEndpoint: true, bytesRange: S3DataBytesRange(start: start, end: end), diff --git a/packages/storage/amplify_storage_s3/example/integration_test/use_case_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/use_case_test.dart index 2dddcf4f19..4723da292f 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/use_case_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/use_case_test.dart @@ -56,9 +56,7 @@ void main() { testLargeFileBytes[0] = 101; testLargeFileBytes[5 * 1024] = 102; final testObjectKey1 = 'user1-guest-object-${uuid()}'; - final testObject1MoveKey = 'user1-guest-object-move-${uuid()}'; final testObjectKey2 = 'user1-protected-object-${uuid()}'; - final testObject2MoveKey = 'user1-protected-object-move-${uuid()}'; final testObjectKey3 = 'user1-private-object-${uuid()}'; final testObject3CopyKey = 'user1-private-object-copy-${uuid()}'; final testObject3CopyMoveKey = 'user1-private-object-copy-move-${uuid()}'; @@ -66,7 +64,8 @@ void main() { const testObjectFileName2 = 'user1Protected.jpg'; const testObjectFileName3 = 'user1Private.large'; - for (final entry in amplifyEnvironments.entries) { + for (final entry in amplifyEnvironments.entries + .where((element) => element.key != 'custom-prefix')) { group('[Environment ${entry.key}]', () { S3PrefixResolver? prefixResolver; late String user1IdentityId; @@ -90,6 +89,13 @@ void main() { await Amplify.addPlugins([authPlugin, storagePlugin]); await Amplify.configure(amplifyEnvironments[entry.key]!); + // confirm no user is signed in before starting tests + try { + await Amplify.Auth.signOut(); + } on Exception { + // do nothing + } + await Amplify.Auth.signUp( username: username1, password: password, @@ -156,9 +162,8 @@ void main() { testBytes, contentType: testContentType, ), - key: testObjectKey1, + path: StoragePath.fromString('public/$testObjectKey1'), options: const StorageUploadDataOptions( - accessLevel: StorageAccessLevel.guest, metadata: { 'filename': testObjectFileName1, }, @@ -206,9 +211,10 @@ void main() { final result = await s3Plugin .uploadData( data: S3DataPayload.dataUrl(testDataUrl), - key: testObjectKey2, + path: StoragePath.fromIdentityId( + (identityId) => 'protected/$identityId/$testObjectKey2', + ), options: const StorageUploadDataOptions( - accessLevel: StorageAccessLevel.protected, metadata: { 'filename': testObjectFileName2, }, @@ -244,9 +250,10 @@ void main() { final result = await s3Plugin .uploadFile( localFile: AWSFile.fromData(testLargeFileBytes), - key: testObjectKey3, + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$testObjectKey3', + ), options: const StorageUploadFileOptions( - accessLevel: StorageAccessLevel.private, metadata: { 'filename': testObjectFileName3, }, @@ -279,9 +286,10 @@ void main() { 'generate downloadable url with access level private for the' ' currently signed in user', (WidgetTester tester) async { final result = await Amplify.Storage.getUrl( - key: testObjectKey3, + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$testObjectKey3', + ), options: const StorageGetUrlOptions( - accessLevel: StorageAccessLevel.private, pluginOptions: S3GetUrlPluginOptions( validateObjectExistence: true, expiresIn: Duration(minutes: 5), @@ -296,9 +304,10 @@ void main() { 'should throw generating downloadable url of a non-existent object', (WidgetTester tester) async { final result = Amplify.Storage.getUrl( - key: 'random/non-existent/object.png', + path: const StoragePath.fromString( + 'public/random/non-existent/object.png', + ), options: const StorageGetUrlOptions( - accessLevel: StorageAccessLevel.private, pluginOptions: S3GetUrlPluginOptions( validateObjectExistence: true, expiresIn: Duration(minutes: 5), @@ -313,9 +322,10 @@ void main() { 'download object as bytes data in memory with access level private' ' for the currently signed in user', (WidgetTester tester) async { final result = await Amplify.Storage.downloadData( - key: testObjectKey3, + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$testObjectKey3', + ), options: const StorageDownloadDataOptions( - accessLevel: StorageAccessLevel.private, pluginOptions: S3DownloadDataPluginOptions( getProperties: true, ), @@ -332,9 +342,10 @@ void main() { const start = 5 * 1024; const end = 5 * 1024 + 12; final result = await Amplify.Storage.downloadData( - key: testObjectKey3, + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$testObjectKey3', + ), options: StorageDownloadDataOptions( - accessLevel: StorageAccessLevel.private, pluginOptions: S3DownloadDataPluginOptions( getProperties: true, bytesRange: S3DataBytesRange( @@ -364,10 +375,11 @@ void main() { final result = await s3Plugin .downloadFile( - key: testObjectKey3, + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$testObjectKey3', + ), localFile: localFile, options: const StorageDownloadFileOptions( - accessLevel: StorageAccessLevel.private, pluginOptions: S3DownloadFilePluginOptions( getProperties: true, ), @@ -390,13 +402,11 @@ void main() { 'copy object with access level private for the currently signed in user', (WidgetTester tester) async { final result = await Amplify.Storage.copy( - source: S3ItemWithAccessLevel( - storageItem: S3Item(key: testObjectKey3), - accessLevel: StorageAccessLevel.private, + source: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$testObjectKey3', ), - destination: S3ItemWithAccessLevel( - storageItem: S3Item(key: testObject3CopyKey), - accessLevel: StorageAccessLevel.private, + destination: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$testObject3CopyKey', ), options: const StorageCopyOptions( pluginOptions: S3CopyPluginOptions( @@ -405,56 +415,23 @@ void main() { ), ).result; - expect(result.copiedItem.key, testObject3CopyKey); + expect(result.copiedItem.path.endsWith(testObject3CopyKey), isTrue); expect(result.copiedItem.eTag, isNotEmpty); }); - testWidgets( - 'move object with access level private for the currently signed in user', - (WidgetTester tester) async { - // ignore: deprecated_member_use - final result = await Amplify.Storage.move( - source: S3ItemWithAccessLevel( - storageItem: S3Item(key: testObject3CopyKey), - accessLevel: StorageAccessLevel.private, - ), - destination: S3ItemWithAccessLevel( - storageItem: S3Item(key: testObject3CopyMoveKey), - accessLevel: StorageAccessLevel.private, - ), - options: const StorageMoveOptions( - pluginOptions: S3MovePluginOptions(getProperties: true), - ), - ).result; - - expect(result.movedItem.key, testObject3CopyMoveKey); - expect(result.movedItem.eTag, isNotEmpty); - - final listedObjects = await Amplify.Storage.list( - options: const StorageListOptions( - accessLevel: StorageAccessLevel.private, - ), - ).result; - - expect( - listedObjects.items.map((item) => item.key), - isNot( - contains(testObject3CopyKey), - ), - ); - }); - testWidgets( 'delete object with access level private for the currently signed in user', (WidgetTester tester) async { final result = await Amplify.Storage.remove( - key: testObject3CopyMoveKey, - options: const StorageRemoveOptions( - accessLevel: StorageAccessLevel.private, + path: StoragePath.fromIdentityId( + (id) => 'private/$id/$testObject3CopyMoveKey', ), ).result; - expect(result.removedItem.key, testObject3CopyMoveKey); + expect( + result.removedItem.path.endsWith(testObject3CopyMoveKey), + isTrue, + ); }); group('content type infer', () { @@ -466,11 +443,14 @@ void main() { if (shouldTestTransferAcceleration) { group('transfer acceleration', () { + final dataPayloadId = uuid(); + final awsFileId = uuid(); testTransferAcceleration( dataPayloads: [ TestTransferAccelerationConfig( - targetKey: 'transfer-acceleration-datapayload-${uuid()}', - targetAccessLevel: StorageAccessLevel.guest, + targetPath: StoragePath.fromString( + 'public/transfer-acceleration-datapayload-$dataPayloadId', + ), uploadSource: S3DataPayload.bytes( testBytes, ), @@ -479,8 +459,10 @@ void main() { ], awsFiles: [ TestTransferAccelerationConfig( - targetKey: 'transfer-acceleration-awsfile-${uuid()}', - targetAccessLevel: StorageAccessLevel.private, + targetPath: StoragePath.fromIdentityId( + (identityId) => + 'private/$identityId/transfer-acceleration-awsfile-$awsFileId', + ), uploadSource: AWSFile.fromData(testLargeFileBytes), referenceBytes: testLargeFileBytes, ), @@ -507,10 +489,9 @@ void main() { testBytes, contentType: testContentType, ), - key: objectKey, + path: StoragePath.fromString('public/$objectKey'), options: StorageUploadDataOptions( metadata: testMetadata, - accessLevel: StorageAccessLevel.guest, pluginOptions: const S3UploadDataPluginOptions( getProperties: true, ), @@ -535,7 +516,9 @@ void main() { expect(metadata, equals(testMetadata)); - await s3Plugin.remove(key: objectKey).result; + await s3Plugin + .remove(path: StoragePath.fromString('public/$objectKey')) + .result; }); }); @@ -555,8 +538,9 @@ void main() { testWidgets('get properties of object with access level guest', (WidgetTester tester) async { - final result = - await Amplify.Storage.getProperties(key: testObjectKey1).result; + final result = await Amplify.Storage.getProperties( + path: StoragePath.fromString('public/$testObjectKey1'), + ).result; expect(result.storageItem.eTag, object1Etag); }); @@ -565,11 +549,8 @@ void main() { 'get properties of object with access level protected and a target identity id', (WidgetTester tester) async { final result = await Amplify.Storage.getProperties( - key: testObjectKey2, - options: StorageGetPropertiesOptions( - accessLevel: StorageAccessLevel.protected, - pluginOptions: - S3GetPropertiesPluginOptions.forIdentity(user1IdentityId), + path: StoragePath.fromString( + 'protected/$user1IdentityId/$testObjectKey2', ), ).result; @@ -581,9 +562,8 @@ void main() { ' private for the currently signed user throws exception', (WidgetTester tester) async { final operation = Amplify.Storage.getProperties( - key: testObjectKey3, - options: const StorageGetPropertiesOptions( - accessLevel: StorageAccessLevel.private, + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$testObjectKey3', ), ); @@ -596,9 +576,8 @@ void main() { testWidgets('get url of object with access level guest', (WidgetTester tester) async { final operation = Amplify.Storage.getUrl( - key: testObjectKey1, + path: StoragePath.fromString('public/$testObjectKey1'), options: const StorageGetUrlOptions( - accessLevel: StorageAccessLevel.guest, pluginOptions: S3GetUrlPluginOptions( validateObjectExistence: true, ), @@ -612,13 +591,11 @@ void main() { 'get url of object with access level protected and a target identity id', (WidgetTester tester) async { final operation = Amplify.Storage.getUrl( - key: testObjectKey2, - options: StorageGetUrlOptions( - accessLevel: StorageAccessLevel.protected, - pluginOptions: S3GetUrlPluginOptions.forIdentity( - user1IdentityId, - validateObjectExistence: true, - ), + path: StoragePath.fromIdentityId( + (identityId) => 'protected/$identityId/$testObjectKey2', + ), + options: const StorageGetUrlOptions( + pluginOptions: S3GetUrlPluginOptions(), ), ); @@ -630,9 +607,10 @@ void main() { ' private for the currently signed user throws exception', (WidgetTester tester) async { final operation = Amplify.Storage.getUrl( - key: testObjectKey3, + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$testObjectKey3', + ), options: const StorageGetUrlOptions( - accessLevel: StorageAccessLevel.private, pluginOptions: S3GetUrlPluginOptions( validateObjectExistence: true, ), @@ -650,11 +628,11 @@ void main() { ' identity id for the currently signed user', (WidgetTester tester) async { final result = await Amplify.Storage.downloadData( - key: testObjectKey2, - options: StorageDownloadDataOptions( - accessLevel: StorageAccessLevel.protected, - pluginOptions: S3DownloadDataPluginOptions.forIdentity( - user1IdentityId, + path: StoragePath.fromString( + 'protected/$user1IdentityId/$testObjectKey2', + ), + options: const StorageDownloadDataOptions( + pluginOptions: S3DownloadDataPluginOptions( getProperties: true, ), ), @@ -668,9 +646,10 @@ void main() { ' private for the currently signed user', (WidgetTester tester) async { final operation = Amplify.Storage.downloadData( - key: testObjectKey3, + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$testObjectKey3', + ), options: const StorageDownloadDataOptions( - accessLevel: StorageAccessLevel.private, pluginOptions: S3DownloadDataPluginOptions( getProperties: true, ), @@ -683,40 +662,15 @@ void main() { ); }); - testWidgets( - 'move object with access level guest for the currently signed in user', - (WidgetTester tester) async { - // ignore: deprecated_member_use - final result = await Amplify.Storage.move( - source: S3ItemWithAccessLevel( - storageItem: S3Item(key: testObjectKey1), - accessLevel: StorageAccessLevel.guest, - ), - destination: S3ItemWithAccessLevel( - storageItem: S3Item(key: testObject1MoveKey), - accessLevel: StorageAccessLevel.private, - ), - options: const StorageMoveOptions( - pluginOptions: S3MovePluginOptions( - getProperties: true, - ), - ), - ).result; - - expect(result.movedItem.eTag, isNotEmpty); - }); - testWidgets( 'copy object (belongs to other user) with access level protected' ' for the currently signed in user', (WidgetTester tester) async { final result = await Amplify.Storage.copy( - source: S3ItemWithAccessLevel.forIdentity( - user1IdentityId, - storageItem: S3Item(key: testObjectKey2), + source: StoragePath.fromString( + 'protected/$user1IdentityId/$testObjectKey2', ), - destination: S3ItemWithAccessLevel( - storageItem: S3Item(key: testObject2MoveKey), - accessLevel: StorageAccessLevel.private, + destination: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$testObjectKey2', ), options: const StorageCopyOptions( pluginOptions: S3CopyPluginOptions( @@ -731,7 +685,6 @@ void main() { testWidgets('list respects pageSize', (WidgetTester tester) async { const filesToUpload = 2; const filesToList = 1; - const accessLevel = StorageAccessLevel.private; final uploadedKeys = []; // Upload some files to test. for (var i = filesToUpload; i > 0; i--) { @@ -743,9 +696,10 @@ void main() { testBytes, contentType: 'text/plain', ), - key: fileKey, + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$fileKey', + ), options: StorageUploadDataOptions( - accessLevel: accessLevel, metadata: { 'filename': fileNameTemp, }, @@ -755,8 +709,10 @@ void main() { // Call list() and ensure length of result matches pageSize. final listResult = await Amplify.Storage.list( + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/', + ), options: const StorageListOptions( - accessLevel: accessLevel, pageSize: filesToList, ), ).result; @@ -764,10 +720,13 @@ void main() { expect(listResult.items.length, filesToList); // Clean up files from this test. await Amplify.Storage.removeMany( - keys: uploadedKeys, - options: const StorageRemoveManyOptions( - accessLevel: accessLevel, - ), + paths: uploadedKeys + .map( + (key) => StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$key', + ), + ) + .toList(), ).result; }); @@ -775,7 +734,6 @@ void main() { (WidgetTester tester) async { const filesToUpload = 2; const filesToList = 1; - const accessLevel = StorageAccessLevel.private; final keyPrefix = 'testObjectList${uuid()}'; final uploadedKeys = []; // Upload some files to test. @@ -788,9 +746,10 @@ void main() { testBytes, contentType: 'text/plain', ), - key: fileKey, + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$fileKey', + ), options: StorageUploadDataOptions( - accessLevel: accessLevel, metadata: { 'filename': fileNameTemp, }, @@ -803,12 +762,13 @@ void main() { do { // Call list() until nextToken is null and ensure we paginated expected times. final listResult = await Amplify.Storage.list( + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$keyPrefix', + ), options: StorageListOptions( - accessLevel: accessLevel, pageSize: filesToList, nextToken: lastNextToken, ), - path: keyPrefix, ).result; lastNextToken = listResult.nextToken; timesCalled++; @@ -816,35 +776,46 @@ void main() { expect(timesCalled, equals(filesToUpload)); // Clean up files from this test. await Amplify.Storage.removeMany( - keys: uploadedKeys, - options: const StorageRemoveManyOptions( - accessLevel: accessLevel, - ), + paths: uploadedKeys + .map( + (key) => StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$key', + ), + ) + .toList(), ).result; }); testWidgets( 'remove many objects belongs to the currently signed user', (WidgetTester tester) async { - final listedObjects = await Amplify.Storage.list( - options: const StorageListOptions( - accessLevel: StorageAccessLevel.private, + final result1 = await Amplify.Storage.uploadData( + data: HttpPayload.string('obj1'), + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/remove-test/obj1', ), ).result; - expect(listedObjects.items, hasLength(2)); - final result = await Amplify.Storage.removeMany( - keys: listedObjects.items.map((item) => item.key).toList(), - options: const StorageRemoveManyOptions( - accessLevel: StorageAccessLevel.private, + final result2 = await Amplify.Storage.uploadData( + data: HttpPayload.string('obj2'), + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/remove-test/obj2', ), ).result; + + final result = await Amplify.Storage.removeMany( + paths: [ + StoragePath.fromString(result1.uploadedItem.path), + StoragePath.fromString(result2.uploadedItem.path), + ], + ).result; expect(result.removedItems, hasLength(2)); expect( - result.removedItems.map((item) => item.key), - containsAll( - listedObjects.items.map((item) => item.key).toList(), - ), + result.removedItems.map((item) => item.path), + containsAll([ + result1.uploadedItem.path, + result2.uploadedItem.path, + ]), ); }); }, @@ -862,47 +833,35 @@ void main() { await Amplify.Auth.signOut(); }); - testWidgets('move object with access level protected as object owner', + testWidgets('remove many objects belongs to the currently signed user', (WidgetTester tester) async { - // ignore: deprecated_member_use - final result = await Amplify.Storage.move( - source: S3ItemWithAccessLevel.forIdentity( - user1IdentityId, - storageItem: S3Item(key: testObjectKey2), - ), - destination: S3ItemWithAccessLevel( - storageItem: S3Item(key: testObject2MoveKey), - accessLevel: StorageAccessLevel.private, - ), - options: const StorageMoveOptions( - pluginOptions: S3MovePluginOptions(getProperties: true), + final result1 = await Amplify.Storage.uploadData( + data: HttpPayload.string('obj1'), + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/remove-test-user-a/obj1', ), ).result; - expect(result.movedItem.eTag, isNotEmpty); - }); - - testWidgets('remove many objects belongs to the currently signed user', - (WidgetTester tester) async { - final listedObjects = await Amplify.Storage.list( - options: const StorageListOptions( - accessLevel: StorageAccessLevel.private, + final result2 = await Amplify.Storage.uploadData( + data: HttpPayload.string('obj2'), + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/remove-test-user-a/obj2', ), ).result; - expect(listedObjects.items, hasLength(2)); final result = await Amplify.Storage.removeMany( - keys: listedObjects.items.map((item) => item.key).toList(), - options: const StorageRemoveManyOptions( - accessLevel: StorageAccessLevel.private, - ), + paths: [ + StoragePath.fromString(result1.uploadedItem.path), + StoragePath.fromString(result2.uploadedItem.path), + ], ).result; expect(result.removedItems, hasLength(2)); expect( - result.removedItems.map((item) => item.key), - containsAll( - listedObjects.items.map((item) => item.key).toList(), - ), + result.removedItems.map((item) => item.path), + containsAll([ + result1.uploadedItem.path, + result2.uploadedItem.path, + ]), ); }); }); diff --git a/packages/storage/amplify_storage_s3/example/lib/main.dart b/packages/storage/amplify_storage_s3/example/lib/main.dart index 270f5dd4f1..5aee512eeb 100644 --- a/packages/storage/amplify_storage_s3/example/lib/main.dart +++ b/packages/storage/amplify_storage_s3/example/lib/main.dart @@ -129,8 +129,7 @@ class _HomeScreenState extends State { // upload a file to the S3 bucket Future _uploadFile() async { final result = await FilePicker.platform.pickFiles( - type: FileType.custom, - allowedExtensions: ['jpg', 'png'], + type: FileType.image, withReadStream: true, withData: false, ); @@ -148,7 +147,7 @@ class _HomeScreenState extends State { platformFile.readStream!, size: platformFile.size, ), - key: platformFile.name, + path: StoragePath.fromString('public/${platformFile.name}'), onProgress: (p) => _logger.debug('Uploading: ${p.transferredBytes}/${p.totalBytes}'), ).result; @@ -162,8 +161,8 @@ class _HomeScreenState extends State { Future _listAllPublicFiles() async { try { final result = await Amplify.Storage.list( + path: const StoragePath.fromString('public/'), options: const StorageListOptions( - accessLevel: StorageAccessLevel.guest, pluginOptions: S3ListPluginOptions.listAll(), ), ).result; @@ -176,12 +175,12 @@ class _HomeScreenState extends State { } // download file on mobile - Future downloadFileMobile(String key) async { + Future downloadFileMobile(String path) async { final documentsDir = await getApplicationDocumentsDirectory(); - final filepath = '${documentsDir.path}/$key'; + final filepath = '${documentsDir.path}/$path'; try { await Amplify.Storage.downloadFile( - key: key, + path: StoragePath.fromString(path), localFile: AWSFile.fromPath(filepath), onProgress: (p0) => _logger .debug('Progress: ${(p0.transferredBytes / p0.totalBytes) * 100}%'), @@ -193,11 +192,11 @@ class _HomeScreenState extends State { } // download file on web - Future downloadFileWeb(String key) async { + Future downloadFileWeb(String path) async { try { await Amplify.Storage.downloadFile( - key: key, - localFile: AWSFile.fromPath(key), + path: StoragePath.fromString(path), + localFile: AWSFile.fromPath(path), onProgress: (p0) => _logger .debug('Progress: ${(p0.transferredBytes / p0.totalBytes) * 100}%'), ).result; @@ -208,14 +207,10 @@ class _HomeScreenState extends State { } // delete file from S3 bucket - Future removeFile({ - required String key, - required StorageAccessLevel accessLevel, - }) async { + Future removeFile(String path) async { try { await Amplify.Storage.remove( - key: key, - options: StorageRemoveOptions(accessLevel: accessLevel), + path: StoragePath.fromString(path), ).result; setState(() { // set the imageUrl to empty if the deleted file is the one being displayed @@ -228,16 +223,12 @@ class _HomeScreenState extends State { } // get the url of a file in the S3 bucket - Future getUrl({ - required String key, - required StorageAccessLevel accessLevel, - }) async { + Future getUrl(String path) async { try { final result = await Amplify.Storage.getUrl( - key: key, - options: StorageGetUrlOptions( - accessLevel: accessLevel, - pluginOptions: const S3GetUrlPluginOptions( + path: StoragePath.fromString(path), + options: const StorageGetUrlOptions( + pluginOptions: S3GetUrlPluginOptions( validateObjectExistence: true, expiresIn: Duration(minutes: 1), ), @@ -270,19 +261,13 @@ class _HomeScreenState extends State { final item = list[index]; return ListTile( onTap: () { - getUrl( - key: item.key, - accessLevel: StorageAccessLevel.guest, - ); + getUrl(item.path); }, - title: Text(item.key), + title: Text(item.path), trailing: IconButton( icon: const Icon(Icons.delete), onPressed: () { - removeFile( - key: item.key, - accessLevel: StorageAccessLevel.guest, - ); + removeFile(item.path); }, color: Colors.red, ), @@ -290,8 +275,8 @@ class _HomeScreenState extends State { icon: const Icon(Icons.download), onPressed: () { zIsWeb - ? downloadFileWeb(item.key) - : downloadFileMobile(item.key); + ? downloadFileWeb(item.path) + : downloadFileMobile(item.path); }, ), ); diff --git a/packages/storage/amplify_storage_s3_dart/example/bin/example.dart b/packages/storage/amplify_storage_s3_dart/example/bin/example.dart index c4529a1a65..f51a73d84b 100644 --- a/packages/storage/amplify_storage_s3_dart/example/bin/example.dart +++ b/packages/storage/amplify_storage_s3_dart/example/bin/example.dart @@ -41,8 +41,7 @@ Future main() async { 3. getUrl 4. download data 5. download file 6. upload data url 7. upload file 8. copy - 9. move 10. remove - 0. exit + 9. remove 0. exit '''); final operationNum = int.tryParse(operation); @@ -64,8 +63,6 @@ Future main() async { case 8: await copyOperation(); case 9: - await moveOperation(); - case 10: await removeOperation(); case null: break; @@ -79,9 +76,6 @@ Future main() async { Future listOperation() async { final path = prompt('Enter a path to list objects for: '); - final accessLevel = promptStorageAccessLevel( - 'Choose the storage access level associated with the path: ', - ); final listAll = prompt('List with pagination? (Y/n): ').toLowerCase() == 'n'; const pageSize = 5; @@ -89,16 +83,14 @@ Future listOperation() async { // get plugin with plugin key to gain S3 specific interface final s3Plugin = Amplify.Storage.getPlugin(AmplifyStorageS3Dart.pluginKey); final options = listAll - ? StorageListOptions( - accessLevel: accessLevel, - pluginOptions: const S3ListPluginOptions.listAll(), + ? const StorageListOptions( + pluginOptions: S3ListPluginOptions.listAll(), ) - : StorageListOptions( - accessLevel: accessLevel, + : const StorageListOptions( pageSize: pageSize, ); final operation = s3Plugin.list( - path: path, + path: StoragePath.fromString(path), options: options, ); @@ -106,7 +98,6 @@ Future listOperation() async { // final operation = Amplify.Storage.list( // path: path, // options: StorageS3ListOptions( - // accessLevel: accessLevel, // pageSize: 5, // ), // ); @@ -119,7 +110,7 @@ Future listOperation() async { stdout.writeln('Listed ${result.items.length} objects.'); stdout.writeln('Sub directories: ${result.metadata.subPaths}'); result.items.asMap().forEach((index, item) { - stdout.writeln('$index. key: ${item.key} | size: ${item.size}'); + stdout.writeln('$index. path: ${item.path} | size: ${item.size}'); }); if (!result.hasNextPage) { @@ -136,9 +127,8 @@ Future listOperation() async { result = await s3Plugin .list( - path: path, + path: StoragePath.fromString(path), options: StorageListOptions( - accessLevel: accessLevel, pageSize: pageSize, nextToken: result.nextToken, ), @@ -149,22 +139,16 @@ Future listOperation() async { Future getPropertiesOperation() async { final key = prompt('Enter the object to get properties for: '); - final accessLevel = promptStorageAccessLevel( - 'Choose the storage access level associated with the object: ', - ); final s3Plugin = Amplify.Storage.getPlugin(AmplifyStorageS3Dart.pluginKey); final getPropertiesOperation = s3Plugin.getProperties( - key: key, - options: StorageGetPropertiesOptions( - accessLevel: accessLevel, - ), + path: StoragePath.fromString(key), ); final result = await getPropertiesOperation.result; stdout ..writeln('Got properties: ') - ..writeln('key: ${result.storageItem.key}') + ..writeln('path: ${result.storageItem.path}') ..writeln('size: ${result.storageItem.size}') ..writeln('lastModified: ${result.storageItem.lastModified}') ..writeln('eTag: ${result.storageItem.eTag}') @@ -173,16 +157,13 @@ Future getPropertiesOperation() async { Future getUrlOperation() async { final key = prompt('Enter the object key to get url for: '); - final accessLevel = promptStorageAccessLevel( - 'Choose the storage access level associated with the object: ', - ); + final useAccelerateEndpoint = promptUseAcceleration(); final s3Plugin = Amplify.Storage.getPlugin(AmplifyStorageS3Dart.pluginKey); final getUrlOperation = s3Plugin.getUrl( - key: key, + path: StoragePath.fromString(key), options: StorageGetUrlOptions( - accessLevel: accessLevel, pluginOptions: S3GetUrlPluginOptions( expiresIn: const Duration( minutes: 10, @@ -207,16 +188,12 @@ Future getUrlOperation() async { Future downloadDataOperation() async { final key = prompt('Enter the key of the object to download: '); - final accessLevel = promptStorageAccessLevel( - 'Choose the storage access level associated with the object: ', - ); final s3Plugin = Amplify.Storage.getPlugin(AmplifyStorageS3Dart.pluginKey); final downloadDataOperation = s3Plugin.downloadData( - key: key, - options: StorageDownloadDataOptions( - accessLevel: accessLevel, - pluginOptions: const S3DownloadDataPluginOptions( + path: StoragePath.fromString(key), + options: const StorageDownloadDataOptions( + pluginOptions: S3DownloadDataPluginOptions( getProperties: true, ), ), @@ -245,9 +222,6 @@ Future downloadDataOperation() async { Future downloadFileOperation() async { final key = prompt('Enter the key of the object to download: '); - final accessLevel = promptStorageAccessLevel( - 'Choose the storage access level associated with the object: ', - ); final destinationPath = prompt( 'Enter the destination file path (ensure the file path is writable): ', ); @@ -257,10 +231,9 @@ Future downloadFileOperation() async { final s3Plugin = Amplify.Storage.getPlugin(AmplifyStorageS3Dart.pluginKey); final downloadFileOperation = s3Plugin.downloadFile( - key: key, + path: StoragePath.fromString(key), localFile: localFile, options: StorageDownloadFileOptions( - accessLevel: accessLevel, pluginOptions: S3DownloadFilePluginOptions( getProperties: true, useAccelerateEndpoint: useAccelerateEndpoint, @@ -292,17 +265,13 @@ Future downloadFileOperation() async { Future uploadDataUrlOperation() async { final dataUrl = prompt('Enter the data url to upload: '); final key = prompt('Enter the object key to upload the data url to: '); - final accessLevel = promptStorageAccessLevel( - 'Choose the storage access level associated with the object to upload: ', - ); final s3Plugin = Amplify.Storage.getPlugin(AmplifyStorageS3Dart.pluginKey); final uploadDataOperation = s3Plugin.uploadData( data: S3DataPayload.dataUrl(dataUrl), - key: key, - options: StorageUploadDataOptions( - accessLevel: accessLevel, - pluginOptions: const S3UploadDataPluginOptions( + path: StoragePath.fromString(key), + options: const StorageUploadDataOptions( + pluginOptions: S3UploadDataPluginOptions( getProperties: true, ), ), @@ -313,7 +282,7 @@ Future uploadDataUrlOperation() async { final result = await uploadDataOperation.result; stdout ..writeln('Uploaded data url: ') - ..writeln('key: ${result.uploadedItem.key}') + ..writeln('path: ${result.uploadedItem.path}') ..writeln('size: ${result.uploadedItem.size}') ..writeln('lastModified: ${result.uploadedItem.lastModified}') ..writeln('eTag: ${result.uploadedItem.eTag}'); @@ -327,9 +296,6 @@ Future uploadDataUrlOperation() async { Future uploadFileOperation() async { final filePath = prompt('Enter the path of the file to be uploaded: '); final key = prompt('Enter the object key to upload the file to: '); - final accessLevel = promptStorageAccessLevel( - 'Choose the storage access level associated with the object to upload: ', - ); final nameTag = prompt('Enter value of the name tag for this file: '); final file = AWSFile.fromPath(filePath); @@ -344,10 +310,9 @@ Future uploadFileOperation() async { final s3Plugin = Amplify.Storage.getPlugin(AmplifyStorageS3Dart.pluginKey); final uploadFileOperation = s3Plugin.uploadFile( localFile: file, - key: key, + path: StoragePath.fromString(key), onProgress: onTransferProgress, options: StorageUploadFileOptions( - accessLevel: accessLevel, metadata: { 'nameTag': nameTag, }, @@ -380,24 +345,12 @@ Future uploadFileOperation() async { Future copyOperation() async { final sourceKey = prompt('Enter the key of the source object: '); - final sourceStorageAccessLevel = promptStorageAccessLevel( - 'Choose the storage access level associated with the source object: ', - ); final destinationKey = prompt('Enter the key of the destination object: '); - final destinationStorageAccessLevel = promptStorageAccessLevel( - 'Choose the storage access level associated with the destination object: ', - ); final s3Plugin = Amplify.Storage.getPlugin(AmplifyStorageS3Dart.pluginKey); final copyOperation = s3Plugin.copy( - source: S3ItemWithAccessLevel( - storageItem: S3Item(key: sourceKey), - accessLevel: sourceStorageAccessLevel, - ), - destination: S3ItemWithAccessLevel( - storageItem: S3Item(key: destinationKey), - accessLevel: destinationStorageAccessLevel, - ), + source: StoragePath.fromString(sourceKey), + destination: StoragePath.fromString(destinationKey), options: const StorageCopyOptions( pluginOptions: S3CopyPluginOptions(getProperties: true), ), @@ -407,7 +360,7 @@ Future copyOperation() async { final result = await copyOperation.result; stdout ..writeln('Copied object: ') - ..writeln('key: ${result.copiedItem.key}') + ..writeln('path: ${result.copiedItem.path}') ..writeln('size: ${result.copiedItem.size}') ..writeln('lastModified: ${result.copiedItem.lastModified}') ..writeln('eTag: ${result.copiedItem.eTag}') @@ -419,66 +372,19 @@ Future copyOperation() async { } } -Future moveOperation() async { - final sourceKey = prompt('Enter the key of the source object: '); - final sourceStorageAccessLevel = promptStorageAccessLevel( - 'Choose the storage access level associated with the source object: ', - ); - final destinationKey = prompt('Enter the key of the destination object: '); - final destinationStorageAccessLevel = promptStorageAccessLevel( - 'Choose the storage access level associated with the destination object: ', - ); - - final s3Plugin = Amplify.Storage.getPlugin(AmplifyStorageS3Dart.pluginKey); - final moveOperation = s3Plugin.move( - source: S3ItemWithAccessLevel( - storageItem: S3Item(key: sourceKey), - accessLevel: sourceStorageAccessLevel, - ), - destination: S3ItemWithAccessLevel( - storageItem: S3Item(key: destinationKey), - accessLevel: destinationStorageAccessLevel, - ), - options: const StorageMoveOptions( - pluginOptions: S3MovePluginOptions(getProperties: true), - ), - ); - - try { - final result = await moveOperation.result; - stdout - ..writeln('Copied object: ') - ..writeln('key: ${result.movedItem.key}') - ..writeln('size: ${result.movedItem.size}') - ..writeln('lastModified: ${result.movedItem.lastModified}') - ..writeln('eTag: ${result.movedItem.eTag}') - ..writeln('metadata: ${result.movedItem.metadata}'); - } on Exception catch (error) { - stderr - ..writeln('Something went wrong...') - ..writeln(error); - } -} - Future removeOperation() async { final key = prompt('Enter the object key to remove: '); - final accessLevel = promptStorageAccessLevel( - 'Choose the storage access level associated with the object: ', - ); final s3Plugin = Amplify.Storage.getPlugin(AmplifyStorageS3Dart.pluginKey); final removeOperation = s3Plugin.remove( - key: key, - options: StorageRemoveOptions( - accessLevel: accessLevel, - ), + path: StoragePath.fromString(key), ); try { final result = await removeOperation.result; stdout ..writeln('Remove completed.') - ..writeln('Removed object: ${result.removedItem.key}'); + ..writeln('Removed object: ${result.removedItem.path}'); } on Exception catch (error) { stderr ..writeln('Something went wrong...') @@ -495,32 +401,6 @@ String prompt(String prompt) { return value; } -StorageAccessLevel promptStorageAccessLevel(String message) { - int? value; - bool valueInRange(int? value) { - return [1, 2, 3].contains(value); - } - - while (value == null || !valueInRange(value)) { - stdout.write('''$message: -1. guest -2. protected -3. private -'''); - final input = stdin.readLineSync(encoding: utf8); - value = int.tryParse(input ?? ''); - } - - var accessLevel = StorageAccessLevel.guest; - if (value == 2) { - accessLevel = StorageAccessLevel.protected; - } else if (value == 3) { - accessLevel = StorageAccessLevel.private; - } - - return accessLevel; -} - bool promptUseAcceleration() { String input; diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/amplify_storage_s3_dart_impl.dart b/packages/storage/amplify_storage_s3_dart/lib/src/amplify_storage_s3_dart_impl.dart index 9e63131c8e..b8547c8b94 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/amplify_storage_s3_dart_impl.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/amplify_storage_s3_dart_impl.dart @@ -8,6 +8,7 @@ import 'package:amplify_core/amplify_core.dart'; import 'package:amplify_db_common_dart/amplify_db_common_dart.dart' as db_common; import 'package:amplify_storage_s3_dart/amplify_storage_s3_dart.dart'; +import 'package:amplify_storage_s3_dart/src/path_resolver/s3_path_resolver.dart'; import 'package:amplify_storage_s3_dart/src/platform_impl/download_file/download_file.dart' as download_file_impl; import 'package:amplify_storage_s3_dart/src/prefix_resolver/storage_access_level_aware_prefix_resolver.dart'; @@ -58,6 +59,8 @@ class AmplifyStorageS3Dart extends StoragePluginInterface S3PrefixResolver? _prefixResolver; + late S3PathResolver _pathResolver; + /// Gets prefix resolver for testing @visibleForTesting S3PrefixResolver? get prefixResolver => _prefixResolver; @@ -96,6 +99,10 @@ class AmplifyStorageS3Dart extends StoragePluginInterface identityProvider: identityProvider, ); + _pathResolver = S3PathResolver( + identityProvider: identityProvider, + ); + final credentialsProvider = authProviderRepo .getAuthProvider(APIAuthorizationType.iam.authProviderToken); @@ -116,7 +123,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface credentialsProvider: credentialsProvider, s3PluginConfig: s3PluginConfig, delimiter: _delimiter, - prefixResolver: _prefixResolver!, + pathResolver: _pathResolver, logger: logger, dependencyManager: dependencies, ), @@ -133,7 +140,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface @override S3ListOperation list({ - String? path, + required StoragePath path, StorageListOptions? options, }) { final s3PluginOptions = reifyPluginOptions( @@ -141,7 +148,6 @@ class AmplifyStorageS3Dart extends StoragePluginInterface defaultPluginOptions: const S3ListPluginOptions(), ); final s3Options = StorageListOptions( - accessLevel: options?.accessLevel, pluginOptions: s3PluginOptions, nextToken: options?.nextToken, pageSize: options?.pageSize ?? 1000, @@ -161,7 +167,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface @override S3GetPropertiesOperation getProperties({ - required String key, + required StoragePath path, StorageGetPropertiesOptions? options, }) { final s3PluginOptions = reifyPluginOptions( @@ -170,17 +176,16 @@ class AmplifyStorageS3Dart extends StoragePluginInterface ); final s3Options = StorageGetPropertiesOptions( - accessLevel: options?.accessLevel, pluginOptions: s3PluginOptions, ); return S3GetPropertiesOperation( request: StorageGetPropertiesRequest( - key: key, + path: path, options: options, ), result: storageS3Service.getProperties( - key: key, + path: path, options: s3Options, ), ); @@ -188,7 +193,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface @override S3GetUrlOperation getUrl({ - required String key, + required StoragePath path, StorageGetUrlOptions? options, }) { final s3PluginOptions = reifyPluginOptions( @@ -197,17 +202,16 @@ class AmplifyStorageS3Dart extends StoragePluginInterface ); final s3Options = StorageGetUrlOptions( - accessLevel: options?.accessLevel, pluginOptions: s3PluginOptions, ); return S3GetUrlOperation( request: StorageGetUrlRequest( - key: key, + path: path, options: options, ), result: storageS3Service.getUrl( - key: key, + path: path, options: s3Options, ), ); @@ -215,7 +219,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface @override S3DownloadDataOperation downloadData({ - required String key, + required StoragePath path, StorageDownloadDataOptions? options, void Function(S3TransferProgress)? onProgress, }) { @@ -225,13 +229,12 @@ class AmplifyStorageS3Dart extends StoragePluginInterface ); final s3Options = StorageDownloadDataOptions( - accessLevel: options?.accessLevel, pluginOptions: s3PluginOptions, ); final bytes = BytesBuilder(); final downloadTask = storageS3Service.downloadData( - key: key, + path: path, options: s3Options, onProgress: onProgress, onData: bytes.add, @@ -239,7 +242,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface return S3DownloadDataOperation( request: StorageDownloadDataRequest( - key: key, + path: path, options: options, ), result: downloadTask.result.then( @@ -256,7 +259,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface @override S3DownloadFileOperation downloadFile({ - required String key, + required StoragePath path, required AWSFile localFile, void Function(S3TransferProgress)? onProgress, StorageDownloadFileOptions? options, @@ -266,11 +269,10 @@ class AmplifyStorageS3Dart extends StoragePluginInterface defaultPluginOptions: const S3DownloadFilePluginOptions(), ); options = StorageDownloadFileOptions( - accessLevel: options?.accessLevel, pluginOptions: s3PluginOptions, ); return download_file_impl.downloadFile( - key: key, + path: path, localFile: localFile, options: options, s3pluginConfig: s3pluginConfig, @@ -283,7 +285,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface @override S3UploadDataOperation uploadData({ required StorageDataPayload data, - required String key, + required StoragePath path, void Function(S3TransferProgress)? onProgress, StorageUploadDataOptions? options, }) { @@ -293,13 +295,12 @@ class AmplifyStorageS3Dart extends StoragePluginInterface ); final s3Options = StorageUploadDataOptions( - accessLevel: options?.accessLevel, metadata: options?.metadata ?? const {}, pluginOptions: s3PluginOptions, ); final uploadTask = storageS3Service.uploadData( - key: key, + path: path, dataPayload: data, options: s3Options, onProgress: onProgress, @@ -307,8 +308,8 @@ class AmplifyStorageS3Dart extends StoragePluginInterface return S3UploadDataOperation( request: StorageUploadDataRequest( + path: path, data: data, - key: key, options: options, ), result: uploadTask.result.then( @@ -321,7 +322,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface @override S3UploadFileOperation uploadFile({ required AWSFile localFile, - required String key, + required StoragePath path, void Function(S3TransferProgress)? onProgress, StorageUploadFileOptions? options, }) { @@ -331,13 +332,12 @@ class AmplifyStorageS3Dart extends StoragePluginInterface ); final s3Options = StorageUploadFileOptions( - accessLevel: options?.accessLevel, metadata: options?.metadata ?? const {}, pluginOptions: s3PluginOptions, ); final uploadTask = storageS3Service.uploadFile( - key: key, + path: path, localFile: localFile, options: s3Options, onProgress: onProgress, @@ -345,8 +345,8 @@ class AmplifyStorageS3Dart extends StoragePluginInterface return S3UploadFileOperation( request: StorageUploadFileRequest( + path: path, localFile: localFile, - key: key, options: options, ), result: uploadTask.result.then( @@ -360,13 +360,10 @@ class AmplifyStorageS3Dart extends StoragePluginInterface @override S3CopyOperation copy({ - required StorageItemWithAccessLevel source, - required StorageItemWithAccessLevel destination, + required StoragePath source, + required StoragePath destination, StorageCopyOptions? options, }) { - final s3Source = S3ItemWithAccessLevel.from(source); - final s3Destination = S3ItemWithAccessLevel.from(destination); - final s3PluginOptions = reifyPluginOptions( pluginOptions: options?.pluginOptions, defaultPluginOptions: const S3CopyPluginOptions(), @@ -378,45 +375,13 @@ class AmplifyStorageS3Dart extends StoragePluginInterface return S3CopyOperation( request: StorageCopyRequest( - source: s3Source, - destination: s3Destination, + source: source, + destination: destination, options: options, ), result: storageS3Service.copy( - source: s3Source, - destination: s3Destination, - options: s3Options, - ), - ); - } - - @override - S3MoveOperation move({ - required StorageItemWithAccessLevel source, - required StorageItemWithAccessLevel destination, - StorageMoveOptions? options, - }) { - final s3PluginOptions = reifyPluginOptions( - pluginOptions: options?.pluginOptions, - defaultPluginOptions: const S3MovePluginOptions(), - ); - - final s3Options = StorageMoveOptions( - pluginOptions: s3PluginOptions, - ); - - final s3Source = S3ItemWithAccessLevel.from(source); - final s3Destination = S3ItemWithAccessLevel.from(destination); - - return S3MoveOperation( - request: StorageMoveRequest( - source: s3Source, - destination: s3Destination, - options: options, - ), - result: storageS3Service.move( - source: s3Source, - destination: s3Destination, + source: source, + destination: destination, options: s3Options, ), ); @@ -424,7 +389,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface @override S3RemoveOperation remove({ - required String key, + required StoragePath path, StorageRemoveOptions? options, }) { final s3PluginOptions = reifyPluginOptions( @@ -433,17 +398,16 @@ class AmplifyStorageS3Dart extends StoragePluginInterface ); final s3Options = StorageRemoveOptions( - accessLevel: options?.accessLevel, pluginOptions: s3PluginOptions, ); return S3RemoveOperation( request: StorageRemoveRequest( - key: key, + path: path, options: options, ), result: storageS3Service.remove( - key: key, + path: path, options: s3Options, ), ); @@ -451,7 +415,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface @override S3RemoveManyOperation removeMany({ - required List keys, + required List paths, StorageRemoveManyOptions? options, }) { final s3PluginOptions = reifyPluginOptions( @@ -460,17 +424,16 @@ class AmplifyStorageS3Dart extends StoragePluginInterface ); final s3Options = StorageRemoveManyOptions( - accessLevel: options?.accessLevel, pluginOptions: s3PluginOptions, ); return S3RemoveManyOperation( request: StorageRemoveManyRequest( - keys: keys, + paths: paths, options: options, ), result: storageS3Service.removeMany( - keys: keys, + paths: paths, options: s3Options, ), ); diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/exception/s3_storage_exception.dart b/packages/storage/amplify_storage_s3_dart/lib/src/exception/s3_storage_exception.dart index ebc24536fc..520277ee82 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/exception/s3_storage_exception.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/exception/s3_storage_exception.dart @@ -8,8 +8,7 @@ import 'package:meta/meta.dart'; import 'package:smithy/smithy.dart' as smithy; const _keyNotFoundRecoveryMessage = - 'Ensure that correct object key is provided, and/or correct `StorageAccessLevel`' - ' and `targetIdentityId` are included in the options.'; + 'Ensure that correct StoragePath is provided.'; const _httpErrorRecoveryMessage = 'HTTP error returned from service, review the `underlyingException` for details.'; @@ -34,7 +33,7 @@ extension NoSuchKeyToStorageKeyNotFoundException on s3.NoSuchKey { /// underlying exception. StorageKeyNotFoundException toStorageKeyNotFoundException() { return StorageKeyNotFoundException( - 'Cannot find the item specified by the key and access level.', + 'Cannot find the item specified by the provided path.', recoverySuggestion: _keyNotFoundRecoveryMessage, underlyingException: this, ); @@ -66,7 +65,7 @@ extension UnknownSmithyHttpExceptionToStorageException ); } else if (statusCode == 404) { return StorageKeyNotFoundException( - 'Cannot find the item specified by the key and access level.', + 'Cannot find the item specified by the provided path.', recoverySuggestion: _keyNotFoundRecoveryMessage, underlyingException: this, ); diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_download_data_options.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_download_data_options.dart index 87e99bb7fc..f55ff8389a 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_download_data_options.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_download_data_options.dart @@ -16,12 +16,10 @@ class S3DownloadDataOptions extends StorageDownloadDataOptions { 'use StorageDownloadDataOptions(pluginOptions:S3DownloadDataPluginOptions(...)) instead.', ) const S3DownloadDataOptions({ - StorageAccessLevel accessLevel = StorageAccessLevel.guest, bool getProperties = false, S3DataBytesRange? bytesRange, bool useAccelerateEndpoint = false, }) : this._( - accessLevel: accessLevel, bytesRange: bytesRange, getProperties: getProperties, useAccelerateEndpoint: useAccelerateEndpoint, @@ -30,42 +28,14 @@ class S3DownloadDataOptions extends StorageDownloadDataOptions { 'use StorageDownloadDataOptions(pluginOptions:S3DownloadDataPluginOptions(...)) instead.', ) const S3DownloadDataOptions._({ - super.accessLevel = StorageAccessLevel.guest, this.getProperties = false, this.bytesRange, - this.targetIdentityId, this.useAccelerateEndpoint = false, }); - /// {@macro storage.amplify_storage_s3.download_data_options} - /// - /// Use this when calling `downloadData` on an object that belongs to another - /// user (identified by [targetIdentityId]) rather than the currently - /// signed-in user. - @Deprecated( - 'use StorageDownloadDataOptions(pluginOptions:S3DownloadDataPluginOptions(...)) instead.', - ) - const S3DownloadDataOptions.forIdentity( - String targetIdentityId, { - bool getProperties = false, - S3DataBytesRange? bytesRange, - bool useAccelerateEndpoint = false, - }) : this._( - accessLevel: StorageAccessLevel.protected, - targetIdentityId: targetIdentityId, - getProperties: getProperties, - bytesRange: bytesRange, - useAccelerateEndpoint: useAccelerateEndpoint, - ); - /// The byte range to download from the object. final S3DataBytesRange? bytesRange; - /// The identity ID of another user who uploaded the object. - /// - /// This can be set by using [S3DownloadDataOptions.forIdentity]. - final String? targetIdentityId; - /// Whether to retrieve properties for the downloaded object using the /// `getProperties` API. final bool getProperties; diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_download_data_plugin_options.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_download_data_plugin_options.dart index 526ebdb757..d86a188b36 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_download_data_plugin_options.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_download_data_plugin_options.dart @@ -25,27 +25,9 @@ class S3DownloadDataPluginOptions extends StorageDownloadDataPluginOptions { const S3DownloadDataPluginOptions._({ this.getProperties = false, this.bytesRange, - this.targetIdentityId, this.useAccelerateEndpoint = false, }); - /// {@macro storage.amplify_storage_s3.download_data_plugin_options} - /// - /// Use this when calling `downloadData` on an object that belongs to another - /// user (identified by [targetIdentityId]) rather than the currently - /// signed-in user. - const S3DownloadDataPluginOptions.forIdentity( - String targetIdentityId, { - bool getProperties = false, - S3DataBytesRange? bytesRange, - bool useAccelerateEndpoint = false, - }) : this._( - targetIdentityId: targetIdentityId, - getProperties: getProperties, - bytesRange: bytesRange, - useAccelerateEndpoint: useAccelerateEndpoint, - ); - /// {@macro storage.amplify_storage_s3.download_data_plugin_options} factory S3DownloadDataPluginOptions.fromJson(Map json) => _$S3DownloadDataPluginOptionsFromJson(json); @@ -53,11 +35,6 @@ class S3DownloadDataPluginOptions extends StorageDownloadDataPluginOptions { /// The byte range to download from the object. final S3DataBytesRange? bytesRange; - /// The identity ID of another user who uploaded the object. - /// - /// This can be set by using [S3DownloadDataPluginOptions.forIdentity]. - final String? targetIdentityId; - /// Whether to retrieve properties for the downloaded object using the /// `getProperties` API. final bool getProperties; @@ -73,7 +50,6 @@ class S3DownloadDataPluginOptions extends StorageDownloadDataPluginOptions { getProperties, useAccelerateEndpoint, bytesRange, - targetIdentityId, ]; @override diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_download_file_options.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_download_file_options.dart index 8cc40ca3be..c151fd1ae3 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_download_file_options.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_download_file_options.dart @@ -15,11 +15,9 @@ class S3DownloadFileOptions extends StorageDownloadFileOptions { 'use StorageDownloadFileOptions(pluginOptions:S3DownloadFilePluginOptions(...)) instead.', ) const S3DownloadFileOptions({ - StorageAccessLevel accessLevel = StorageAccessLevel.guest, bool getProperties = false, bool useAccelerateEndpoint = false, }) : this._( - accessLevel: accessLevel, getProperties: getProperties, useAccelerateEndpoint: useAccelerateEndpoint, ); @@ -28,36 +26,10 @@ class S3DownloadFileOptions extends StorageDownloadFileOptions { 'use StorageDownloadFileOptions(pluginOptions:S3DownloadFilePluginOptions(...)) instead.', ) const S3DownloadFileOptions._({ - super.accessLevel = StorageAccessLevel.guest, this.getProperties = false, - this.targetIdentityId, this.useAccelerateEndpoint = false, }); - /// {@macro storage.amplify_storage_s3.download_data_options} - /// - /// Use this when calling `downloadFile` on an object that belongs to other - /// user (identified by [targetIdentityId]) rather than the currently signed - /// user. - @Deprecated( - 'use StorageDownloadFileOptions(pluginOptions:S3DownloadFilePluginOptions.forIdentity(...)) instead.', - ) - const S3DownloadFileOptions.forIdentity( - String targetIdentityId, { - bool getProperties = false, - bool useAccelerateEndpoint = false, - }) : this._( - accessLevel: StorageAccessLevel.protected, - targetIdentityId: targetIdentityId, - getProperties: getProperties, - useAccelerateEndpoint: useAccelerateEndpoint, - ); - - /// The identity ID of the user who uploaded the object. - /// - /// This can be set by using [S3DownloadFileOptions.forIdentity]. - final String? targetIdentityId; - /// Whether to retrieve properties for the downloaded object using the /// `getProperties` API. final bool getProperties; diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_download_file_plugin_options.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_download_file_plugin_options.dart index fad767b307..43d159d297 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_download_file_plugin_options.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_download_file_plugin_options.dart @@ -21,34 +21,13 @@ class S3DownloadFilePluginOptions extends StorageDownloadFilePluginOptions { const S3DownloadFilePluginOptions._({ this.getProperties = false, - this.targetIdentityId, this.useAccelerateEndpoint = false, }); - /// {@macro storage.amplify_storage_s3.download_file_plugin_options} - /// - /// Use this when calling `downloadFile` on an object that belongs to other - /// user (identified by [targetIdentityId]) rather than the currently signed - /// user. - const S3DownloadFilePluginOptions.forIdentity( - String targetIdentityId, { - bool getProperties = false, - bool useAccelerateEndpoint = false, - }) : this._( - targetIdentityId: targetIdentityId, - getProperties: getProperties, - useAccelerateEndpoint: useAccelerateEndpoint, - ); - /// {@macro storage.amplify_storage_s3.download_file_plugin_options} factory S3DownloadFilePluginOptions.fromJson(Map json) => _$S3DownloadFilePluginOptionsFromJson(json); - /// The identity ID of the user who uploaded the object. - /// - /// This can be set by using [S3DownloadFilePluginOptions.forIdentity]. - final String? targetIdentityId; - /// Whether to retrieve properties for the downloaded object using the /// `getProperties` API. final bool getProperties; @@ -60,7 +39,6 @@ class S3DownloadFilePluginOptions extends StorageDownloadFilePluginOptions { List get props => [ getProperties, useAccelerateEndpoint, - targetIdentityId, ]; @override diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_get_properties_options.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_get_properties_options.dart index 50e82a5123..41eb7be303 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_get_properties_options.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_get_properties_options.dart @@ -14,37 +14,10 @@ class S3GetPropertiesOptions extends StorageGetPropertiesOptions { @Deprecated( 'use StorageGetPropertiesOptions(pluginOptions: S3GetPropertiesPluginOptions(...)) instead.', ) - const S3GetPropertiesOptions({ - StorageAccessLevel accessLevel = StorageAccessLevel.guest, - }) : this._( - accessLevel: accessLevel, - ); + const S3GetPropertiesOptions() : this._(); @Deprecated( 'use StorageGetPropertiesOptions(pluginOptions: S3GetPropertiesPluginOptions(...)) instead.', ) - const S3GetPropertiesOptions._({ - super.accessLevel = StorageAccessLevel.guest, - this.targetIdentityId, - }); - - /// {@macro storage.amplify_storage_s3.get_properties_options} - /// - /// Use this when calling `getProperties` on an object that belongs to other - /// user (identified by [targetIdentityId]) rather than the currently signed - /// user. - @Deprecated( - 'use StorageGetPropertiesOptions(pluginOptions: S3GetPropertiesPluginOptions.forIdentity(...)) instead.', - ) - const S3GetPropertiesOptions.forIdentity( - String targetIdentityId, - ) : this._( - accessLevel: StorageAccessLevel.protected, - targetIdentityId: targetIdentityId, - ); - - /// The identity ID of another user who uploaded the object. - /// - /// This can be set by using [S3GetPropertiesOptions.forIdentity]. - final String? targetIdentityId; + const S3GetPropertiesOptions._(); } diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_get_properties_plugin_options.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_get_properties_plugin_options.dart index f9a80ebefe..4b9915d63e 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_get_properties_plugin_options.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_get_properties_plugin_options.dart @@ -13,32 +13,14 @@ class S3GetPropertiesPluginOptions extends StorageGetPropertiesPluginOptions { /// {@macro storage.amplify_storage_s3.get_properties_plugin_options} const S3GetPropertiesPluginOptions() : this._(); - const S3GetPropertiesPluginOptions._({ - this.targetIdentityId, - }); - - /// {@macro storage.amplify_storage_s3.get_properties_plugin_options} - /// - /// Use this when calling `getProperties` on an object that belongs to other - /// user (identified by [targetIdentityId]) rather than the currently signed - /// user. - const S3GetPropertiesPluginOptions.forIdentity( - String targetIdentityId, - ) : this._( - targetIdentityId: targetIdentityId, - ); + const S3GetPropertiesPluginOptions._(); /// {@macro storage.amplify_storage_s3.get_properties_plugin_options} factory S3GetPropertiesPluginOptions.fromJson(Map json) => _$S3GetPropertiesPluginOptionsFromJson(json); - /// The identity ID of another user who uploaded the object. - /// - /// This can be set by using [S3GetPropertiesPluginOptions.forIdentity]. - final String? targetIdentityId; - @override - List get props => [targetIdentityId]; + List get props => []; @override String get runtimeTypeName => 'S3GetPropertiesPluginOptions'; diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_get_url_options.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_get_url_options.dart index 8e70c6ef49..421ce22d82 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_get_url_options.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_get_url_options.dart @@ -16,12 +16,10 @@ class S3GetUrlOptions extends StorageGetUrlOptions { 'use StorageGetUrlOptions(pluginOptions: S3GetUrlPluginOptions(...)) instead.', ) const S3GetUrlOptions({ - StorageAccessLevel accessLevel = StorageAccessLevel.guest, Duration expiresIn = const Duration(minutes: 15), bool validateObjectExistence = false, bool useAccelerateEndpoint = false, }) : this._( - accessLevel: accessLevel, expiresIn: expiresIn, validateObjectExistence: validateObjectExistence, useAccelerateEndpoint: useAccelerateEndpoint, @@ -30,33 +28,11 @@ class S3GetUrlOptions extends StorageGetUrlOptions { 'use StorageGetUrlOptions(pluginOptions: S3GetUrlPluginOptions(...)) instead.', ) const S3GetUrlOptions._({ - super.accessLevel = StorageAccessLevel.guest, this.expiresIn = const Duration(minutes: 15), this.validateObjectExistence = false, - this.targetIdentityId, this.useAccelerateEndpoint = false, }); - /// {@macro storage.amplify_storage_s3.get_url_options} - /// - /// Use this when calling `getUrl` on an object that belongs to other user - /// (identified by [targetIdentityId]) rather than the currently signed user. - @Deprecated( - 'use StorageGetUrlOptions(pluginOptions: S3GetUrlPluginOptions.forIdentity(...)) instead.', - ) - const S3GetUrlOptions.forIdentity( - String targetIdentityId, { - Duration expiresIn = const Duration(minutes: 15), - bool validateObjectExistence = false, - bool useAccelerateEndpoint = false, - }) : this._( - accessLevel: StorageAccessLevel.protected, - expiresIn: expiresIn, - validateObjectExistence: validateObjectExistence, - targetIdentityId: targetIdentityId, - useAccelerateEndpoint: useAccelerateEndpoint, - ); - /// Specifies the period of time that the generated url expires in. final Duration expiresIn; @@ -64,11 +40,6 @@ class S3GetUrlOptions extends StorageGetUrlOptions { /// a presigned url. final bool validateObjectExistence; - /// The identity ID of another user who uploaded the object. - /// - /// This can be set by using [S3GetUrlOptions.forIdentity]. - final String? targetIdentityId; - /// {@macro storage.amplify_storage_s3.transfer_acceleration} final bool useAccelerateEndpoint; } diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_get_url_plugin_options.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_get_url_plugin_options.dart index 76862c306a..fa890e8b83 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_get_url_plugin_options.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_get_url_plugin_options.dart @@ -25,26 +25,9 @@ class S3GetUrlPluginOptions extends StorageGetUrlPluginOptions { const S3GetUrlPluginOptions._({ this.expiresIn = const Duration(minutes: 15), this.validateObjectExistence = false, - this.targetIdentityId, this.useAccelerateEndpoint = false, }); - /// {@macro storage.amplify_storage_s3.get_url_plugin_options} - /// - /// Use this when calling `getUrl` on an object that belongs to other user - /// (identified by [targetIdentityId]) rather than the currently signed user. - const S3GetUrlPluginOptions.forIdentity( - String targetIdentityId, { - Duration expiresIn = const Duration(minutes: 15), - bool validateObjectExistence = false, - bool useAccelerateEndpoint = false, - }) : this._( - expiresIn: expiresIn, - validateObjectExistence: validateObjectExistence, - targetIdentityId: targetIdentityId, - useAccelerateEndpoint: useAccelerateEndpoint, - ); - /// {@macro storage.amplify_storage_s3.get_url_plugin_options} factory S3GetUrlPluginOptions.fromJson(Map json) => _$S3GetUrlPluginOptionsFromJson(json); @@ -56,11 +39,6 @@ class S3GetUrlPluginOptions extends StorageGetUrlPluginOptions { /// a presigned url. final bool validateObjectExistence; - /// The identity ID of another user who uploaded the object. - /// - /// This can be set by using [S3GetUrlPluginOptions.forIdentity]. - final String? targetIdentityId; - /// {@macro storage.amplify_storage_s3.transfer_acceleration} final bool useAccelerateEndpoint; @@ -69,7 +47,6 @@ class S3GetUrlPluginOptions extends StorageGetUrlPluginOptions { expiresIn, validateObjectExistence, useAccelerateEndpoint, - targetIdentityId, ]; @override diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_item.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_item.dart index c0238cf6dc..eb816e0ae3 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_item.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_item.dart @@ -19,7 +19,7 @@ class S3Item extends StorageItem AWSDebuggable { /// {@macro storage.amplify_storage_s3.storage_s3_item} S3Item({ - required super.key, + required super.path, super.size, super.lastModified, super.eTag, @@ -33,7 +33,7 @@ class S3Item extends StorageItem return storageItem is S3Item ? storageItem : S3Item( - key: storageItem.key, + path: storageItem.path, size: storageItem.size, lastModified: storageItem.lastModified, eTag: storageItem.eTag, @@ -47,9 +47,8 @@ class S3Item extends StorageItem /// Creates a [S3Item] from [s3.S3Object] provided by S3 Client. @internal factory S3Item.fromS3Object( - s3.S3Object object, { - required String prefixToDrop, - }) { + s3.S3Object object, + ) { final key = object.key; // Sanity check, key property should never be null in a S3Object returned @@ -61,13 +60,8 @@ class S3Item extends StorageItem ); } - final keyDroppedPrefix = dropPrefixFromKey( - prefixToDrop: prefixToDrop, - key: key, - ); - return S3Item( - key: keyDroppedPrefix, + path: key, size: object.size?.toInt(), lastModified: object.lastModified, eTag: object.eTag, @@ -79,10 +73,10 @@ class S3Item extends StorageItem @internal factory S3Item.fromHeadObjectOutput( s3.HeadObjectOutput headObjectOutput, { - required String key, + required String path, }) { return S3Item( - key: key, + path: path, lastModified: headObjectOutput.lastModified, eTag: headObjectOutput.eTag, metadata: headObjectOutput.metadata?.toMap() ?? const {}, @@ -97,10 +91,10 @@ class S3Item extends StorageItem @internal factory S3Item.fromGetObjectOutput( s3.GetObjectOutput getObjectOutput, { - required String key, + required String path, }) { return S3Item( - key: key, + path: path, lastModified: getObjectOutput.lastModified, eTag: getObjectOutput.eTag, metadata: getObjectOutput.metadata?.toMap() ?? const {}, @@ -110,15 +104,6 @@ class S3Item extends StorageItem ); } - /// Removes [prefixToDrop] from [key] string. - @internal - static String dropPrefixFromKey({ - required String prefixToDrop, - required String key, - }) { - return key.replaceRange(0, prefixToDrop.length, ''); - } - /// Object `versionId`, may be available when S3 bucket versioning is enabled. final String? versionId; @@ -127,7 +112,7 @@ class S3Item extends StorageItem @override List get props => [ - key, + path, size, lastModified, eTag, diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_item.g.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_item.g.dart index db2c8a9479..6ba820001c 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_item.g.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_item.g.dart @@ -7,7 +7,7 @@ part of 's3_item.dart'; // ************************************************************************** S3Item _$S3ItemFromJson(Map json) => S3Item( - key: json['key'] as String, + path: json['path'] as String, size: json['size'] as int?, lastModified: json['lastModified'] == null ? null @@ -23,7 +23,7 @@ S3Item _$S3ItemFromJson(Map json) => S3Item( Map _$S3ItemToJson(S3Item instance) { final val = { - 'key': instance.key, + 'path': instance.path, }; void writeNotNull(String key, dynamic value) { diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_item_with_access_level.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_item_with_access_level.dart deleted file mode 100644 index 267996ca2f..0000000000 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_item_with_access_level.dart +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import 'package:amplify_core/amplify_core.dart'; -import 'package:amplify_storage_s3_dart/amplify_storage_s3_dart.dart'; - -/// {@template storage.amplify_storage_s3.storage_item_access_level} -/// A [S3Item] with its associated access level information. -/// {@endtemplate} -class S3ItemWithAccessLevel extends StorageItemWithAccessLevel { - /// {@macro storage.amplify_storage_s3.storage_item_access_level} - const S3ItemWithAccessLevel({ - required S3Item storageItem, - StorageAccessLevel accessLevel = StorageAccessLevel.guest, - }) : this._( - storageItem: storageItem, - accessLevel: accessLevel, - ); - - /// Creates [S3ItemWithAccessLevel] from [StorageItemWithAccessLevel]. - factory S3ItemWithAccessLevel.from( - StorageItemWithAccessLevel storageItem, - ) { - return storageItem is S3ItemWithAccessLevel - ? storageItem - : S3ItemWithAccessLevel( - storageItem: S3Item.from(storageItem.storageItem), - accessLevel: storageItem.accessLevel, - ); - } - - const S3ItemWithAccessLevel._({ - super.accessLevel = StorageAccessLevel.guest, - required super.storageItem, - this.targetIdentityId, - }); - - /// {@macro storage.amplify_storage_s3.storage_item_access_level} - /// - /// Use when creating the `source` calling `copy` API, and the contained - /// `storageItem` belongs to another user (identified by `targetIdentityId`) - /// rather than the currently signed in user. - const S3ItemWithAccessLevel.forIdentity( - String targetIdentityId, { - required S3Item storageItem, - }) : this._( - storageItem: storageItem, - accessLevel: StorageAccessLevel.protected, - targetIdentityId: targetIdentityId, - ); - - /// The identity ID of another user who uploaded the object that is - /// represented by the containing [storageItem]. - /// - /// This can be set by using [S3ItemWithAccessLevel.forIdentity]. - final String? targetIdentityId; -} diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_list_options.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_list_options.dart index 20163109f9..1fec5d1536 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_list_options.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_list_options.dart @@ -15,12 +15,10 @@ class S3ListOptions extends StorageListOptions { 'use StorageListOptions(pluginOptions: S3ListPluginOptions(...)) instead.', ) const S3ListOptions({ - StorageAccessLevel accessLevel = StorageAccessLevel.guest, int pageSize = 1000, bool excludeSubPaths = false, String? nextToken, }) : this._( - accessLevel: accessLevel, pageSize: pageSize, nextToken: nextToken, excludeSubPaths: excludeSubPaths, @@ -30,34 +28,12 @@ class S3ListOptions extends StorageListOptions { 'use StorageListOptions(pluginOptions: S3ListPluginOptions(...)) instead.', ) const S3ListOptions._({ - super.accessLevel = StorageAccessLevel.guest, super.pageSize = 1000, super.nextToken, - this.targetIdentityId, this.excludeSubPaths = false, this.listAll = false, }); - /// {@macro storage.amplify_storage_s3.list_options} - /// - /// Use this to list objects that belongs to other user (identified by - /// [targetIdentityId]) rather than the currently signed in user. - @Deprecated( - 'use StorageListOptions(pluginOptions: S3ListPluginOptions.forIdentity(...)) instead.', - ) - const S3ListOptions.forIdentity( - String targetIdentityId, { - int pageSize = 1000, - bool excludeSubPaths = false, - String? nextToken, - }) : this._( - accessLevel: StorageAccessLevel.protected, - pageSize: pageSize, - targetIdentityId: targetIdentityId, - nextToken: nextToken, - excludeSubPaths: excludeSubPaths, - ); - /// {@macro storage.amplify_storage_s3.list_options} /// /// Use this to list all objects without pagination. @@ -65,36 +41,12 @@ class S3ListOptions extends StorageListOptions { 'use StorageListOptions(pluginOptions: S3ListPluginOptions(...)) instead.', ) const S3ListOptions.listAll({ - StorageAccessLevel accessLevel = StorageAccessLevel.guest, bool excludeSubPaths = false, }) : this._( - accessLevel: accessLevel, excludeSubPaths: excludeSubPaths, listAll: true, ); - /// {@macro storage.amplify_storage_s3.list_options} - /// - /// Use this to list all objects without pagination when the objects belong - /// to other user (identified by [targetIdentityId]) rather than the currently - /// signed in user. - @Deprecated( - 'use StorageListOptions(pluginOptions: S3ListPluginOptions(...)) instead.', - ) - const S3ListOptions.listAllForIdentity( - String targetIdentityId, { - bool excludeSubPaths = false, - }) : this._( - accessLevel: StorageAccessLevel.protected, - targetIdentityId: targetIdentityId, - excludeSubPaths: excludeSubPaths, - ); - - /// The identity ID of another user who uploaded the objects. - /// - /// This can be set by using [S3ListOptions.forIdentity]. - final String? targetIdentityId; - /// Whether to exclude objects under the sub paths of the path to list. The /// default value is `false`. final bool excludeSubPaths; @@ -102,8 +54,7 @@ class S3ListOptions extends StorageListOptions { /// Whether to list all objects under a given path without pagination. The /// default value is `false`. /// - /// This can be set by using [S3ListOptions.listAll] or - /// [S3ListOptions.listAllForIdentity]. + /// This can be set by using [S3ListOptions.listAll]. /// /// Use with caution if numerous objects are under the given path. final bool listAll; diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_list_plugin_options.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_list_plugin_options.dart index 5b4bfca883..ebebdbbc41 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_list_plugin_options.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_list_plugin_options.dart @@ -18,23 +18,10 @@ class S3ListPluginOptions extends StorageListPluginOptions { ); const S3ListPluginOptions._({ - this.targetIdentityId, this.excludeSubPaths = false, this.listAll = false, }); - /// {@macro storage.amplify_storage_s3.list_plugin_options} - /// - /// Use this to list objects that belongs to other user (identified by - /// [targetIdentityId]) rather than the currently signed in user. - const S3ListPluginOptions.forIdentity( - String targetIdentityId, { - bool excludeSubPaths = false, - }) : this._( - targetIdentityId: targetIdentityId, - excludeSubPaths: excludeSubPaths, - ); - /// {@macro storage.amplify_storage_s3.list_plugin_options} /// /// Use this to list all objects without pagination. @@ -45,28 +32,10 @@ class S3ListPluginOptions extends StorageListPluginOptions { listAll: true, ); - /// {@macro storage.amplify_storage_s3.list_plugin_options} - /// - /// Use this to list all objects without pagination when the objects belong - /// to other user (identified by [targetIdentityId]) rather than the currently - /// signed in user. - const S3ListPluginOptions.listAllForIdentity( - String targetIdentityId, { - bool excludeSubPaths = false, - }) : this._( - targetIdentityId: targetIdentityId, - excludeSubPaths: excludeSubPaths, - ); - /// {@macro storage.amplify_storage_s3.list_plugin_options} factory S3ListPluginOptions.fromJson(Map json) => _$S3ListPluginOptionsFromJson(json); - /// The identity ID of another user who uploaded the objects. - /// - /// This can be set by using [S3ListPluginOptions.forIdentity]. - final String? targetIdentityId; - /// Whether to exclude objects under the sub paths of the path to list. The /// default value is `false`. final bool excludeSubPaths; @@ -74,8 +43,7 @@ class S3ListPluginOptions extends StorageListPluginOptions { /// Whether to list all objects under a given path without pagination. The /// default value is `false`. /// - /// This can be set by using [S3ListPluginOptions.listAll] or - /// [S3ListPluginOptions.listAllForIdentity]. + /// This can be set by using [S3ListPluginOptions.listAll]. /// /// Use with caution if numerous objects are under the given path. final bool listAll; @@ -84,7 +52,6 @@ class S3ListPluginOptions extends StorageListPluginOptions { List get props => [ excludeSubPaths, listAll, - targetIdentityId, ]; @override diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_list_result.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_list_result.dart index f8b6cef2ad..a9ba5ca0fe 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_list_result.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_list_result.dart @@ -23,27 +23,17 @@ class S3ListResult extends StorageListResult { /// smithy. This named constructor should be used internally only. @internal factory S3ListResult.fromPaginatedResult( - PaginatedResult paginatedResult, { - required String prefixToDrop, - }) { + PaginatedResult paginatedResult, + ) { final output = paginatedResult.items; final metadata = S3ListMetadata.fromS3CommonPrefixes( - prefixToDrop: prefixToDrop, commonPrefixes: output.commonPrefixes?.toList(), delimiter: output.delimiter, ); - final items = output.contents - ?.map( - (s3.S3Object item) => S3Item.fromS3Object( - item, - prefixToDrop: prefixToDrop, - ), - ) - .toList() ?? - const []; + final items = output.contents?.map(S3Item.fromS3Object).toList(); return S3ListResult( - items, + items ?? const [], hasNextPage: paginatedResult.hasNext, nextToken: paginatedResult.nextContinuationToken, metadata: metadata, @@ -71,28 +61,16 @@ class S3ListMetadata { /// Creates a S3ListMetadata from the `commonPrefix` and `delimiter` /// properties of the [s3.ListObjectsV2Output]. factory S3ListMetadata.fromS3CommonPrefixes({ - required String prefixToDrop, List? commonPrefixes, String? delimiter, }) { - final extractedSubPath = []; - - if (commonPrefixes != null) { - for (final commonPrefix in commonPrefixes) { - final prefix = commonPrefix.prefix; - if (prefix != null) { - extractedSubPath.add( - S3Item.dropPrefixFromKey( - prefixToDrop: prefixToDrop, - key: prefix, - ), - ); - } - } - } + final subPaths = commonPrefixes + ?.map((commonPrefix) => commonPrefix.prefix) + .whereType() + .toList(); return S3ListMetadata._( - subPaths: extractedSubPath, + subPaths: subPaths, delimiter: delimiter, ); } diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_models.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_models.dart index 270cfa45c2..4fbcfc854c 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_models.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_models.dart @@ -24,15 +24,10 @@ export 's3_get_url_options.dart'; export 's3_get_url_plugin_options.dart'; export 's3_get_url_result.dart'; export 's3_item.dart'; -export 's3_item_with_access_level.dart'; export 's3_list_operation.dart'; export 's3_list_options.dart'; export 's3_list_plugin_options.dart'; export 's3_list_result.dart'; -export 's3_move_operation.dart'; -export 's3_move_options.dart'; -export 's3_move_plugin_options.dart'; -export 's3_move_result.dart'; export 's3_remove_many_operation.dart'; export 's3_remove_many_options.dart'; export 's3_remove_many_plugin_options.dart'; diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_move_operation.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_move_operation.dart deleted file mode 100644 index 7eefde5939..0000000000 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_move_operation.dart +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import 'package:amplify_core/amplify_core.dart'; -import 'package:amplify_storage_s3_dart/amplify_storage_s3_dart.dart'; - -/// {@template storage.amplify_storage_s3.move_operation} -/// An operation created by calling the Storage S3 plugin `copy` API. -/// {@endtemplate} -class S3MoveOperation - extends StorageMoveOperation, S3MoveResult> { - /// {@macro storage.amplify_storage_s3.move_operation} - S3MoveOperation({ - required super.request, - required super.result, - }); -} diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_move_options.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_move_options.dart deleted file mode 100644 index bed6a0c89b..0000000000 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_move_options.dart +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import 'package:amplify_core/amplify_core.dart'; - -/// {@template storage.amplify_storage_s3.move_options} -/// The configurable parameters invoking the Storage S3 plugin `copy` -/// API. -/// {@endtemplate} -@Deprecated( - 'use StorageMoveOptions(pluginOptions: S3MovePluginOptions(...)) instead.', -) -class S3MoveOptions extends StorageMoveOptions { - /// {@macro storage.amplify_storage_s3.move_options} - @Deprecated( - 'use StorageMoveOptions(pluginOptions: S3MovePluginOptions(...)) instead.', - ) - const S3MoveOptions({ - this.getProperties = false, - }); - - /// Whether to retrieve properties for the moved object using the - /// `getProperties` API. - final bool getProperties; -} diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_move_plugin_options.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_move_plugin_options.dart deleted file mode 100644 index e2c8cc92e9..0000000000 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_move_plugin_options.dart +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import 'package:amplify_core/amplify_core.dart'; - -part 's3_move_plugin_options.g.dart'; - -/// {@template storage.amplify_storage_s3.move_plugin_options} -/// The configurable parameters invoking the Storage S3 plugin `move` API. -/// {@endtemplate} -@zAmplifySerializable -class S3MovePluginOptions extends StorageMovePluginOptions { - /// {@macro storage.amplify_storage_s3.move_plugin_options} - const S3MovePluginOptions({ - this.getProperties = false, - }); - - /// {@macro storage.amplify_storage_s3.move_plugin_options} - factory S3MovePluginOptions.fromJson(Map json) => - _$S3MovePluginOptionsFromJson(json); - - /// Whether to retrieve properties for the moved object using the - /// `getProperties` API. - final bool getProperties; - - @override - List get props => [getProperties]; - - @override - String get runtimeTypeName => 'S3MovePluginOptions'; - - @override - Map toJson() => _$S3MovePluginOptionsToJson(this); -} diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_move_plugin_options.g.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_move_plugin_options.g.dart deleted file mode 100644 index 3ff6ab9492..0000000000 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_move_plugin_options.g.dart +++ /dev/null @@ -1,18 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 's3_move_plugin_options.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -S3MovePluginOptions _$S3MovePluginOptionsFromJson(Map json) => - S3MovePluginOptions( - getProperties: json['getProperties'] as bool? ?? false, - ); - -Map _$S3MovePluginOptionsToJson( - S3MovePluginOptions instance) => - { - 'getProperties': instance.getProperties, - }; diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_move_result.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_move_result.dart deleted file mode 100644 index 2a5d5944fe..0000000000 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_move_result.dart +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import 'package:amplify_core/amplify_core.dart'; -import 'package:amplify_storage_s3_dart/amplify_storage_s3_dart.dart'; - -/// {@template storage.amplify_storage_s3.move_result} -/// The result returned by the Storage S3 plugin `move` API. -/// {@endtemplate} -class S3MoveResult extends StorageMoveResult { - /// {@macro storage.amplify_storage_s3.move_result} - const S3MoveResult({ - required super.movedItem, - }); -} diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_remove_many_options.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_remove_many_options.dart index f3f9e0e2b1..c1621df30d 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_remove_many_options.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_remove_many_options.dart @@ -15,7 +15,5 @@ class S3RemoveManyOptions extends StorageRemoveManyOptions { @Deprecated( 'use StorageRemoveManyOptions(pluginOptions: S3RemoveManyPluginOptions(...)) instead.', ) - const S3RemoveManyOptions({ - super.accessLevel = StorageAccessLevel.guest, - }); + const S3RemoveManyOptions(); } diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_remove_options.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_remove_options.dart index 859845714f..3bf28fc4dd 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_remove_options.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_remove_options.dart @@ -15,7 +15,5 @@ class S3RemoveOptions extends StorageRemoveOptions { @Deprecated( 'use StorageRemoveOptions(pluginOptions: S3RemovePluginOptions(...)) instead.', ) - const S3RemoveOptions({ - super.accessLevel = StorageAccessLevel.guest, - }); + const S3RemoveOptions(); } diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_upload_data_options.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_upload_data_options.dart index a6befc2751..7b56db9f54 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_upload_data_options.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_upload_data_options.dart @@ -16,7 +16,6 @@ class S3UploadDataOptions extends StorageUploadDataOptions { 'use StorageUploadDataOptions(pluginOptions: S3UploadDataPluginOptions(...)) instead.', ) const S3UploadDataOptions({ - super.accessLevel = StorageAccessLevel.guest, this.getProperties = false, super.metadata, this.useAccelerateEndpoint = false, diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_upload_file_options.dart b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_upload_file_options.dart index 3d79c24de2..b7f919ab01 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_upload_file_options.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/model/s3_upload_file_options.dart @@ -16,7 +16,6 @@ class S3UploadFileOptions extends StorageUploadFileOptions { 'use StorageUploadFileOptions(pluginOptions: S3UploadFilePluginOptions(...)) instead.', ) const S3UploadFileOptions({ - super.accessLevel = StorageAccessLevel.guest, this.getProperties = false, super.metadata, this.useAccelerateEndpoint = false, diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/path_resolver/s3_path_resolver.dart b/packages/storage/amplify_storage_s3_dart/lib/src/path_resolver/s3_path_resolver.dart new file mode 100644 index 0000000000..c98119f645 --- /dev/null +++ b/packages/storage/amplify_storage_s3_dart/lib/src/path_resolver/s3_path_resolver.dart @@ -0,0 +1,91 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:amplify_core/amplify_core.dart'; +// ignore: implementation_imports +import 'package:amplify_core/src/types/storage/storage_path_from_identity_id.dart'; +import 'package:meta/meta.dart'; + +/// {@template amplify_storage_s3_dart.path_resolver} +/// A class which resolves the full path of the S3 Object. +/// {@endtemplate} +@internal +class S3PathResolver { + /// {@macro amplify_storage_s3_dart.path_resolver} + const S3PathResolver({ + required TokenIdentityAmplifyAuthProvider identityProvider, + }) : _identityProvider = identityProvider; + + final TokenIdentityAmplifyAuthProvider _identityProvider; + + Future _getIdentityId() async { + try { + return await _identityProvider.getIdentityId(); + } on Exception catch (e) { + if (e is SessionExpiredException || e is AuthNotAuthorizedException) { + throw StorageAccessDeniedException( + 'Unable to fetch the identity Id. This can occur when a user is not ' + 'authenticated and the Identity Pool does not allow unauthenticated sessions. ' + 'See underlyingException for more info.', + underlyingException: e, + ); + } + throw UnknownException( + 'An unknown error occurred while fetching the identity Id. ' + 'See underlyingException for more info.', + underlyingException: e, + ); + } + } + + /// Resolves the given [path]. + /// + /// Returns a string which is the S3 Object Key. + /// + /// When [path] is a [StoragePathFromIdentityId], the current user's identity + /// Id will be fetched in order to build the path. + Future resolvePath({ + required StoragePath path, + String? identityId, + }) async { + final resolvedPath = switch (path) { + // ignore: invalid_use_of_internal_member + final StoragePathFromIdentityId p => p.resolvePath( + identityId: identityId ?? await _getIdentityId(), + ), + // ignore: invalid_use_of_internal_member + _ => path.resolvePath() + }; + if (resolvedPath.isEmpty) { + throw const StoragePathValidationException( + 'StoragePath cannot be empty', + ); + } + if (resolvedPath.startsWith('/')) { + throw const StoragePathValidationException( + 'StoragePath cannot start with a leading "/"', + recoverySuggestion: 'Remove the leading "/" from the StoragePath', + ); + } + return resolvedPath; + } + + /// Resolves multiple [StoragePath] objects. + /// + /// This method will only fetch the current user's identityId one time. + Future> resolvePaths({ + required List paths, + }) async { + final requiresIdentityId = + paths.whereType().isNotEmpty; + final identityId = requiresIdentityId ? await _getIdentityId() : null; + return Future.wait( + paths.map( + (path) => resolvePath( + path: path, + identityId: identityId, + ), + ), + ); + } +} diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_html.dart b/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_html.dart index f6e64dd076..3d2b750855 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_html.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_html.dart @@ -8,7 +8,7 @@ import 'package:amplify_storage_s3_dart/src/storage_s3_service/storage_s3_servic /// The html implementation of `downloadFile` API. S3DownloadFileOperation downloadFile({ - required String key, + required StoragePath path, required AWSFile localFile, required StorageDownloadFileOptions options, required S3PluginConfig s3pluginConfig, @@ -20,12 +20,12 @@ S3DownloadFileOperation downloadFile({ return S3DownloadFileOperation( request: StorageDownloadFileRequest( - key: key, + path: path, localFile: localFile, options: options, ), result: _downloadFromUrl( - key: key, + path: path, localFile: localFile, options: options, s3pluginConfig: s3pluginConfig, @@ -40,14 +40,13 @@ S3DownloadFileOperation downloadFile({ } Future _downloadFromUrl({ - required String key, + required StoragePath path, required AWSFile localFile, required StorageDownloadFileOptions options, required S3PluginConfig s3pluginConfig, required StorageS3Service storageS3Service, }) async { final s3PluginOptions = options.pluginOptions as S3DownloadFilePluginOptions; - final targetIdentityId = s3PluginOptions.targetIdentityId; // Calling the `getProperties` by default to verify the existence of the object // before downloading from the presigned URL, as the 404 or 403 should be // handled by the plugin but not be thrown to an end user in browser. @@ -56,16 +55,8 @@ Future _downloadFromUrl({ // Exception thrown from the getProperties will be thrown as download // operation. final downloadedItem = (await storageS3Service.getProperties( - key: key, - options: targetIdentityId == null - ? StorageGetPropertiesOptions( - accessLevel: options.accessLevel, - ) - : StorageGetPropertiesOptions( - accessLevel: options.accessLevel, - pluginOptions: - S3GetPropertiesPluginOptions.forIdentity(targetIdentityId), - ), + path: path, + options: const StorageGetPropertiesOptions(), )) .storageItem; @@ -73,21 +64,12 @@ Future _downloadFromUrl({ // We are not setting validateObjectExistence to true here as we are not // able to directly get the result of underlying HeadObject result. final url = (await storageS3Service.getUrl( - key: key, - options: targetIdentityId == null - ? StorageGetUrlOptions( - accessLevel: options.accessLevel, - pluginOptions: S3GetUrlPluginOptions( - useAccelerateEndpoint: s3PluginOptions.useAccelerateEndpoint, - ), - ) - : StorageGetUrlOptions( - accessLevel: options.accessLevel, - pluginOptions: S3GetUrlPluginOptions.forIdentity( - targetIdentityId, - useAccelerateEndpoint: s3PluginOptions.useAccelerateEndpoint, - ), - ), + path: path, + options: StorageGetUrlOptions( + pluginOptions: S3GetUrlPluginOptions( + useAccelerateEndpoint: s3PluginOptions.useAccelerateEndpoint, + ), + ), )) .url; @@ -100,8 +82,9 @@ Future _downloadFromUrl({ ); return S3DownloadFileResult( - downloadedItem: - s3PluginOptions.getProperties ? downloadedItem : S3Item(key: key), + downloadedItem: s3PluginOptions.getProperties + ? downloadedItem + : S3Item(path: downloadedItem.path), localFile: localFile, ); } diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_io.dart b/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_io.dart index 52e9a1e501..246cf8a3ae 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_io.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_io.dart @@ -7,12 +7,12 @@ import 'package:amplify_core/amplify_core.dart'; import 'package:amplify_storage_s3_dart/amplify_storage_s3_dart.dart'; import 'package:amplify_storage_s3_dart/src/storage_s3_service/storage_s3_service.dart'; import 'package:meta/meta.dart'; -import 'package:path/path.dart' as path; +import 'package:path/path.dart' as p; /// The io implementation of `downloadFile` API. @internal S3DownloadFileOperation downloadFile({ - required String key, + required StoragePath path, required AWSFile localFile, required StorageDownloadFileOptions options, required S3PluginConfig s3pluginConfig, @@ -25,30 +25,22 @@ S3DownloadFileOperation downloadFile({ late final File tempFile; final s3PluginOptions = options.pluginOptions as S3DownloadFilePluginOptions; - final targetIdentityId = s3PluginOptions.targetIdentityId; final downloadDataOptions = StorageDownloadDataOptions( - accessLevel: options.accessLevel, - pluginOptions: targetIdentityId == null - ? S3DownloadDataPluginOptions( - getProperties: s3PluginOptions.getProperties, - useAccelerateEndpoint: s3PluginOptions.useAccelerateEndpoint, - ) - : S3DownloadDataPluginOptions.forIdentity( - targetIdentityId, - getProperties: s3PluginOptions.getProperties, - useAccelerateEndpoint: s3PluginOptions.useAccelerateEndpoint, - ), + pluginOptions: S3DownloadDataPluginOptions( + getProperties: s3PluginOptions.getProperties, + useAccelerateEndpoint: s3PluginOptions.useAccelerateEndpoint, + ), ); final downloadDataTask = storageS3Service.downloadData( - key: key, + path: path, options: downloadDataOptions, // Ensure destination file is writable. Exception thrown in the check // will be forwarded to the Future, downloadDataTask.result below preStart: () async { destinationPath = await _ensureDestinationWritable(localFile); tempFile = File( - path.join( + p.join( await appPathProvider.getTemporaryPath(), 'amplify_storage_s3_temp_${uuid()}', ), @@ -78,7 +70,7 @@ S3DownloadFileOperation downloadFile({ return S3DownloadFileOperation( request: StorageDownloadFileRequest( - key: key, + path: path, localFile: localFile, options: options, ), diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_stub.dart b/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_stub.dart index 81fd034fd1..94eb03c0e9 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_stub.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/platform_impl/download_file/download_file_stub.dart @@ -7,7 +7,7 @@ import 'package:amplify_storage_s3_dart/src/storage_s3_service/storage_s3_servic /// Interface of the platform implementation of the `downloadFile` API. S3DownloadFileOperation downloadFile({ - required String key, + required StoragePath path, required AWSFile localFile, required StorageDownloadFileOptions options, required S3PluginConfig s3pluginConfig, diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/storage_s3_service_impl.dart b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/storage_s3_service_impl.dart index 926d20d0e9..a8c7505117 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/storage_s3_service_impl.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/storage_s3_service_impl.dart @@ -8,6 +8,7 @@ import 'package:amplify_core/amplify_core.dart' hide PaginatedResult; import 'package:amplify_storage_s3_dart/amplify_storage_s3_dart.dart'; import 'package:amplify_storage_s3_dart/src/exception/s3_storage_exception.dart' as s3_exception; +import 'package:amplify_storage_s3_dart/src/path_resolver/s3_path_resolver.dart'; import 'package:amplify_storage_s3_dart/src/sdk/s3.dart' as s3; import 'package:amplify_storage_s3_dart/src/sdk/src/s3/common/endpoint_resolver.dart' as endpoint_resolver; @@ -31,7 +32,7 @@ class StorageS3Service { /// {@macro amplify_storage_s3.storage_s3_service} factory StorageS3Service({ required S3PluginConfig s3PluginConfig, - required S3PrefixResolver prefixResolver, + required S3PathResolver pathResolver, required AWSIamAmplifyAuthProvider credentialsProvider, required AWSLogger logger, required DependencyManager dependencyManager, @@ -56,7 +57,7 @@ class StorageS3Service { return StorageS3Service._( s3PluginConfig: s3PluginConfig, s3ClientConfig: s3ClientConfig, - prefixResolver: prefixResolver, + pathResolver: pathResolver, credentialsProvider: credentialsProvider, logger: logger, dependencyManager: dependencyManager, @@ -67,7 +68,7 @@ class StorageS3Service { StorageS3Service._({ required S3PluginConfig s3PluginConfig, required smithy_aws.S3ClientConfig s3ClientConfig, - required S3PrefixResolver prefixResolver, + required S3PathResolver pathResolver, required AWSIamAmplifyAuthProvider credentialsProvider, required AWSLogger logger, required DependencyManager dependencyManager, @@ -84,7 +85,7 @@ class StorageS3Service { client: AmplifyHttpClient(dependencyManager) ..supportedProtocols = SupportedProtocols.http1, ), - _prefixResolver = prefixResolver, + _pathResolver = pathResolver, _logger = logger, // dependencyManager.get() => sigv4.AWSSigV4Signer is used for unit tests _awsSigV4Signer = dependencyManager.get() ?? @@ -101,7 +102,7 @@ class StorageS3Service { final String _delimiter; final smithy_aws.S3ClientConfig _defaultS3ClientConfig; final s3.S3Client _defaultS3Client; - final S3PrefixResolver _prefixResolver; + final S3PathResolver _pathResolver; final AWSLogger _logger; final sigv4.AWSSigV4Signer _awsSigV4Signer; final DependencyManager _dependencyManager; @@ -125,25 +126,19 @@ class StorageS3Service { /// service returned [smithy.UnknownSmithyHttpException] if any. /// {@endtemplate} Future list({ - String? path, + required StoragePath path, required StorageListOptions options, }) async { final s3PluginOptions = options.pluginOptions as S3ListPluginOptions? ?? const S3ListPluginOptions(); - final resolvedPrefix = await getResolvedPrefix( - prefixResolver: _prefixResolver, - logger: _logger, - accessLevel: options.accessLevel ?? _s3PluginConfig.defaultAccessLevel, - identityId: s3PluginOptions.targetIdentityId, - ); - final listTargetPrefix = '$resolvedPrefix${path ?? ''}'; + final resolvedPath = await _pathResolver.resolvePath(path: path); if (!s3PluginOptions.listAll) { final request = s3.ListObjectsV2Request.build((builder) { builder ..bucket = _s3PluginConfig.bucket - ..prefix = listTargetPrefix + ..prefix = resolvedPath ..maxKeys = options.pageSize ..continuationToken = options.nextToken ..delimiter = s3PluginOptions.excludeSubPaths ? _delimiter : null; @@ -152,7 +147,6 @@ class StorageS3Service { try { return S3ListResult.fromPaginatedResult( await _defaultS3Client.listObjectsV2(request).result, - prefixToDrop: resolvedPrefix, ); } on smithy.UnknownSmithyHttpException catch (error) { // S3Client.headObject may return 403 error @@ -169,14 +163,13 @@ class StorageS3Service { final request = s3.ListObjectsV2Request.build((builder) { builder ..bucket = _s3PluginConfig.bucket - ..prefix = listTargetPrefix + ..prefix = resolvedPath ..delimiter = s3PluginOptions.excludeSubPaths ? _delimiter : null; }); listResult = await _defaultS3Client.listObjectsV2(request).result; recursiveResult = S3ListResult.fromPaginatedResult( listResult, - prefixToDrop: resolvedPrefix, ); while (listResult.hasNext) { @@ -184,7 +177,6 @@ class StorageS3Service { recursiveResult = recursiveResult.merge( S3ListResult.fromPaginatedResult( listResult, - prefixToDrop: resolvedPrefix, ), ); } @@ -205,30 +197,19 @@ class StorageS3Service { /// /// {@macro storage.s3_service.throw_exception_unknown_smithy_exception} Future getProperties({ - required String key, + required StoragePath path, required StorageGetPropertiesOptions options, }) async { - final s3PluginOptions = - options.pluginOptions as S3GetPropertiesPluginOptions? ?? - const S3GetPropertiesPluginOptions(); - - final resolvedPrefix = await getResolvedPrefix( - prefixResolver: _prefixResolver, - logger: _logger, - accessLevel: options.accessLevel ?? _s3PluginConfig.defaultAccessLevel, - identityId: s3PluginOptions.targetIdentityId, - ); - - final keyToGetProperties = '$resolvedPrefix$key'; + final resolvedPath = await _pathResolver.resolvePath(path: path); return S3GetPropertiesResult( storageItem: S3Item.fromHeadObjectOutput( await headObject( s3client: _defaultS3Client, bucket: _s3PluginConfig.bucket, - key: keyToGetProperties, + key: resolvedPath, ), - key: key, + path: resolvedPath, ), ); } @@ -239,7 +220,7 @@ class StorageS3Service { /// /// {@macro storage.s3_service.throw_exception_unknown_smithy_exception} Future getUrl({ - required String key, + required StoragePath path, required StorageGetUrlOptions options, }) async { final s3PluginOptions = options.pluginOptions as S3GetUrlPluginOptions? ?? @@ -254,39 +235,18 @@ class StorageS3Service { // make a HeadObject call for validating object existence // the validation may throw exceptions that are thrown from // the `getProperties` API (i.e. HeadObject) - final targetIdentityId = s3PluginOptions.targetIdentityId; - final getPropertiesOptions = targetIdentityId == null - ? StorageGetPropertiesOptions( - accessLevel: options.accessLevel, - ) - : StorageGetPropertiesOptions( - accessLevel: options.accessLevel, - pluginOptions: - S3GetPropertiesPluginOptions.forIdentity(targetIdentityId), - ); await getProperties( - key: key, - options: getPropertiesOptions, + path: path, + options: const StorageGetPropertiesOptions(), ); } - final resolvedPrefix = await getResolvedPrefix( - prefixResolver: _prefixResolver, - logger: _logger, - accessLevel: options.accessLevel ?? _s3PluginConfig.defaultAccessLevel, - identityId: s3PluginOptions.targetIdentityId, - ); - var keyToGetUrl = '$resolvedPrefix$key'; - if (!keyToGetUrl.startsWith('/')) { - keyToGetUrl = '/$keyToGetUrl'; - } - + var resolvedPath = await _pathResolver.resolvePath(path: path); var host = '${_s3PluginConfig.bucket}.${_getS3EndpointHost(region: _s3PluginConfig.region)}'; - if (_defaultS3ClientConfig.usePathStyle) { host = host.replaceFirst('${_s3PluginConfig.bucket}.', ''); - keyToGetUrl = '/${_s3PluginConfig.bucket}$keyToGetUrl'; + resolvedPath = '${_s3PluginConfig.bucket}/$resolvedPath'; } else if (s3PluginOptions.useAccelerateEndpoint) { // https: //docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration-getting-started.html host = host @@ -297,7 +257,7 @@ class StorageS3Service { final urlRequest = AWSHttpRequest.raw( method: AWSHttpMethod.get, host: host, - path: keyToGetUrl, + path: '/$resolvedPath', ); return S3GetUrlResult( @@ -325,7 +285,7 @@ class StorageS3Service { /// /// {@macro amplify_storage_s3_dart.download_task.on_done} S3DownloadTask downloadData({ - required String key, + required StoragePath path, required StorageDownloadDataOptions options, FutureOr Function()? preStart, void Function(S3TransferProgress)? onProgress, @@ -337,11 +297,9 @@ class StorageS3Service { s3Client: _defaultS3Client, defaultS3ClientConfig: _defaultS3ClientConfig, bucket: _s3PluginConfig.bucket, - defaultAccessLevel: _s3PluginConfig.defaultAccessLevel, - key: key, + path: path, options: options, - prefixResolver: _prefixResolver, - logger: _logger, + pathResolver: _pathResolver, onProgress: onProgress, onData: onData, preStart: preStart, @@ -358,7 +316,7 @@ class StorageS3Service { /// a [S3UploadTask], to start the upload process, then returns the /// [S3UploadTask]. S3UploadTask uploadData({ - required String key, + required StoragePath path, required S3DataPayload dataPayload, required StorageUploadDataOptions options, void Function(S3TransferProgress)? onProgress, @@ -370,10 +328,9 @@ class StorageS3Service { s3Client: _defaultS3Client, defaultS3ClientConfig: _defaultS3ClientConfig, bucket: _s3PluginConfig.bucket, - defaultAccessLevel: _s3PluginConfig.defaultAccessLevel, - key: key, + path: path, options: options, - prefixResolver: _prefixResolver, + pathResolver: _pathResolver, logger: _logger, onProgress: onProgress, transferDatabase: _transferDatabase, @@ -388,7 +345,7 @@ class StorageS3Service { /// a [S3UploadTask], to start the upload process, then returns the /// [S3UploadTask]. S3UploadTask uploadFile({ - required String key, + required StoragePath path, required AWSFile localFile, required StorageUploadFileOptions options, void Function(S3TransferProgress)? onProgress, @@ -399,7 +356,6 @@ class StorageS3Service { options.pluginOptions as S3UploadFilePluginOptions? ?? const S3UploadFilePluginOptions(); final uploadDataOptions = StorageUploadDataOptions( - accessLevel: options.accessLevel, metadata: options.metadata, pluginOptions: S3UploadDataPluginOptions( getProperties: s3PluginOptions.getProperties, @@ -410,10 +366,9 @@ class StorageS3Service { s3Client: _defaultS3Client, defaultS3ClientConfig: _defaultS3ClientConfig, bucket: _s3PluginConfig.bucket, - defaultAccessLevel: _s3PluginConfig.defaultAccessLevel, - key: key, + path: path, options: uploadDataOptions, - prefixResolver: _prefixResolver, + pathResolver: _pathResolver, logger: _logger, onProgress: onProgress, transferDatabase: _transferDatabase, @@ -437,36 +392,22 @@ class StorageS3Service { /// /// {@macro storage.s3_service.throw_exception_unknown_smithy_exception} Future copy({ - required S3ItemWithAccessLevel source, - required S3ItemWithAccessLevel destination, + required StoragePath source, + required StoragePath destination, required StorageCopyOptions options, }) async { final s3PluginOptions = options.pluginOptions as S3CopyPluginOptions? ?? const S3CopyPluginOptions(); - final resolvedPrefixes = await Future.wait([ - getResolvedPrefix( - prefixResolver: _prefixResolver, - logger: _logger, - accessLevel: source.accessLevel, - identityId: source.targetIdentityId, - ), - getResolvedPrefix( - prefixResolver: _prefixResolver, - logger: _logger, - accessLevel: destination.accessLevel, - identityId: destination.targetIdentityId, - ), - ]); - final sourceKey = '${resolvedPrefixes[0]}${source.storageItem.key}'; - final destinationKey = - '${resolvedPrefixes[1]}${destination.storageItem.key}'; + final [sourcePath, destinationPath] = await _pathResolver.resolvePaths( + paths: [source, destination], + ); final copyRequest = s3.CopyObjectRequest.build((builder) { builder ..bucket = _s3PluginConfig.bucket - ..copySource = '${_s3PluginConfig.bucket}/$sourceKey' - ..key = destinationKey + ..copySource = '${_s3PluginConfig.bucket}/$sourcePath' + ..key = destinationPath ..metadataDirective = s3.MetadataDirective.copy; }); @@ -485,85 +426,14 @@ class StorageS3Service { await headObject( s3client: _defaultS3Client, bucket: _s3PluginConfig.bucket, - key: destinationKey, + key: destinationPath, ), - key: destination.storageItem.key, + path: destinationPath, ) - : S3Item(key: destination.storageItem.key), - ); - } - - /// Takes in input from [AmplifyStorageS3Dart.move] API to compose a - /// [s3.CopyObjectRequest] and send to S3 service to copy `source` to - /// `destination`, followed by a [s3.DeleteObjectRequest] to delete the - /// `source`, then returns a [S3MoveResult] based on the `key` of - /// `destination`. - /// - /// When [S3CopyPluginOptions.getProperties] is set to `true`, when both - /// [s3.CopyObjectRequest] and [s3.DeleteObjectRequest] succeed, the API - /// creates a [s3.HeadObjectRequest] with the `key` of the `destination`, - /// and sends to S3 Service, then returns a [S3CopyResult] based on - /// the [s3.HeadObjectOutput] returned by [s3.S3Client.headObject] API. - /// - /// {@macro storage.s3_service.throw_exception_unknown_smithy_exception} - Future move({ - required S3ItemWithAccessLevel source, - required S3ItemWithAccessLevel destination, - required StorageMoveOptions options, - }) async { - final s3PluginOptions = options.pluginOptions as S3MovePluginOptions? ?? - const S3MovePluginOptions(); - - late S3CopyResult copyResult; - - try { - copyResult = await copy( - source: source, - destination: destination, - options: StorageCopyOptions( - pluginOptions: options.pluginOptions != null - ? S3CopyPluginOptions( - getProperties: s3PluginOptions.getProperties, - ) - : const S3CopyPluginOptions(), - ), - ); - } on StorageException catch (error) { - // Normally copy should not fail during moving, if it fails it's a - // "unknown" failure wrapping the underlying exception. - throw UnknownException( - 'Copying the source object failed during the move operation.', - recoverySuggestion: 'Review the underlying exception.', - underlyingException: error, - ); - } - - final resolvedSourcePrefix = await getResolvedPrefix( - prefixResolver: _prefixResolver, - logger: _logger, - accessLevel: source.accessLevel, - identityId: source.targetIdentityId, + : S3Item( + path: destinationPath, + ), ); - - final keyToRemove = '$resolvedSourcePrefix${source.storageItem.key}'; - - try { - await _deleteObject( - s3client: _defaultS3Client, - bucket: _s3PluginConfig.bucket, - key: keyToRemove, - ); - } on StorageException catch (error) { - // Normally delete should not fail during moving, if it fails it's a - // "unknown" failure wrapping the underlying exception. - throw UnknownException( - 'Deleting the source object failed during the move operation.', - recoverySuggestion: 'Review the underlying exception.', - underlyingException: error, - ); - } - - return S3MoveResult(movedItem: copyResult.copiedItem); } /// Takes in input from [AmplifyStorageS3Dart.remove] API to compose a @@ -572,25 +442,19 @@ class StorageS3Service { /// /// {@macro storage.s3_service.throw_exception_unknown_smithy_exception} Future remove({ - required String key, + required StoragePath path, required StorageRemoveOptions options, }) async { - final resolvedPrefix = await getResolvedPrefix( - prefixResolver: _prefixResolver, - logger: _logger, - accessLevel: options.accessLevel ?? _s3PluginConfig.defaultAccessLevel, - ); - - final keyToRemove = '$resolvedPrefix$key'; + final resolvedPath = await _pathResolver.resolvePath(path: path); await _deleteObject( s3client: _defaultS3Client, bucket: _s3PluginConfig.bucket, - key: keyToRemove, + key: resolvedPath, ); return S3RemoveResult( - removedItem: S3Item(key: key), + removedItem: S3Item(path: resolvedPath), ); } @@ -601,21 +465,17 @@ class StorageS3Service { /// /// {@macro storage.s3_service.throw_exception_unknown_smithy_exception} Future removeMany({ - required List keys, + required List paths, required StorageRemoveManyOptions options, }) async { // Each request can contain up to 1000 objects to remove // https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html const defaultBatchSize = 1000; - final resolvedPrefix = await getResolvedPrefix( - prefixResolver: _prefixResolver, - logger: _logger, - accessLevel: options.accessLevel ?? _s3PluginConfig.defaultAccessLevel, - ); - final objectIdentifiersToRemove = keys - .map((key) => s3.ObjectIdentifier(key: '$resolvedPrefix$key')) - .toList(); + final resolvedPaths = await _pathResolver.resolvePaths(paths: paths); + + final objectIdentifiersToRemove = + resolvedPaths.map((path) => s3.ObjectIdentifier(key: path)).toList(); final removedItems = []; final removedErrors = []; @@ -642,7 +502,6 @@ class StorageS3Service { output.deleted?.toList().map( (removedObject) => S3Item.fromS3Object( s3.S3Object(key: removedObject.key), - prefixToDrop: resolvedPrefix, ), ) ?? [], diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/task/s3_download_task.dart b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/task/s3_download_task.dart index cb16ea5468..3415447a3a 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/task/s3_download_task.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/task/s3_download_task.dart @@ -7,8 +7,8 @@ import 'package:amplify_core/amplify_core.dart'; import 'package:amplify_storage_s3_dart/amplify_storage_s3_dart.dart'; import 'package:amplify_storage_s3_dart/src/exception/s3_storage_exception.dart' as s3_exception; +import 'package:amplify_storage_s3_dart/src/path_resolver/s3_path_resolver.dart'; import 'package:amplify_storage_s3_dart/src/sdk/s3.dart' as s3; -import 'package:amplify_storage_s3_dart/src/storage_s3_service/storage_s3_service.dart'; import 'package:meta/meta.dart'; import 'package:smithy/smithy.dart' as smithy; import 'package:smithy_aws/smithy_aws.dart' as smithy_aws; @@ -50,31 +50,26 @@ class S3DownloadTask { S3DownloadTask({ required s3.S3Client s3Client, required smithy_aws.S3ClientConfig defaultS3ClientConfig, - required S3PrefixResolver prefixResolver, - required StorageAccessLevel defaultAccessLevel, + required S3PathResolver pathResolver, required String bucket, - required String key, + required StoragePath path, required StorageDownloadDataOptions options, FutureOr Function()? preStart, void Function(S3TransferProgress)? onProgress, void Function(List)? onData, FutureOr Function()? onDone, FutureOr Function()? onError, - required AWSLogger logger, }) : _downloadCompleter = Completer(), _s3Client = s3Client, _defaultS3ClientConfig = defaultS3ClientConfig, - _prefixResolver = prefixResolver, + _pathResolver = pathResolver, _bucket = bucket, - _defaultAccessLevel = defaultAccessLevel, - _key = key, - _downloadDataOptions = options, + _path = path, _preStart = preStart, _onProgress = onProgress, _onData = onData, _onDone = onDone, _onError = onError, - _logger = logger, _downloadedBytesSize = 0, _s3PluginOptions = options.pluginOptions as S3DownloadDataPluginOptions? ?? @@ -85,17 +80,14 @@ class S3DownloadTask { final s3.S3Client _s3Client; final smithy_aws.S3ClientConfig _defaultS3ClientConfig; - final S3PrefixResolver _prefixResolver; + final S3PathResolver _pathResolver; final String _bucket; - final StorageAccessLevel _defaultAccessLevel; - final String _key; - final StorageDownloadDataOptions _downloadDataOptions; + final StoragePath _path; final FutureOr Function()? _preStart; final void Function(S3TransferProgress)? _onProgress; final void Function(List bytes)? _onData; final FutureOr Function()? _onDone; final FutureOr Function()? _onError; - final AWSLogger _logger; final S3DownloadDataPluginOptions _s3PluginOptions; int _downloadedBytesSize; @@ -110,7 +102,7 @@ class S3DownloadTask { Completer? _pauseCompleter; late StorageTransferState _state; - late final String _resolvedKey; + late final String _resolvedPath; late final S3Item _downloadedS3Item; // Total bytes that need to be downloaded, this field is set when the @@ -145,19 +137,12 @@ class S3DownloadTask { return; } - final resolvedPrefix = await StorageS3Service.getResolvedPrefix( - prefixResolver: _prefixResolver, - logger: _logger, - accessLevel: _downloadDataOptions.accessLevel ?? _defaultAccessLevel, - identityId: _s3PluginOptions.targetIdentityId, - ); - - _resolvedKey = '$resolvedPrefix$_key'; + _resolvedPath = await _pathResolver.resolvePath(path: _path); try { final getObjectOutput = await _getObject( bucket: _bucket, - key: _resolvedKey, + key: _resolvedPath, bytesRange: _s3PluginOptions.bytesRange, ); @@ -176,7 +161,7 @@ class S3DownloadTask { _totalBytes = remoteSize; _listenToBytesSteam(getObjectOutput.body); _downloadedS3Item = - S3Item.fromGetObjectOutput(getObjectOutput, key: _key); + S3Item.fromGetObjectOutput(getObjectOutput, path: _resolvedPath); } on Exception catch (error, stackTrace) { await _completeDownloadWithError(error, stackTrace); } @@ -227,7 +212,7 @@ class S3DownloadTask { try { final getObjectOutput = await _getObject( bucket: _bucket, - key: _resolvedKey, + key: _resolvedPath, bytesRange: bytesRangeToDownload, ); _listenToBytesSteam(getObjectOutput.body); @@ -311,7 +296,7 @@ class S3DownloadTask { // is set to `false`. _s3PluginOptions.getProperties ? _downloadedS3Item - : S3Item(key: _downloadedS3Item.key), + : S3Item(path: _downloadedS3Item.path), ); } on Exception catch (error, stackTrace) { await _completeDownloadWithError(error, stackTrace); diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/task/s3_upload_task.dart b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/task/s3_upload_task.dart index 63cba5a0b4..dc0fac48bd 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/task/s3_upload_task.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/task/s3_upload_task.dart @@ -8,6 +8,7 @@ import 'package:amplify_core/amplify_core.dart'; import 'package:amplify_storage_s3_dart/amplify_storage_s3_dart.dart'; import 'package:amplify_storage_s3_dart/src/exception/s3_storage_exception.dart' as s3_exception; +import 'package:amplify_storage_s3_dart/src/path_resolver/s3_path_resolver.dart'; import 'package:amplify_storage_s3_dart/src/sdk/s3.dart' as s3; import 'package:amplify_storage_s3_dart/src/storage_s3_service/service/task/part_size_util.dart' as part_size_util; @@ -48,10 +49,9 @@ class S3UploadTask { S3UploadTask._({ required s3.S3Client s3Client, required smithy_aws.S3ClientConfig defaultS3ClientConfig, - required S3PrefixResolver prefixResolver, + required S3PathResolver pathResolver, required String bucket, - required StorageAccessLevel defaultAccessLevel, - required String key, + required StoragePath path, required StorageUploadDataOptions options, S3DataPayload? dataPayload, AWSFile? localFile, @@ -60,10 +60,9 @@ class S3UploadTask { required transfer.TransferDatabase transferDatabase, }) : _s3Client = s3Client, _defaultS3ClientConfig = defaultS3ClientConfig, - _prefixResolver = prefixResolver, + _pathResolver = pathResolver, _bucket = bucket, - _defaultAccessLevel = defaultAccessLevel, - _key = key, + _path = path, _options = options, _dataPayload = dataPayload, _localFile = localFile, @@ -83,10 +82,9 @@ class S3UploadTask { S3DataPayload dataPayload, { required s3.S3Client s3Client, required smithy_aws.S3ClientConfig defaultS3ClientConfig, - required S3PrefixResolver prefixResolver, + required S3PathResolver pathResolver, required String bucket, - required StorageAccessLevel defaultAccessLevel, - required String key, + required StoragePath path, required StorageUploadDataOptions options, void Function(S3TransferProgress)? onProgress, required AWSLogger logger, @@ -94,10 +92,9 @@ class S3UploadTask { }) : this._( s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: prefixResolver, + pathResolver: pathResolver, bucket: bucket, - defaultAccessLevel: defaultAccessLevel, - key: key, + path: path, dataPayload: dataPayload, options: options, onProgress: onProgress, @@ -112,10 +109,9 @@ class S3UploadTask { AWSFile localFile, { required s3.S3Client s3Client, required smithy_aws.S3ClientConfig defaultS3ClientConfig, - required S3PrefixResolver prefixResolver, + required S3PathResolver pathResolver, required String bucket, - required StorageAccessLevel defaultAccessLevel, - required String key, + required StoragePath path, required StorageUploadDataOptions options, void Function(S3TransferProgress)? onProgress, required AWSLogger logger, @@ -123,10 +119,9 @@ class S3UploadTask { }) : this._( s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: prefixResolver, + pathResolver: pathResolver, bucket: bucket, - defaultAccessLevel: defaultAccessLevel, - key: key, + path: path, localFile: localFile, options: options, onProgress: onProgress, @@ -141,10 +136,9 @@ class S3UploadTask { final s3.S3Client _s3Client; final smithy_aws.S3ClientConfig _defaultS3ClientConfig; - final S3PrefixResolver _prefixResolver; + final S3PathResolver _pathResolver; final String _bucket; - final StorageAccessLevel _defaultAccessLevel; - final String _key; + final StoragePath _path; final StorageUploadDataOptions _options; final void Function(S3TransferProgress)? _onProgress; final AWSLogger _logger; @@ -156,7 +150,7 @@ class S3UploadTask { bool _isMultipartUpload = false; late StorageTransferState _state; - late final String _resolvedKey; + late final String _resolvedPath; // fields used to manage the single upload process smithy.SmithyOperation? _putObjectOperation; @@ -206,7 +200,7 @@ class S3UploadTask { } try { - await _setResolvedKey(); + await _setResolvedPath(); } on Exception catch (error, stackTrace) { _completeUploadWithError(error, stackTrace); return; @@ -313,14 +307,8 @@ class S3UploadTask { } } - Future _setResolvedKey() async { - final resolvedPrefix = await StorageS3Service.getResolvedPrefix( - prefixResolver: _prefixResolver, - logger: _logger, - accessLevel: _options.accessLevel ?? _defaultAccessLevel, - ); - - _resolvedKey = '$resolvedPrefix$_key'; + Future _setResolvedPath() async { + _resolvedPath = await _pathResolver.resolvePath(path: _path); } Future _startPutObject(S3DataPayload body) async { @@ -332,7 +320,7 @@ class S3UploadTask { ..bucket = _bucket ..body = body ..contentType = body.contentType ?? fallbackContentType - ..key = _resolvedKey + ..key = _resolvedPath ..metadata.addAll(_metadata); }); @@ -357,11 +345,11 @@ class S3UploadTask { await StorageS3Service.headObject( s3client: _s3Client, bucket: _bucket, - key: _resolvedKey, + key: _resolvedPath, ), - key: _key, + path: _resolvedPath, ) - : S3Item(key: _key), + : S3Item(path: _resolvedPath), ); _state = StorageTransferState.success; @@ -466,11 +454,11 @@ class S3UploadTask { await StorageS3Service.headObject( s3client: _s3Client, bucket: _bucket, - key: _resolvedKey, + key: _resolvedPath, ), - key: _key, + path: _resolvedPath, ) - : S3Item(key: _key), + : S3Item(path: _resolvedPath), ); _state = StorageTransferState.success; _emitTransferProgress(); @@ -486,7 +474,7 @@ class S3UploadTask { builder ..bucket = _bucket ..contentType = contentType ?? fallbackContentType - ..key = _resolvedKey + ..key = _resolvedPath ..metadata.addAll(_metadata); }); @@ -503,7 +491,7 @@ class S3UploadTask { await _transferDatabase.insertTransferRecord( TransferRecord( uploadId: uploadId, - objectKey: _resolvedKey, + objectKey: _resolvedPath, createdAt: DateTime.now(), ), ); @@ -530,7 +518,7 @@ class S3UploadTask { final request = s3.CompleteMultipartUploadRequest.build((builder) { builder ..bucket = _bucket - ..key = _resolvedKey + ..key = _resolvedPath ..uploadId = _multipartUploadId ..multipartUpload = s3.CompletedMultipartUpload( parts: (_completedSubtasks @@ -650,7 +638,7 @@ class S3UploadTask { builder ..bucket = _bucket ..body = partBody - ..key = _resolvedKey + ..key = _resolvedPath ..partNumber = partNumber ..uploadId = _multipartUploadId; }); @@ -741,7 +729,7 @@ class S3UploadTask { final request = s3.AbortMultipartUploadRequest.build((builder) { builder ..bucket = _bucket - ..key = _resolvedKey + ..key = _resolvedPath ..uploadId = _multipartUploadId; }); diff --git a/packages/storage/amplify_storage_s3_dart/test/amplify_storage_s3_dart_test.dart b/packages/storage/amplify_storage_s3_dart/test/amplify_storage_s3_dart_test.dart index af7c26e7a3..526ad121b9 100644 --- a/packages/storage/amplify_storage_s3_dart/test/amplify_storage_s3_dart_test.dart +++ b/packages/storage/amplify_storage_s3_dart/test/amplify_storage_s3_dart_test.dart @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import 'package:amplify_core/amplify_core.dart'; +import 'package:amplify_core/src/types/storage/storage_path_from_identity_id.dart'; import 'package:amplify_storage_s3_dart/amplify_storage_s3_dart.dart'; import 'package:amplify_storage_s3_dart/src/prefix_resolver/storage_access_level_aware_prefix_resolver.dart'; import 'package:amplify_storage_s3_dart/src/storage_s3_service/storage_s3_service.dart'; @@ -10,11 +11,13 @@ import 'package:test/test.dart'; import 'test_utils/mocks.dart'; import 'test_utils/test_custom_prefix_resolver.dart'; +import 'test_utils/test_path_resolver.dart'; import 'test_utils/test_token_provider.dart'; +const testPath = StoragePath.fromString('some/path.txt'); + void main() { const testDefaultStorageAccessLevel = StorageAccessLevel.guest; - const testAccessLevelProtected = StorageAccessLevel.protected; const testConfig = AmplifyConfig( storage: StorageConfig( plugins: { @@ -130,13 +133,12 @@ void main() { }); group('list()', () { - const testPath = 'some/path'; + const testPath = StoragePath.fromString('some/path'); final testResult = S3ListResult( [], hasNextPage: false, metadata: S3ListMetadata.fromS3CommonPrefixes( commonPrefixes: [], - prefixToDrop: 'prefix', ), ); @@ -185,7 +187,6 @@ void main() { test('should forward options to StorageS3Service.list() API', () async { const testOptions = StorageListOptions( - accessLevel: testAccessLevelProtected, pluginOptions: S3ListPluginOptions(excludeSubPaths: true), nextToken: 'next-token-123', pageSize: 2, @@ -231,7 +232,7 @@ void main() { const testKey = 'some-object-key'; final testResult = S3GetPropertiesResult( storageItem: S3Item( - key: testKey, + path: testKey, lastModified: DateTime(2022, 9, 19), eTag: '12345', size: 1024, @@ -257,7 +258,7 @@ void main() { when( () => storageS3Service.getProperties( - key: testKey, + path: testPath, options: defaultOptions, ), ).thenAnswer( @@ -265,11 +266,11 @@ void main() { ); final getPropertiesOperation = - storageS3Plugin.getProperties(key: testKey); + storageS3Plugin.getProperties(path: testPath); final capturedOptions = verify( () => storageS3Service.getProperties( - key: testKey, + path: testPath, options: captureAny( named: 'options', ), @@ -292,14 +293,12 @@ void main() { test('should forward options to StorageS3Service.getProperties() API', () async { const testOptions = StorageGetPropertiesOptions( - accessLevel: testAccessLevelProtected, - pluginOptions: - S3GetPropertiesPluginOptions.forIdentity('target-identity-id'), + pluginOptions: S3GetPropertiesPluginOptions(), ); when( () => storageS3Service.getProperties( - key: testKey, + path: testPath, options: testOptions, ), ).thenAnswer( @@ -307,13 +306,13 @@ void main() { ); final getPropertiesOperation = storageS3Plugin.getProperties( - key: testKey, + path: testPath, options: testOptions, ); final capturedOptions = verify( () => storageS3Service.getProperties( - key: testKey, + path: testPath, options: captureAny( named: 'options', ), @@ -331,7 +330,6 @@ void main() { }); group('getUrl() API', () { - const testKey = 'some-object-key'; final testResult = S3GetUrlResult( url: Uri( host: 's3.amazon.aws', @@ -354,18 +352,20 @@ void main() { when( () => storageS3Service.getUrl( - key: testKey, + path: testPath, options: defaultOptions, ), ).thenAnswer( (_) async => testResult, ); - final getUrlOperation = storageS3Plugin.getUrl(key: testKey); + final getUrlOperation = storageS3Plugin.getUrl( + path: testPath, + ); final capturedOptions = verify( () => storageS3Service.getUrl( - key: testKey, + path: testPath, options: captureAny( named: 'options', ), @@ -386,7 +386,6 @@ void main() { test('should forward options to StorageS3Service.getUrl() API', () async { const testOptions = StorageGetUrlOptions( - accessLevel: testAccessLevelProtected, pluginOptions: S3GetUrlPluginOptions( validateObjectExistence: true, expiresIn: Duration(minutes: 10), @@ -396,7 +395,7 @@ void main() { when( () => storageS3Service.getUrl( - key: testKey, + path: testPath, options: testOptions, ), ).thenAnswer( @@ -404,13 +403,13 @@ void main() { ); final getUrlOperation = storageS3Plugin.getUrl( - key: testKey, + path: testPath, options: testOptions, ); final capturedOptions = verify( () => storageS3Service.getUrl( - key: testKey, + path: testPath, options: captureAny( named: 'options', ), @@ -433,7 +432,7 @@ void main() { group('downloadData() API', () { const testKey = 'some-object-key'; final testItem = S3Item( - key: testKey, + path: testKey, lastModified: DateTime(2022, 9, 19), eTag: '12345', size: 1024, @@ -449,6 +448,12 @@ void main() { registerFallbackValue( const StorageDownloadDataOptions(), ); + registerFallbackValue(const StoragePath.fromString('public/$testKey')); + registerFallbackValue( + StoragePathFromIdentityId( + (identityId) => 'private/$identityId/$testKey', + ), + ); }); test( @@ -460,7 +465,7 @@ void main() { when( () => storageS3Service.downloadData( - key: testKey, + path: const StoragePath.fromString('public/$testKey'), options: defaultOptions, preStart: any(named: 'preStart'), onProgress: any(named: 'onProgress'), @@ -472,12 +477,12 @@ void main() { when(() => testS3DownloadTask.result).thenAnswer((_) async => testItem); downloadDataOperation = storageS3Plugin.downloadData( - key: testKey, + path: const StoragePath.fromString('public/$testKey'), ); final capturedOptions = verify( () => storageS3Service.downloadData( - key: testKey, + path: const StoragePath.fromString('public/$testKey'), options: captureAny( named: 'options', ), @@ -501,7 +506,6 @@ void main() { test('should forward options to StorageS3Service.downloadData API', () async { const testOptions = StorageDownloadDataOptions( - accessLevel: testAccessLevelProtected, pluginOptions: S3DownloadDataPluginOptions( useAccelerateEndpoint: true, getProperties: true, @@ -510,7 +514,7 @@ void main() { when( () => storageS3Service.downloadData( - key: testKey, + path: any(named: 'path'), options: any(named: 'options'), onData: any(named: 'onData'), ), @@ -519,13 +523,15 @@ void main() { when(() => testS3DownloadTask.result).thenAnswer((_) async => testItem); downloadDataOperation = storageS3Plugin.downloadData( - key: testKey, + path: StoragePath.fromIdentityId( + (identityId) => 'protected/$identityId/$testKey', + ), options: testOptions, ); final capturedOptions = verify( () => storageS3Service.downloadData( - key: testKey, + path: any(named: 'path'), onData: any(named: 'onData'), options: captureAny( named: 'options', @@ -560,7 +566,7 @@ void main() { const testKey = 'object-upload-to'; final testData = S3DataPayload.string('Hello S3.'); final testItem = S3Item( - key: testKey, + path: testKey, lastModified: DateTime(2022, 10, 14), eTag: '12345', size: 1024, @@ -587,7 +593,7 @@ void main() { when( () => storageS3Service.uploadData( - key: testKey, + path: testPath, dataPayload: any(named: 'dataPayload'), options: defaultOptions, onProgress: any(named: 'onProgress'), @@ -598,12 +604,14 @@ void main() { when(() => testS3UploadTask.result).thenAnswer((_) async => testItem); - final uploadDataOperation = - storageS3Plugin.uploadData(data: testData, key: testKey); + final uploadDataOperation = storageS3Plugin.uploadData( + data: testData, + path: testPath, + ); final capturedParams = verify( () => storageS3Service.uploadData( - key: testKey, + path: testPath, dataPayload: captureAny(named: 'dataPayload'), options: captureAny( named: 'options', @@ -636,7 +644,6 @@ void main() { test('should forward options to StorageS3Service.uploadData API', () async { const testOptions = StorageUploadDataOptions( - accessLevel: testAccessLevelProtected, pluginOptions: S3UploadDataPluginOptions( getProperties: true, useAccelerateEndpoint: true, @@ -645,7 +652,7 @@ void main() { when( () => storageS3Service.uploadData( - key: testKey, + path: testPath, dataPayload: any(named: 'dataPayload'), options: testOptions, ), @@ -654,12 +661,12 @@ void main() { when(() => testS3UploadTask.result).thenAnswer((_) async => testItem); uploadDataOperation = storageS3Plugin.uploadData( data: testData, - key: testKey, + path: testPath, options: testOptions, ); final capturedOptions = verify( () => storageS3Service.uploadData( - key: testKey, + path: testPath, dataPayload: any(named: 'dataPayload'), options: captureAny( named: 'options', @@ -679,13 +686,12 @@ void main() { 'filename': '123', }; const testOptions = StorageUploadDataOptions( - accessLevel: testAccessLevelProtected, metadata: testMetadata, ); when( () => storageS3Service.uploadData( - key: testKey, + path: testPath, dataPayload: any(named: 'dataPayload'), options: any(named: 'options'), ), @@ -694,12 +700,12 @@ void main() { when(() => testS3UploadTask.result).thenAnswer((_) async => testItem); uploadDataOperation = storageS3Plugin.uploadData( data: testData, - key: testKey, + path: testPath, options: testOptions, ); final capturedOptions = verify( () => storageS3Service.uploadData( - key: testKey, + path: testPath, dataPayload: any(named: 'dataPayload'), options: captureAny( named: 'options', @@ -732,7 +738,7 @@ void main() { const testKey = 'object-upload-to'; final testLocalFile = AWSFile.fromData([101]); final testItem = S3Item( - key: testKey, + path: testKey, lastModified: DateTime(2022, 10, 14), eTag: '12345', size: 1024, @@ -760,7 +766,7 @@ void main() { when( () => storageS3Service.uploadFile( - key: testKey, + path: testPath, localFile: any(named: 'localFile'), options: defaultOptions, onProgress: any(named: 'onProgress'), @@ -772,13 +778,13 @@ void main() { when(() => testS3UploadTask.result).thenAnswer((_) async => testItem); uploadFileOperation = storageS3Plugin.uploadFile( - key: testKey, + path: testPath, localFile: testLocalFile, ); final capturedParams = verify( () => storageS3Service.uploadFile( - key: testKey, + path: testPath, localFile: captureAny(named: 'localFile'), options: captureAny( named: 'options', @@ -809,7 +815,6 @@ void main() { test('should forward options to StorageS3Service.uploadFile API', () async { const testOptions = StorageUploadFileOptions( - accessLevel: testAccessLevelProtected, pluginOptions: S3UploadFilePluginOptions( getProperties: true, useAccelerateEndpoint: true, @@ -818,7 +823,7 @@ void main() { when( () => storageS3Service.uploadFile( - key: testKey, + path: testPath, localFile: any(named: 'localFile'), options: testOptions, ), @@ -827,14 +832,14 @@ void main() { when(() => testS3UploadTask.result).thenAnswer((_) async => testItem); uploadFileOperation = storageS3Plugin.uploadFile( - key: testKey, + path: testPath, localFile: testLocalFile, options: testOptions, ); final capturedOptions = verify( () => storageS3Service.uploadFile( - key: testKey, + path: testPath, localFile: any(named: 'localFile'), options: captureAny( named: 'options', @@ -854,13 +859,12 @@ void main() { 'filename': '123', }; const testOptions = StorageUploadFileOptions( - accessLevel: testAccessLevelProtected, metadata: testMetadata, ); when( () => storageS3Service.uploadFile( - key: testKey, + path: testPath, localFile: any(named: 'localFile'), options: any(named: 'options'), ), @@ -869,14 +873,14 @@ void main() { when(() => testS3UploadTask.result).thenAnswer((_) async => testItem); uploadFileOperation = storageS3Plugin.uploadFile( - key: testKey, + path: testPath, localFile: testLocalFile, options: testOptions, ); final capturedOptions = verify( () => storageS3Service.uploadFile( - key: testKey, + path: testPath, localFile: any(named: 'localFile'), options: captureAny( named: 'options', @@ -912,26 +916,19 @@ void main() { }); group('copy() API', () { - const sourceKey = 'source-key'; - const destinationKey = 'destination-key'; - - const testSource = StorageItemWithAccessLevel( - storageItem: StorageItem(key: sourceKey), - accessLevel: StorageAccessLevel.guest, - ); - const testDestination = StorageItemWithAccessLevel( - storageItem: StorageItem(key: destinationKey), - accessLevel: StorageAccessLevel.protected, + const testSource = StoragePath.fromString('public/source-key'); + final testDestination = StoragePath.fromIdentityId( + (identityId) => 'protected/$identityId/destination-key', ); - final testResult = S3CopyResult(copiedItem: S3Item(key: destinationKey)); + final testResult = S3CopyResult( + copiedItem: S3Item(path: TestPathResolver.path), + ); setUpAll(() { registerFallbackValue(const StorageCopyOptions()); registerFallbackValue( - S3ItemWithAccessLevel( - storageItem: S3Item(key: 'some-key'), - ), + const StoragePath.fromString('public/source-key'), ); }); @@ -956,9 +953,8 @@ void main() { final captured = verify( () => storageS3Service.copy( - source: captureAny(named: 'source'), - destination: - captureAny(named: 'destination'), + source: captureAny(named: 'source'), + destination: captureAny(named: 'destination'), options: captureAny( named: 'options', ), @@ -968,34 +964,8 @@ void main() { final capturedDestination = captured[1]; final capturedOptions = captured[2]; - expect( - capturedSource, - isA() - .having( - (i) => i.accessLevel, - 'accessLevel', - testSource.accessLevel, - ) - .having( - (i) => i.storageItem.key, - 'key', - testSource.storageItem.key, - ), - ); - expect( - capturedDestination, - isA() - .having( - (o) => o.accessLevel, - 'accessLevel', - testDestination.accessLevel, - ) - .having( - (i) => i.storageItem.key, - 'key', - testDestination.storageItem.key, - ), - ); + expect(capturedSource, testSource); + expect(capturedDestination, testDestination); expect( capturedOptions, @@ -1017,107 +987,11 @@ void main() { }); }); - group('move() API', () { - const sourceKey = 'source-key'; - const destinationKey = 'destination-key'; - - const testSource = StorageItemWithAccessLevel( - storageItem: StorageItem(key: sourceKey), - accessLevel: StorageAccessLevel.guest, - ); - - const testDestination = StorageItemWithAccessLevel( - storageItem: StorageItem(key: destinationKey), - accessLevel: StorageAccessLevel.protected, - ); - - final testResult = S3MoveResult(movedItem: S3Item(key: destinationKey)); - - setUpAll(() { - registerFallbackValue(const StorageMoveOptions()); - registerFallbackValue( - S3ItemWithAccessLevel( - storageItem: S3Item(key: 'some-key'), - ), - ); - }); - - test( - 'should forward options with default getProperties value to StorageS3Service.move() API', - () async { - when( - () => storageS3Service.move( - source: any(named: 'source'), - destination: any(named: 'destination'), - options: any(named: 'options'), - ), - ).thenAnswer((_) async => testResult); - - final moveOperation = storageS3Plugin.move( - source: testSource, - destination: testDestination, - ); - - final captured = verify( - () => storageS3Service.move( - source: captureAny(named: 'source'), - destination: - captureAny(named: 'destination'), - options: captureAny( - named: 'options', - ), - ), - ).captured; - final capturedSource = captured[0]; - final capturedDestination = captured[1]; - final capturedOptions = captured[2]; - expect( - capturedSource, - isA() - .having( - (i) => i.accessLevel, - 'accessLevel', - testSource.accessLevel, - ) - .having( - (i) => i.storageItem.key, - 'key', - testSource.storageItem.key, - ), - ); - expect( - capturedDestination, - isA() - .having( - (o) => o.accessLevel, - 'accessLevel', - testDestination.accessLevel, - ) - .having( - (i) => i.storageItem.key, - 'key', - testDestination.storageItem.key, - ), - ); - expect( - capturedOptions, - isA().having( - (o) => (o.pluginOptions! as S3MovePluginOptions).getProperties, - 'getProperties', - false, - ), - ); - - final result = await moveOperation.result; - expect(result, testResult); - }); - }); - group('remove() API', () { const testKey = 'object-to-remove'; final testResult = S3RemoveResult( removedItem: S3Item( - key: testKey, + path: testKey, ), ); @@ -1134,16 +1008,16 @@ void main() { ); when( () => storageS3Service.remove( - key: testKey, + path: testPath, options: defaultOptions, ), ).thenAnswer((_) async => testResult); - final removeOperation = storageS3Plugin.remove(key: testKey); + final removeOperation = storageS3Plugin.remove(path: testPath); final capturedOptions = verify( () => storageS3Service.remove( - key: testKey, + path: testPath, options: captureAny( named: 'options', ), @@ -1164,25 +1038,24 @@ void main() { test('should forward options to StorageS3Service.remove() API', () async { const testOptions = StorageRemoveOptions( - accessLevel: testAccessLevelProtected, pluginOptions: S3RemovePluginOptions(), ); when( () => storageS3Service.remove( - key: testKey, + path: testPath, options: any(named: 'options'), ), ).thenAnswer((_) async => testResult); final removeOperation = storageS3Plugin.remove( - key: testKey, + path: testPath, options: testOptions, ); final capturedOptions = verify( () => storageS3Service.remove( - key: testKey, + path: testPath, options: captureAny( named: 'options', ), @@ -1207,8 +1080,9 @@ void main() { 20, (index) => 'object-to-remove-${index + 1}', ); + final testPaths = testKeys.map(StoragePath.fromString).toList(); final resultRemoveItems = - testKeys.map((key) => S3Item(key: key)).toList(); + testKeys.map((key) => S3Item(path: key)).toList(); final testResult = S3RemoveManyResult( removedItems: resultRemoveItems, ); @@ -1228,16 +1102,17 @@ void main() { when( () => storageS3Service.removeMany( - keys: testKeys, + paths: testPaths, options: defaultOptions, ), ).thenAnswer((_) async => testResult); - final removeManyOperation = storageS3Plugin.removeMany(keys: testKeys); + final removeManyOperation = + storageS3Plugin.removeMany(paths: testPaths); final capturedOptions = verify( () => storageS3Service.removeMany( - keys: testKeys, + paths: testPaths, options: captureAny(named: 'options'), ), ).captured.last; @@ -1257,25 +1132,24 @@ void main() { test('should forward options to StorageS3Service.removeMany() API', () async { const testOptions = StorageRemoveManyOptions( - accessLevel: testAccessLevelProtected, pluginOptions: S3RemoveManyPluginOptions(), ); when( () => storageS3Service.removeMany( - keys: testKeys, + paths: testPaths, options: testOptions, ), ).thenAnswer((_) async => testResult); final removeManyOperation = storageS3Plugin.removeMany( - keys: testKeys, + paths: testPaths, options: testOptions, ); final capturedOptions = verify( () => storageS3Service.removeMany( - keys: testKeys, + paths: testPaths, options: captureAny(named: 'options'), ), ).captured.last; diff --git a/packages/storage/amplify_storage_s3_dart/test/model/storage_s3_item_test.dart b/packages/storage/amplify_storage_s3_dart/test/model/storage_s3_item_test.dart index 93d6e77086..ad82f0dff9 100644 --- a/packages/storage/amplify_storage_s3_dart/test/model/storage_s3_item_test.dart +++ b/packages/storage/amplify_storage_s3_dart/test/model/storage_s3_item_test.dart @@ -25,13 +25,12 @@ void main() { final storageS3Item = S3Item.fromS3Object( testS3Object, - prefixToDrop: testPrefixToDrop, ); expect(storageS3Item.eTag, testETag); expect( - storageS3Item.key, - testKey.replaceRange(0, testPrefixToDrop.length, ''), + storageS3Item.path, + testKey, ); expect(storageS3Item.lastModified, testLastModified); expect(storageS3Item.size, testSize.toInt()); @@ -47,7 +46,6 @@ void main() { try { S3Item.fromS3Object( testS3Object, - prefixToDrop: testPrefixToDrop, ); fail("Expected exception wasn't thrown."); } on StorageException catch (error) { diff --git a/packages/storage/amplify_storage_s3_dart/test/path_resolver/path_resolver_test.dart b/packages/storage/amplify_storage_s3_dart/test/path_resolver/path_resolver_test.dart new file mode 100644 index 0000000000..b4b59fe77b --- /dev/null +++ b/packages/storage/amplify_storage_s3_dart/test/path_resolver/path_resolver_test.dart @@ -0,0 +1,127 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:amplify_core/amplify_core.dart'; +import 'package:amplify_storage_s3_dart/src/path_resolver/s3_path_resolver.dart'; +import 'package:test/test.dart'; + +class MockTokenIdentityAmplifyAuthProvider + implements TokenIdentityAmplifyAuthProvider { + MockTokenIdentityAmplifyAuthProvider({ + this.getIdException, + }); + + Exception? getIdException; + int getIdCount = 0; + + @override + Future authorizeRequest( + AWSBaseHttpRequest request, { + covariant AuthProviderOptions? options, + }) { + throw UnimplementedError(); + } + + @override + Future getIdentityId() async { + getIdCount++; + if (getIdException != null) throw getIdException!; + return 'mock-id'; + } + + @override + Future getLatestAuthToken() => throw UnimplementedError(); +} + +void main() { + group('S3PathResolver', () { + final pathResolver = S3PathResolver( + identityProvider: MockTokenIdentityAmplifyAuthProvider(), + ); + + test('should resolve static strings', () async { + const path = StoragePath.fromString('foo/bar/picture.png'); + expect( + await pathResolver.resolvePath(path: path), + 'foo/bar/picture.png', + ); + }); + + test('should resolve multiple static strings', () async { + const path1 = StoragePath.fromString('foo/bar/picture1.png'); + const path2 = StoragePath.fromString('foo/bar/picture2.png'); + expect( + await pathResolver.resolvePaths(paths: [path1, path2]), + ['foo/bar/picture1.png', 'foo/bar/picture2.png'], + ); + }); + + test('should resolve path with identity Id', () async { + final path = StoragePath.fromIdentityId((id) => 'foo/$id/picture.png'); + expect( + await pathResolver.resolvePath(path: path), + 'foo/mock-id/picture.png', + ); + }); + + test('should resolve multiple paths with identity Id', () async { + final tokenAmplifyAuthProvider = MockTokenIdentityAmplifyAuthProvider(); + final pathResolver = S3PathResolver( + identityProvider: tokenAmplifyAuthProvider, + ); + final path1 = StoragePath.fromIdentityId((id) => 'foo/$id/picture1.png'); + final path2 = StoragePath.fromIdentityId((id) => 'foo/$id/picture2.png'); + expect( + await pathResolver.resolvePaths(paths: [path1, path2]), + ['foo/mock-id/picture1.png', 'foo/mock-id/picture2.png'], + ); + expect(tokenAmplifyAuthProvider.getIdCount, 1); + }); + + test('should throw if the path starts with a leading "/"', () async { + const path = StoragePath.fromString('/foo/bar/picture.png'); + expect( + () => pathResolver.resolvePath(path: path), + throwsA(isA()), + ); + }); + + test('should throw if the path is empty', () async { + const path = StoragePath.fromString(''); + expect( + () => pathResolver.resolvePath(path: path), + throwsA(isA()), + ); + }); + + test( + 'should throw StorageAccessDeniedException if AuthProvider throws SessionExpiredException', + () async { + final tokenAmplifyAuthProvider = MockTokenIdentityAmplifyAuthProvider( + getIdException: const SessionExpiredException('Session Expired.'), + ); + final pathResolver = S3PathResolver( + identityProvider: tokenAmplifyAuthProvider, + ); + final path = StoragePath.fromIdentityId((id) => 'foo/$id/bar'); + expect( + () => pathResolver.resolvePath(path: path), + throwsA(isA()), + ); + }); + + test('should throw UnknownException for any other exception', () async { + final tokenAmplifyAuthProvider = MockTokenIdentityAmplifyAuthProvider( + getIdException: Exception('Something went wrong.'), + ); + final pathResolver = S3PathResolver( + identityProvider: tokenAmplifyAuthProvider, + ); + final path = StoragePath.fromIdentityId((id) => 'foo/$id/bar'); + expect( + () => pathResolver.resolvePath(path: path), + throwsA(isA()), + ); + }); + }); +} diff --git a/packages/storage/amplify_storage_s3_dart/test/platform_impl/download_file_html_test.dart b/packages/storage/amplify_storage_s3_dart/test/platform_impl/download_file_html_test.dart index 6b65c851f4..736e8a184c 100644 --- a/packages/storage/amplify_storage_s3_dart/test/platform_impl/download_file_html_test.dart +++ b/packages/storage/amplify_storage_s3_dart/test/platform_impl/download_file_html_test.dart @@ -35,7 +35,7 @@ void main() { region: 'west-2', ); final testItem = S3Item( - key: testKey, + path: testKey, lastModified: DateTime(2022, 9, 19), eTag: '12345', size: 1024, @@ -59,27 +59,25 @@ void main() { storageS3Service = MockStorageS3Service(); registerFallbackValue( - StorageGetUrlOptions( - accessLevel: testS3pluginConfig.defaultAccessLevel, - ), + const StorageGetUrlOptions(), ); registerFallbackValue( - StorageGetPropertiesOptions( - accessLevel: testS3pluginConfig.defaultAccessLevel, - ), + const StorageGetPropertiesOptions(), ); + registerFallbackValue(const StoragePath.fromString(testKey)); + when( () => storageS3Service.getProperties( - key: testKey, + path: any(named: 'path'), options: any(named: 'options'), ), ).thenAnswer((_) async => testGetPropertiesResult); when( () => storageS3Service.getUrl( - key: testKey, + path: any(named: 'path'), options: any(named: 'options'), ), ).thenAnswer((_) async => testGetUrlResult); @@ -88,84 +86,47 @@ void main() { test( 'should invoke StorageS3Service.getUrl with converted S3DownloadFilePluginOptions', () async { - const testTargetIdentity = 'someone-else'; final operation = downloadFile( - key: testKey, + path: const StoragePath.fromString('public/$testKey'), localFile: AWSFile.fromPath('file_name.jpg'), - options: StorageDownloadFileOptions( - accessLevel: testS3pluginConfig.defaultAccessLevel, - pluginOptions: - const S3DownloadFilePluginOptions.forIdentity(testTargetIdentity), - ), + options: const StorageDownloadFileOptions(), s3pluginConfig: testS3pluginConfig, storageS3Service: storageS3Service, appPathProvider: const DummyPathProvider(), ); await operation.result; - - final capturedGetPropertiesOptions = verify( + verify( () => storageS3Service.getProperties( - key: testKey, + path: any(named: 'path'), options: captureAny( named: 'options', ), ), ).captured.last; - expect( - capturedGetPropertiesOptions, - isA() - .having( - (o) => o.accessLevel, - 'accessLevel', - testS3pluginConfig.defaultAccessLevel, - ) - .having( - (o) => (o.pluginOptions! as S3GetPropertiesPluginOptions) - .targetIdentityId, - 'targetIdentityId', - testTargetIdentity, - ), - ); - - final capturedUrlOptions = verify( + verify( () => storageS3Service.getUrl( - key: testKey, + path: any(named: 'path'), options: captureAny( named: 'options', ), ), ).captured.last; - - expect( - capturedUrlOptions, - isA() - .having( - (o) => o.accessLevel, - 'accessLevel', - testS3pluginConfig.defaultAccessLevel, - ) - .having( - (o) => - (o.pluginOptions! as S3GetUrlPluginOptions).targetIdentityId, - 'targetIdentityId', - testTargetIdentity, - ), - ); }); test( 'download result should include metadata when options.getProperties is set to true', () async { const options = StorageDownloadFileOptions( - accessLevel: StorageAccessLevel.private, pluginOptions: S3DownloadFilePluginOptions( getProperties: true, ), ); final result = await downloadFile( - key: testKey, + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$testKey', + ), localFile: AWSFile.fromPath('download.jpg'), options: options, s3pluginConfig: testS3pluginConfig, diff --git a/packages/storage/amplify_storage_s3_dart/test/platform_impl/download_file_io_test.dart b/packages/storage/amplify_storage_s3_dart/test/platform_impl/download_file_io_test.dart index 978ecc620b..ea43d59e18 100644 --- a/packages/storage/amplify_storage_s3_dart/test/platform_impl/download_file_io_test.dart +++ b/packages/storage/amplify_storage_s3_dart/test/platform_impl/download_file_io_test.dart @@ -8,6 +8,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:amplify_core/amplify_core.dart'; +import 'package:amplify_core/src/types/storage/storage_path_from_identity_id.dart'; import 'package:amplify_storage_s3_dart/amplify_storage_s3_dart.dart'; import 'package:amplify_storage_s3_dart/src/platform_impl/download_file/download_file.dart'; import 'package:amplify_storage_s3_dart/src/storage_s3_service/storage_s3_service.dart'; @@ -34,7 +35,7 @@ void main() { ); const testKey = 'upload-key.text'; const testFileContent = 'Hello world!'; - final testItem = S3Item(key: testKey); + final testItem = S3Item(path: testKey); final testFileBytes = utf8.encode(testFileContent); late S3TransferProgress expectedProgress; @@ -43,14 +44,18 @@ void main() { storageS3Service = MockStorageS3Service(); downloadTask = MockS3DownloadTask(); registerFallbackValue( - const StorageDownloadDataOptions( - accessLevel: StorageAccessLevel.guest, + const StorageDownloadDataOptions(), + ); + registerFallbackValue(const StoragePath.fromString('public/$testKey')); + registerFallbackValue( + StoragePathFromIdentityId( + (identityId) => 'private/$identityId/$testKey', ), ); when( () => storageS3Service.downloadData( - key: testKey, + path: any(named: 'path'), options: any(named: 'options'), preStart: any(named: 'preStart'), onProgress: any(named: 'onProgress'), @@ -68,13 +73,14 @@ void main() { test('should invoke StorageS3Service.downloadData with expected parameters', () async { const options = StorageDownloadFileOptions( - accessLevel: StorageAccessLevel.private, pluginOptions: S3DownloadFilePluginOptions( getProperties: true, ), ); final downloadFileOperation = downloadFile( - key: testKey, + path: StoragePath.fromIdentityId( + (identityId) => 'private/$identityId/$testKey', + ), localFile: AWSFile.fromPath(testDestinationPath), options: options, s3pluginConfig: testS3pluginConfig, @@ -87,7 +93,7 @@ void main() { final captureParams = verify( () => storageS3Service.downloadData( - key: testKey, + path: captureAny(named: 'path'), options: captureAny( named: 'options', ), @@ -101,30 +107,26 @@ void main() { ), ).captured; + final capturedOptions = captureParams[1]; + expect( - captureParams[0], - isA() - .having( - (o) => o.accessLevel, - 'accessLevel', - options.accessLevel, - ) - .having( - (o) => (o.pluginOptions! as S3DownloadDataPluginOptions) - .getProperties, - 'getProperties', - isTrue, - ), + capturedOptions, + isA().having( + (o) => + (o.pluginOptions! as S3DownloadDataPluginOptions).getProperties, + 'getProperties', + isTrue, + ), ); - expect(captureParams[1] is Function, true); - preStart = captureParams[1] as FutureOr Function(); expect(captureParams[2] is Function, true); - onProgress = captureParams[2] as void Function(S3TransferProgress); + preStart = captureParams[2] as FutureOr Function(); expect(captureParams[3] is Function, true); - onData = captureParams[3] as void Function(List); + onProgress = captureParams[3] as void Function(S3TransferProgress); expect(captureParams[4] is Function, true); - onDone = captureParams[4] as FutureOr Function(); + onData = captureParams[4] as void Function(List); + expect(captureParams[5] is Function, true); + onDone = captureParams[5] as FutureOr Function(); final result = await downloadFileOperation.result; expect(result.downloadedItem, testItem); @@ -155,121 +157,15 @@ void main() { await downloadedFile.delete(); }); - test( - 'should correctly create S3DownloadDataOptions with default storage access level', - () { - downloadFile( - key: testKey, - localFile: AWSFile.fromPath('path'), - options: StorageDownloadFileOptions( - accessLevel: testS3pluginConfig.defaultAccessLevel, - pluginOptions: const S3DownloadFilePluginOptions(), - ), - s3pluginConfig: testS3pluginConfig, - storageS3Service: storageS3Service, - appPathProvider: appPathProvider, - onProgress: (progress) { - expectedProgress = progress; - }, - ); - - final capturedOptions = verify( - () => storageS3Service.downloadData( - key: testKey, - options: captureAny( - named: 'options', - ), - preStart: any(named: 'preStart'), - onProgress: any(named: 'onProgress'), - onData: any(named: 'onData'), - onDone: any(named: 'onDone'), - onError: any(named: 'onError'), - ), - ).captured.last; - - expect( - capturedOptions, - isA().having( - (o) => o.accessLevel, - 'accessLevel', - testS3pluginConfig.defaultAccessLevel, - ), - ); - }); - - test( - 'should correctly create S3DownloadDataOptions with correct targetIdentityId', - () { - const testTargetIdentity = 'someone-else'; - const testAccessLevel = StorageAccessLevel.protected; - downloadFile( - key: testKey, - localFile: AWSFile.fromPath('path'), - options: const StorageDownloadFileOptions( - accessLevel: testAccessLevel, - pluginOptions: S3DownloadFilePluginOptions.forIdentity( - testTargetIdentity, - ), - ), - s3pluginConfig: testS3pluginConfig, - storageS3Service: storageS3Service, - appPathProvider: appPathProvider, - onProgress: (progress) { - expectedProgress = progress; - }, - ); - - final capturedOptions = verify( - () => storageS3Service.downloadData( - key: testKey, - options: captureAny( - named: 'options', - ), - preStart: any(named: 'preStart'), - onProgress: any(named: 'onProgress'), - onData: any(named: 'onData'), - onDone: any(named: 'onDone'), - onError: any(named: 'onError'), - ), - ).captured.last; - - expect( - capturedOptions, - isA() - .having( - (o) => o.accessLevel, - 'accessLevel', - testAccessLevel, - ) - .having( - (o) => (o.pluginOptions! as S3DownloadDataPluginOptions) - .targetIdentityId, - 'targetIdentityId', - testTargetIdentity, - ), - ); - - expect( - capturedOptions, - isA().having( - (o) => (o.pluginOptions! as S3DownloadDataPluginOptions) - .targetIdentityId, - 'targetIdentityId', - testTargetIdentity, - ), - ); - }); - group('preStart callback should throw exceptions', () { test( 'when destination path is null is throws StorageLocalFileNotFoundException', () { downloadFile( - key: testKey, + path: const StoragePath.fromString('public/$testKey'), localFile: AWSFile.fromData([101]), - options: StorageDownloadFileOptions( - accessLevel: testS3pluginConfig.defaultAccessLevel, - pluginOptions: const S3DownloadFilePluginOptions(), + options: const StorageDownloadFileOptions( + pluginOptions: S3DownloadFilePluginOptions(), ), s3pluginConfig: testS3pluginConfig, storageS3Service: storageS3Service, @@ -281,7 +177,7 @@ void main() { final capturedPreStart = verify( () => storageS3Service.downloadData( - key: testKey, + path: const StoragePath.fromString('public/$testKey'), options: any(named: 'options'), preStart: captureAny Function()?>(named: 'preStart'), onProgress: any(named: 'onProgress'), @@ -298,11 +194,10 @@ void main() { 'when destination path is a directory instead of a file it throws StorageLocalFileNotFoundException', () { downloadFile( - key: testKey, + path: const StoragePath.fromString('public/$testKey'), localFile: AWSFile.fromPath(Directory.systemTemp.path), - options: StorageDownloadFileOptions( - accessLevel: testS3pluginConfig.defaultAccessLevel, - pluginOptions: const S3DownloadFilePluginOptions(), + options: const StorageDownloadFileOptions( + pluginOptions: S3DownloadFilePluginOptions(), ), s3pluginConfig: testS3pluginConfig, storageS3Service: storageS3Service, @@ -314,7 +209,7 @@ void main() { final capturedPreStart = verify( () => storageS3Service.downloadData( - key: testKey, + path: const StoragePath.fromString('public/$testKey'), options: any(named: 'options'), preStart: captureAny Function()?>(named: 'preStart'), onProgress: any(named: 'onProgress'), @@ -337,11 +232,10 @@ void main() { when(downloadTask.cancel).thenAnswer((_) async {}); final downloadFileOperation = downloadFile( - key: testKey, + path: const StoragePath.fromString('public/$testKey'), localFile: AWSFile.fromPath('path'), - options: StorageDownloadFileOptions( - accessLevel: testS3pluginConfig.defaultAccessLevel, - pluginOptions: const S3DownloadFilePluginOptions(), + options: const StorageDownloadFileOptions( + pluginOptions: S3DownloadFilePluginOptions(), ), s3pluginConfig: testS3pluginConfig, storageS3Service: storageS3Service, diff --git a/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/storage_s3_service_test.dart b/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/storage_s3_service_test.dart index e40236a7f4..21474e30a4 100644 --- a/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/storage_s3_service_test.dart +++ b/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/storage_s3_service_test.dart @@ -15,9 +15,11 @@ import 'package:test/test.dart'; import '../test_utils/custom_matchers.dart'; import '../test_utils/mocks.dart'; +import '../test_utils/test_path_resolver.dart'; import '../test_utils/test_token_provider.dart'; const testDelimiter = '#'; +const testPath = StoragePath.fromString('some/path.txt'); class TestPrefixResolver implements S3PrefixResolver { @override @@ -40,7 +42,7 @@ void main() { const s3PluginConfig = S3PluginConfig(bucket: testBucket, region: testRegion); - final testPrefixResolver = TestPrefixResolver(); + final pathResolver = TestPathResolver(); late DependencyManager dependencyManager; late S3Client s3Client; late StorageS3Service storageS3Service; @@ -56,7 +58,7 @@ void main() { ..addInstance(awsSigV4Signer); storageS3Service = StorageS3Service( s3PluginConfig: s3PluginConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, credentialsProvider: TestIamAuthProvider(), logger: logger, dependencyManager: dependencyManager, @@ -69,7 +71,7 @@ void main() { bucket: 'bucket.name.has.dots.com', region: 'us-west-2', ), - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, credentialsProvider: TestIamAuthProvider(), logger: logger, dependencyManager: dependencyManager, @@ -80,35 +82,13 @@ void main() { expect(message, contains('Since your bucket name contains dots')); }); - group('_getResolvedPrefix()', () { - test( - 'should throw a StorageException if supplied prefix resolver throws an exception', - () async { - const testOptions = StorageListOptions( - pageSize: 1000, - pluginOptions: - S3ListPluginOptions.forIdentity('throw exception for me'), - ); - //StorageListOption<>(S3ListOptions.forIdentity('throw exception for me'); - - await expectLater( - storageS3Service.list(path: 'a path', options: testOptions), - throwsA(isA()), - ); - - verify(() => logger.error(any(), any(), any())).called(1); - }); - }); - group('list() API', () { late S3ListResult listResult; const testNextContinuationToken = 'get-next-page'; const testPageSize = 100; const testBucketName = 'a-bucket'; - const testStorageAccessLevel = StorageAccessLevel.protected; - final testPrefixToDrop = - '${testStorageAccessLevel.defaultPrefix}$testDelimiter'; - final testCommonPrefix = CommonPrefix(prefix: testPrefixToDrop); + const testPrefix = 'public$testDelimiter'; + final testCommonPrefix = CommonPrefix(prefix: testPrefix); setUpAll(() { registerFallbackValue(ListObjectsV2Request(bucket: 'fake bucket')); @@ -116,25 +96,20 @@ void main() { test('should invoke S3Client.listObjectsV2 with expected parameters', () async { - final testPrefixToDrop = - '${s3PluginConfig.defaultAccessLevel.defaultPrefix}$testDelimiter'; - final testCommonPrefix = CommonPrefix(prefix: testPrefixToDrop); + final testCommonPrefix = CommonPrefix(prefix: testPrefix); final testS3Objects = [1, 2, 3, 4, 5] .map( (e) => S3Object( - key: '${testPrefixToDrop}object-$e', + key: '${testPrefix}object-$e', size: Int64(100 * 4), eTag: 'object-$e-eTag', ), ) .toList(); - const testPath = 'album'; - const testTargetIdentityId = 'someone-else-id'; + const testPath = StoragePath.fromString('album'); const testOptions = StorageListOptions( pageSize: testPageSize, - pluginOptions: S3ListPluginOptions.forIdentity( - testTargetIdentityId, - ), + pluginOptions: S3ListPluginOptions(), ); final testPaginatedResult = @@ -177,18 +152,14 @@ void main() { expect(request.bucket, testBucket); expect( request.prefix, - '${await testPrefixResolver.resolvePrefix( - accessLevel: s3PluginConfig.defaultAccessLevel, - identityId: testTargetIdentityId, - )}$testPath', + TestPathResolver.path, ); expect(request.maxKeys, testPageSize); }); test('should return correct StorageS3ListResult', () async { listResult.items.asMap().forEach((index, item) { - expect(item.key, isNot(contains(testPrefixToDrop))); - expect(item.key, 'object-${index + 1}'); + expect(item.path, '${testPrefix}object-${index + 1}'); }); expect(listResult.hasNextPage, true); expect(listResult.nextToken, testNextContinuationToken); @@ -200,15 +171,14 @@ void main() { final testS3Objects = [1, 2, 3, 4, 5] .map( (e) => S3Object( - key: '${testPrefixToDrop}object-$e', + key: '${testPrefix}object-$e', size: Int64(100 * 4), eTag: 'object-$e-eTag', ), ) .toList(); - const testPath = 'album'; + const testPath = StoragePath.fromString('album'); const testOptions = StorageListOptions( - accessLevel: testStorageAccessLevel, pageSize: testPageSize, pluginOptions: S3ListPluginOptions(excludeSubPaths: true), ); @@ -222,10 +192,10 @@ void main() { ListObjectsV2Output( contents: testS3Objects, commonPrefixes: [ - CommonPrefix(prefix: '$testPrefixToDrop${testSubPaths[0]}'), - CommonPrefix(prefix: '$testPrefixToDrop${testSubPaths[1]}'), + CommonPrefix(prefix: testSubPaths[0]), + CommonPrefix(prefix: testSubPaths[1]), CommonPrefix( - prefix: '$testPrefixToDrop${testSubPaths[2]}', + prefix: testSubPaths[2], ), ], delimiter: testDelimiter, @@ -273,7 +243,7 @@ void main() { expect( storageS3Service.list( - path: 'a path', + path: const StoragePath.fromString('apath'), options: testOptions, ), throwsA(isA()), @@ -286,15 +256,14 @@ void main() { final testS3Objects = List.generate(2001, (index) => '$index') .map( (e) => S3Object( - key: '${testPrefixToDrop}object-$e', + key: '${testPrefix}object-$e', size: Int64(100 * 4), eTag: 'object-$e-eTag', ), ) .toList(); - const testPath = 'album'; + const testPath = StoragePath.fromString('album'); const testOptions = StorageListOptions( - accessLevel: StorageAccessLevel.private, pageSize: testPageSize, pluginOptions: S3ListPluginOptions.listAll(), ); @@ -373,11 +342,10 @@ void main() { expect( capturedRequest, isA().having( - (o) => o.prefix, - 'prefix', - '${await testPrefixResolver.resolvePrefix( - accessLevel: testOptions.accessLevel!, - )}$testPath'), + (o) => o.prefix, + 'prefix', + TestPathResolver.path, + ), ); expect(listResult.hasNextPage, false); @@ -390,8 +358,7 @@ void main() { }); test('should handle AWSHttpException and throw NetworkException', () { - const testOptions = - StorageListOptions(accessLevel: StorageAccessLevel.guest); + const testOptions = StorageListOptions(); final testException = AWSHttpException( AWSHttpRequest(method: AWSHttpMethod.get, uri: Uri()), ); @@ -402,7 +369,7 @@ void main() { expect( storageS3Service.list( - path: 'a path', + path: const StoragePath.fromString('a path'), options: testOptions, ), throwsA(isA()), @@ -412,7 +379,6 @@ void main() { group('getProperties() API', () { late S3GetPropertiesResult getPropertiesResult; - const testKey = 'some-object'; const testMetadata = { 'filename': 'hello.jpg', 'uploader': '123', @@ -451,7 +417,7 @@ void main() { ).thenAnswer((_) => smithyOperation); getPropertiesResult = await storageS3Service.getProperties( - key: testKey, + path: testPath, options: testOptions, ); @@ -464,22 +430,13 @@ void main() { final request = capturedRequest as HeadObjectRequest; expect(request.bucket, testBucket); - expect( - request.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: s3PluginConfig.defaultAccessLevel, - )}$testKey', - ); + expect(request.key, TestPathResolver.path); }); test('should invoke S3Client.headObject with expected parameters', () async { - const testTargetIdentityId = 'someone-else-id'; const testOptions = StorageGetPropertiesOptions( - accessLevel: StorageAccessLevel.protected, - pluginOptions: S3GetPropertiesPluginOptions.forIdentity( - testTargetIdentityId, - ), + pluginOptions: S3GetPropertiesPluginOptions(), ); final testHeadObjectOutput = HeadObjectOutput( eTag: testETag, @@ -498,7 +455,7 @@ void main() { ).thenAnswer((_) => smithyOperation); getPropertiesResult = await storageS3Service.getProperties( - key: testKey, + path: testPath, options: testOptions, ); @@ -511,18 +468,11 @@ void main() { final request = capturedRequest as HeadObjectRequest; expect(request.bucket, testBucket); - expect( - request.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: testOptions.accessLevel!, - identityId: testTargetIdentityId, - )}$testKey', - ); + expect(request.key, TestPathResolver.path); }); test('should return correct S3GetProperties result', () async { final storageItem = getPropertiesResult.storageItem; - expect(storageItem.key, testKey); expect(storageItem.metadata, testMetadata); expect(storageItem.eTag, testETag); expect(storageItem.size, testSize); @@ -545,7 +495,7 @@ void main() { expect( storageS3Service.getProperties( - key: 'a key', + path: const StoragePath.fromString('a key'), options: testOptions, ), throwsA(isA()), @@ -553,8 +503,7 @@ void main() { }); test('should handle AWSHttpException and throw NetworkException', () { - const testOptions = - StorageGetPropertiesOptions(accessLevel: StorageAccessLevel.guest); + const testOptions = StorageGetPropertiesOptions(); final testException = AWSHttpException( AWSHttpRequest(method: AWSHttpMethod.head, uri: Uri()), ); @@ -565,7 +514,7 @@ void main() { expect( storageS3Service.getProperties( - key: 'a key', + path: const StoragePath.fromString('a key'), options: testOptions, ), throwsA(isA()), @@ -576,7 +525,6 @@ void main() { group('getUrl() API', () { late S3GetUrlResult getUrlResult; const testExpiresIn = Duration(days: 1); - const testKey = 'some-object'; final testUrl = Uri( host: 's3.amazon.aws', path: 'album/1.jpg', @@ -604,10 +552,8 @@ void main() { test('should invoke AWSSigV4Signer.presign with correct parameters', () { runZoned( () async { - const testTargetIdentityId = 'someone-else-id'; const testOptions = StorageGetUrlOptions( - pluginOptions: S3GetUrlPluginOptions.forIdentity( - testTargetIdentityId, + pluginOptions: S3GetUrlPluginOptions( expiresIn: testExpiresIn, ), ); @@ -622,7 +568,7 @@ void main() { ).thenAnswer((_) async => testUrl); getUrlResult = await storageS3Service.getUrl( - key: testKey, + path: testPath, options: testOptions, ); final capturedParams = verify( @@ -641,12 +587,7 @@ void main() { final requestParam = capturedParams.first as AWSHttpRequest; expect( requestParam.uri.toString(), - endsWith( - Uri.encodeComponent('${await testPrefixResolver.resolvePrefix( - accessLevel: s3PluginConfig.defaultAccessLevel, - identityId: testTargetIdentityId, - )}$testKey'), - ), + endsWith(TestPathResolver.path), ); expect(capturedParams[2] is S3ServiceConfiguration, isTrue); @@ -681,7 +622,7 @@ void main() { ).thenAnswer((_) async => testUrl); getUrlResult = await storageS3Service.getUrl( - key: testKey, + path: testPath, options: testOptions, ); final capturedSignerScope1 = verify( @@ -698,7 +639,7 @@ void main() { expect(capturedSignerScope1, isA()); getUrlResult = await storageS3Service.getUrl( - key: testKey, + path: testPath, options: testOptions, ); final capturedSignerScope2 = verify( @@ -721,7 +662,6 @@ void main() { 'should invoke s3Client.headObject when validateObjectExistence option is set to true', () async { const testOptions = StorageGetUrlOptions( - accessLevel: StorageAccessLevel.private, pluginOptions: S3GetUrlPluginOptions( validateObjectExistence: true, ), @@ -739,7 +679,7 @@ void main() { await expectLater( storageS3Service.getUrl( - key: testKey, + path: testPath, options: testOptions, ), throwsA(isA()), @@ -753,20 +693,15 @@ void main() { expect( capturedRequest.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: testOptions.accessLevel!, - )}$testKey', + TestPathResolver.path, ); }); test( 'should invoke s3Client.headObject when validateObjectExistence option is' ' set to true and specified targetIdentityId', () async { - const testTargetIdentityId = 'some-else-id'; const testOptions = StorageGetUrlOptions( - accessLevel: StorageAccessLevel.guest, - pluginOptions: S3GetUrlPluginOptions.forIdentity( - testTargetIdentityId, + pluginOptions: S3GetUrlPluginOptions( validateObjectExistence: true, ), ); @@ -783,7 +718,7 @@ void main() { await expectLater( storageS3Service.getUrl( - key: testKey, + path: testPath, options: testOptions, ), throwsA(isA()), @@ -797,10 +732,7 @@ void main() { expect( capturedRequest.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: testOptions.accessLevel!, - identityId: testTargetIdentityId, - )}$testKey', + TestPathResolver.path, ); }); @@ -821,7 +753,7 @@ void main() { ).thenAnswer((_) async => testUrl); await storageS3Service.getUrl( - key: testKey, + path: testPath, options: testOptions, ); @@ -867,7 +799,7 @@ void main() { ..addInstance(pathStyleAwsSigV4Signer); pathStyleStorageS3Service = StorageS3Service( s3PluginConfig: pathStyleS3PluginConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, credentialsProvider: TestIamAuthProvider(), logger: MockAWSLogger(), dependencyManager: dependencyManager, @@ -889,7 +821,7 @@ void main() { ).thenAnswer((_) async => pathStyleURL); getUrlResult = await pathStyleStorageS3Service.getUrl( - key: testKey, + path: testPath, options: testOptions, ); @@ -915,7 +847,7 @@ void main() { .having( (o) => o.path, 'path', - '/bucket.name.has.dots.com/public#some-object', + '/bucket.name.has.dots.com/${TestPathResolver.path}', ), ); }, @@ -932,7 +864,7 @@ void main() { expect( pathStyleStorageS3Service.getUrl( - key: testKey, + path: testPath, options: testOptions, ), throwsA( @@ -950,11 +882,10 @@ void main() { group('copy() API', () { late S3CopyResult copyResult; - final testSourceItem = S3Item(key: 'source'); - final testDestinationItem = S3Item(key: 'destination'); - final testSource = S3ItemWithAccessLevel(storageItem: testSourceItem); - final testDestination = - S3ItemWithAccessLevel(storageItem: testDestinationItem); + const testSourcePath = 'public/source'; + const testDestinationPath = 'public/destination'; + const testSource = StoragePath.fromString(testSourcePath); + const testDestination = StoragePath.fromString(testDestinationPath); setUpAll(() { registerFallbackValue( @@ -1000,16 +931,9 @@ void main() { final request = capturedRequest as CopyObjectRequest; expect(request.bucket, testBucket); - expect( - request.copySource, - '$testBucket/${await testPrefixResolver.resolvePrefix( - accessLevel: testSource.accessLevel, - )}${testSourceItem.key}', - ); - }); + expect(request.copySource, '$testBucket/$testSourcePath'); - test('should return correct S3CopyResult', () { - expect(copyResult.copiedItem.key, testDestination.storageItem.key); + expect(copyResult.copiedItem.path, testDestinationPath); }); test( @@ -1101,274 +1025,13 @@ void main() { final request = headObjectRequest as HeadObjectRequest; expect(request.bucket, testBucket); - expect( - request.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: testDestination.accessLevel, - )}${testDestinationItem.key}', - ); - }); - }); - - group('move() API', () { - late S3MoveResult moveResult; - final testSourceItem = S3Item(key: 'source'); - final testDestinationItem = S3Item(key: 'destination'); - final testSource = S3ItemWithAccessLevel(storageItem: testSourceItem); - final testDestination = - S3ItemWithAccessLevel(storageItem: testDestinationItem); - - setUpAll(() { - registerFallbackValue( - CopyObjectRequest( - bucket: 'fake bucket', - copySource: 'dummy source', - key: 'imposing destination', - ), - ); - registerFallbackValue( - DeleteObjectRequest( - bucket: 'fake bucket', - key: 'dummy key', - ), - ); - registerFallbackValue( - HeadObjectRequest( - bucket: 'fake bucket', - key: 'dummy key', - ), - ); - }); - - test( - 'should invoke S3Client.copyObject and S3Client.deleteObject with expected parameters', - () async { - const testOptions = StorageMoveOptions(); - final testCopyObjectOutput = CopyObjectOutput(); - final testDeleteObjectOutput = DeleteObjectOutput(); - final copySmithyOperation = MockSmithyOperation(); - final deleteSmithyOperation = MockSmithyOperation(); - when( - () => copySmithyOperation.result, - ).thenAnswer((_) async => testCopyObjectOutput); - - when( - () => deleteSmithyOperation.result, - ).thenAnswer((_) async => testDeleteObjectOutput); - - when( - () => s3Client.copyObject(any()), - ).thenAnswer((_) => copySmithyOperation); - - when( - () => s3Client.deleteObject(any()), - ).thenAnswer((_) => deleteSmithyOperation); - - moveResult = await storageS3Service.move( - source: testSource, - destination: testDestination, - options: testOptions, - ); - - final capturedCopyRequest = verify( - () => s3Client.copyObject(captureAny()), - ).captured.last; - - final capturedDeleteRequest = verify( - () => s3Client.deleteObject(captureAny()), - ).captured.last; - - expect(capturedCopyRequest is CopyObjectRequest, isTrue); - final copyRequest = capturedCopyRequest as CopyObjectRequest; - - expect(capturedDeleteRequest is DeleteObjectRequest, isTrue); - final deleteRequest = capturedDeleteRequest as DeleteObjectRequest; - - expect(copyRequest.bucket, testBucket); - expect( - copyRequest.copySource, - '$testBucket/${await testPrefixResolver.resolvePrefix( - accessLevel: testSource.accessLevel, - )}${testSourceItem.key}', - ); - - expect(deleteRequest.bucket, testBucket); - expect( - deleteRequest.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: testSource.accessLevel, - )}${testSourceItem.key}', - ); - }); - - test('should return correct S3CopyResult', () { - expect(moveResult.movedItem.key, testDestination.storageItem.key); - }); - - test( - 'should throw StorageAccessDeniedException when UnknownSmithyHttpException' - ' with status code 403 returned from service while copying the source', - () async { - const testOptions = StorageMoveOptions(); - const testUnknownException = UnknownSmithyHttpException( - statusCode: 403, - body: 'Access denied.', - ); - - when( - () => s3Client.copyObject( - any(), - ), - ).thenThrow(testUnknownException); - - expect( - storageS3Service.move( - source: testSource, - destination: testDestination, - options: testOptions, - ), - throwsA( - isA().having( - (o) => o.underlyingException, - 'underlyingException', - isA(), - ), - ), - ); - }); - - test('should handle AWSHttpException and throw NetworkException', - () async { - const testOptions = StorageMoveOptions(); - final testException = AWSHttpException( - AWSHttpRequest(method: AWSHttpMethod.put, uri: Uri()), - ); - - when( - () => s3Client.copyObject(any()), - ).thenThrow(testException); - - expect( - storageS3Service.move( - source: testSource, - destination: testDestination, - options: testOptions, - ), - throwsA( - isA().having( - (o) => o.underlyingException, - 'underlyingException', - isA(), - ), - ), - ); - }); - - test( - 'should throw StorageHttpStatusException when UnknownSmithyHttpException' - ' with status code 500 returned from service while deleting the source', - () async { - const testOptions = StorageMoveOptions(); - const testUnknownException = UnknownSmithyHttpException( - statusCode: 500, - body: 'Internal error', - ); - final testCopyObjectOutput = CopyObjectOutput(); - final smithyOperation = MockSmithyOperation(); - - when( - () => smithyOperation.result, - ).thenAnswer((_) async => testCopyObjectOutput); - - when( - () => s3Client.copyObject(any()), - ).thenAnswer((_) => smithyOperation); - - when( - () => s3Client.deleteObject( - any(), - ), - ).thenThrow(testUnknownException); - - expect( - storageS3Service.move( - source: testSource, - destination: testDestination, - options: testOptions, - ), - throwsA( - isA().having( - (o) => o.underlyingException, - 'underlyingException', - isA(), - ), - ), - ); - }); - - test( - 'should invoke S3Client.headObject with correct parameters when' - ' getProperties option is set to true', () async { - const testOptions = StorageMoveOptions( - pluginOptions: S3MovePluginOptions(getProperties: true), - ); - final testCopyObjectOutput = CopyObjectOutput(); - final testDeleteObjectOutput = DeleteObjectOutput(); - final testHeadObjectOutput = HeadObjectOutput(); - final copySmithyOperation = MockSmithyOperation(); - final deleteSmithyOperation = MockSmithyOperation(); - final headSmithyOperation = MockSmithyOperation(); - - when( - () => copySmithyOperation.result, - ).thenAnswer((_) async => testCopyObjectOutput); - - when( - () => deleteSmithyOperation.result, - ).thenAnswer((_) async => testDeleteObjectOutput); - - when( - () => headSmithyOperation.result, - ).thenAnswer((_) async => testHeadObjectOutput); - - when( - () => s3Client.copyObject(any()), - ).thenAnswer((_) => copySmithyOperation); - - when( - () => s3Client.deleteObject(any()), - ).thenAnswer((_) => deleteSmithyOperation); - - when( - () => s3Client.headObject(any()), - ).thenAnswer((_) => headSmithyOperation); - - await storageS3Service.move( - source: testSource, - destination: testDestination, - options: testOptions, - ); - - final headObjectRequest = verify( - () => s3Client.headObject(captureAny()), - ).captured.last; - - expect(headObjectRequest is HeadObjectRequest, isTrue); - final request = headObjectRequest as HeadObjectRequest; - - expect(request.bucket, testBucket); - expect( - request.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: testDestination.accessLevel, - )}${testDestinationItem.key}', - ); + expect(request.key, testDestinationPath); }); }); group('remove() API', () { late S3RemoveResult removeResult; - const testKey = 'object-to-remove'; + const testPath = StoragePath.fromString('object-to-remove'); setUpAll(() { registerFallbackValue( @@ -1394,7 +1057,7 @@ void main() { ).thenAnswer((_) => smithyOperation); removeResult = await storageS3Service.remove( - key: testKey, + path: testPath, options: testOptions, ); @@ -1409,17 +1072,13 @@ void main() { expect(request.bucket, testBucket); expect( request.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: s3PluginConfig.defaultAccessLevel, - )}$testKey', + TestPathResolver.path, ); }); test('should invoke S3Client.deleteObject with expected parameters', () async { - const testOptions = StorageRemoveOptions( - accessLevel: StorageAccessLevel.private, - ); + const testOptions = StorageRemoveOptions(); final testDeleteObjectOutput = DeleteObjectOutput(); final smithyOperation = MockSmithyOperation(); @@ -1432,7 +1091,7 @@ void main() { ).thenAnswer((_) => smithyOperation); removeResult = await storageS3Service.remove( - key: testKey, + path: testPath, options: testOptions, ); @@ -1447,14 +1106,12 @@ void main() { expect(request.bucket, testBucket); expect( request.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: testOptions.accessLevel!, - )}$testKey', + TestPathResolver.path, ); }); test('should return correct S3RemoveResult', () { - expect(removeResult.removedItem.key, testKey); + expect(removeResult.removedItem.path, TestPathResolver.path); }); test( @@ -1472,7 +1129,7 @@ void main() { expect( storageS3Service.remove( - key: 'a key', + path: const StoragePath.fromString('a key'), options: testOptions, ), throwsA(isA()), @@ -1481,8 +1138,7 @@ void main() { test('should handle AWSHttpException and throw NetworkException', () async { - const testOptions = - StorageRemoveOptions(accessLevel: StorageAccessLevel.guest); + const testOptions = StorageRemoveOptions(); final testException = AWSHttpException( AWSHttpRequest(method: AWSHttpMethod.delete, uri: Uri()), ); @@ -1493,7 +1149,7 @@ void main() { expect( storageS3Service.remove( - key: 'a key', + path: const StoragePath.fromString('a key'), options: testOptions, ), throwsA(isA()), @@ -1505,12 +1161,16 @@ void main() { late S3RemoveManyResult removeManyResult; const testNumOfRemovedItems = 955; const testNumOfRemoveErrors = 50; - final testKeys = List.generate( + final testPaths = List.generate( 1005, (index) => 'object-to-remove-${index + 1}', - ).toList(); + ).map(StoragePath.fromString).toList(); + late List resolvedPaths; - setUpAll(() { + setUpAll(() async { + resolvedPaths = await pathResolver.resolvePaths( + paths: testPaths, + ); registerFallbackValue( DeleteObjectsRequest( bucket: 'fake bucket', @@ -1522,12 +1182,10 @@ void main() { test('should invoke S3Client.deleteObjects with default access level', () async { const testOptions = StorageRemoveManyOptions(); - final testPrefix = - '${s3PluginConfig.defaultAccessLevel.defaultPrefix}$testDelimiter'; final testDeleteObjectsOutput = DeleteObjectsOutput( - deleted: testKeys + deleted: resolvedPaths .take(2) - .map((key) => DeletedObject(key: '$testPrefix$key')) + .map((path) => DeletedObject(key: path)) .toList(), ); @@ -1544,7 +1202,7 @@ void main() { ).thenAnswer((_) => smithyOperation); removeManyResult = await storageS3Service.removeMany( - keys: testKeys.take(2).toList(), + paths: testPaths.take(2).toList(), options: testOptions, ); @@ -1559,13 +1217,7 @@ void main() { expect(capturedRequest is DeleteObjectsRequest, isTrue); final request = capturedRequest as DeleteObjectsRequest; - final expectedKeysForRequest = await Future.wait( - testKeys.take(2).map( - (key) async => '${await testPrefixResolver.resolvePrefix( - accessLevel: s3PluginConfig.defaultAccessLevel, - )}$key', - ), - ); + final expectedKeysForRequest = resolvedPaths.take(2).toList(); expect( request.delete.objects.map((object) => object.key), @@ -1575,33 +1227,25 @@ void main() { test('should invoke S3Client.deleteObjects with expected parameters', () async { - const testOptions = StorageRemoveManyOptions( - accessLevel: StorageAccessLevel.protected, - ); - final testPrefix = - '${testOptions.accessLevel!.defaultPrefix}$testDelimiter'; + const testOptions = StorageRemoveManyOptions(); final testDeleteObjectsOutput1 = DeleteObjectsOutput( - deleted: testKeys - .take(1000 - testNumOfRemoveErrors) - .map((key) => DeletedObject(key: '$testPrefix$key')) - .toList(), - errors: testKeys - .skip(1000 - testNumOfRemoveErrors) - .take(testNumOfRemoveErrors) - .map( - (key) => Error( - key: '$testPrefix$key', - message: 'some error', - ), - ) - .toList(), + deleted: List.generate( + 1000 - testNumOfRemoveErrors, + (index) => DeletedObject(key: resolvedPaths[index]), + ), + errors: List.generate( + testNumOfRemoveErrors, + (index) => Error( + key: resolvedPaths[index], + message: 'some error', + ), + ), ); final testDeleteObjectsOutput2 = DeleteObjectsOutput( - deleted: testKeys - .skip(1000) - .take(5) - .map((key) => DeletedObject(key: '$testPrefix$key')) - .toList(), + deleted: List.generate( + 5, + (index) => DeletedObject(key: resolvedPaths[1000 + index]), + ), ); final smithyOperation1 = MockSmithyOperation(); @@ -1630,7 +1274,7 @@ void main() { ).thenAnswer((_) => smithyOperation2); removeManyResult = await storageS3Service.removeMany( - keys: testKeys, + paths: testPaths, options: testOptions, ); @@ -1651,19 +1295,12 @@ void main() { final request1 = capturedRequest1 as DeleteObjectsRequest; final request2 = capturedRequest2 as DeleteObjectsRequest; - final expectedKeysForRequest1 = await Future.wait( - testKeys.take(1000).map( - (key) async => '${await testPrefixResolver.resolvePrefix( - accessLevel: testOptions.accessLevel!, - )}$key', - ), + final expectedKeysForRequest1 = await pathResolver.resolvePaths( + paths: testPaths.take(1000).toList(), ); - final expectedKeysForRequest2 = await Future.wait( - testKeys.skip(1000).map( - (key) async => '${await testPrefixResolver.resolvePrefix( - accessLevel: testOptions.accessLevel!, - )}$key', - ), + + final expectedKeysForRequest2 = await pathResolver.resolvePaths( + paths: testPaths.skip(1000).toList(), ); expect( @@ -1675,9 +1312,6 @@ void main() { request2.delete.objects.map((object) => object.key), containsAllInOrder(expectedKeysForRequest2), ); - }); - - test('should return correct S3RemoveManyResult', () { final removedItems = removeManyResult.removedItems; final removeErrors = removeManyResult.removeErrors; @@ -1687,7 +1321,7 @@ void main() { removedItems.asMap().forEach((index, item) { final lookupIndex = index < 950 ? index : index + testNumOfRemoveErrors; - expect(item.key, testKeys[lookupIndex]); + expect(item.path, resolvedPaths[lookupIndex]); }); }); @@ -1706,7 +1340,7 @@ void main() { expect( storageS3Service.removeMany( - keys: testKeys, + paths: testPaths, options: testOptions, ), throwsA(isA()), @@ -1714,8 +1348,7 @@ void main() { }); test('should handle AWSHttpRequest and throw NetworkException', () async { - const testOptions = - StorageRemoveManyOptions(accessLevel: StorageAccessLevel.guest); + const testOptions = StorageRemoveManyOptions(); final testException = AWSHttpException( AWSHttpRequest(method: AWSHttpMethod.delete, uri: Uri()), ); @@ -1726,7 +1359,7 @@ void main() { expect( storageS3Service.removeMany( - keys: testKeys, + paths: testPaths, options: testOptions, ), throwsA(isA()), diff --git a/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/task/s3_download_task_test.dart b/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/task/s3_download_task_test.dart index c1fa8b60f1..ae1ea95f3d 100644 --- a/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/task/s3_download_task_test.dart +++ b/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/task/s3_download_task_test.dart @@ -15,24 +15,18 @@ import 'package:test/test.dart'; import '../../test_utils/helper_types.dart'; import '../../test_utils/mocks.dart'; -import '../storage_s3_service_test.dart'; +import '../../test_utils/test_path_resolver.dart'; void main() { group('S3DownloadTask', () { const testBucket = 'bucket1'; - const testDefaultAccessLevel = StorageAccessLevel.guest; const testKey = 'some-object'; - const defaultTestOptions = StorageDownloadDataOptions( - accessLevel: StorageAccessLevel.private, - ); - final testPrefixResolver = TestPrefixResolver(); + const defaultTestOptions = StorageDownloadDataOptions(); const defaultS3ClientConfig = S3ClientConfig(); late S3Client s3Client; - late AWSLogger logger; setUpAll(() { s3Client = MockS3Client(); - logger = MockAWSLogger(); registerFallbackValue( GetObjectRequest( @@ -63,12 +57,10 @@ void main() { final downloadTask = S3DownloadTask( s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), + pathResolver: TestPathResolver(), options: defaultTestOptions, - logger: logger, preStart: testPreStart, ); @@ -102,12 +94,10 @@ void main() { final downloadTask = S3DownloadTask( s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), + pathResolver: TestPathResolver(), options: const StorageDownloadDataOptions(), - logger: logger, onProgress: (progress) { finalState = progress.state; }, @@ -128,9 +118,7 @@ void main() { expect(request.bucket, testBucket); expect( request.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: testDefaultAccessLevel, - )}$testKey', + TestPathResolver.path, ); expect(request.checksumMode, ChecksumMode.enabled); @@ -143,7 +131,6 @@ void main() { () async { const testUseAccelerateEndpoint = true; const testOptions = StorageDownloadDataOptions( - accessLevel: StorageAccessLevel.guest, pluginOptions: S3DownloadDataPluginOptions( useAccelerateEndpoint: testUseAccelerateEndpoint, ), @@ -169,12 +156,10 @@ void main() { final downloadTask = S3DownloadTask( s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), + pathResolver: TestPathResolver(), options: testOptions, - logger: logger, ); await downloadTask.start(); @@ -219,12 +204,10 @@ void main() { final downloadTask = S3DownloadTask( s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), + pathResolver: TestPathResolver(), options: defaultTestOptions, - logger: logger, onError: () { onErrorHasBeenCalled = true; }, @@ -245,16 +228,14 @@ void main() { final downloadTask = S3DownloadTask( s3Client: s3Client, defaultS3ClientConfig: const S3ClientConfig(usePathStyle: true), - prefixResolver: testPrefixResolver, bucket: 'bucket.name.has.dots.com', - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString('public/$testKey'), + pathResolver: TestPathResolver(), options: const StorageDownloadDataOptions( pluginOptions: S3DownloadDataPluginOptions( useAccelerateEndpoint: true, ), ), - logger: logger, ); unawaited(downloadTask.start()); @@ -296,12 +277,10 @@ void main() { final downloadTask = S3DownloadTask( s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString('public/$testKey'), + pathResolver: TestPathResolver(), options: defaultTestOptions, - logger: logger, onProgress: (progress) { receivedState.add(progress.state); }, @@ -343,12 +322,10 @@ void main() { final downloadTask = S3DownloadTask( s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString('public/$testKey'), + pathResolver: TestPathResolver(), options: defaultTestOptions, - logger: logger, onProgress: (progress) { receivedState.add(progress.state); }, @@ -414,12 +391,10 @@ void main() { final downloadTask = S3DownloadTask( s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString('public/$testKey'), + pathResolver: TestPathResolver(), options: defaultTestOptions, - logger: logger, onProgress: (progress) { receivedState.add(progress.state); }, @@ -487,12 +462,10 @@ void main() { final downloadTask = S3DownloadTask( s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString('public/$testKey'), + pathResolver: TestPathResolver(), options: defaultTestOptions, - logger: logger, ); unawaited(downloadTask.start()); @@ -506,12 +479,10 @@ void main() { downloadTask = S3DownloadTask( s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString('public/$testKey'), + pathResolver: TestPathResolver(), options: defaultTestOptions, - logger: logger, ); }); @@ -559,12 +530,10 @@ void main() { downloadTask = S3DownloadTask( s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString('public/$testKey'), + pathResolver: TestPathResolver(), options: defaultTestOptions, - logger: logger, ); }); @@ -614,12 +583,10 @@ void main() { final downloadTask = S3DownloadTask( s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString('public/$testKey'), + pathResolver: TestPathResolver(), options: defaultTestOptions, - logger: logger, onProgress: (progress) { finalState = progress.state; }, @@ -658,12 +625,10 @@ void main() { final downloadTask = S3DownloadTask( s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString('public/$testKey'), + pathResolver: TestPathResolver(), options: defaultTestOptions, - logger: logger, onDone: () async { onDoneHasBeenCalled = true; throw testOnDoneException; @@ -691,13 +656,13 @@ void main() { description: 'should include metadata when getPropertiesValue is set to true', getPropertiesValue: true, - expectedS3Item: S3Item(key: testKey, metadata: testMetadata), + expectedS3Item: S3Item(path: testKey, metadata: testMetadata), ), GetPropertiesTestItem( description: 'should NOT include metadata when getPropertiesValue is set to false', getPropertiesValue: false, - expectedS3Item: S3Item(key: testKey, metadata: {}), + expectedS3Item: S3Item(path: testKey, metadata: {}), ), ]; @@ -726,17 +691,14 @@ void main() { final downloadTask = S3DownloadTask( s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - defaultAccessLevel: testDefaultAccessLevel, - prefixResolver: testPrefixResolver, bucket: testBucket, - key: testKey, + path: const StoragePath.fromString('public/$testKey'), + pathResolver: TestPathResolver(), options: StorageDownloadDataOptions( - accessLevel: StorageAccessLevel.guest, pluginOptions: S3DownloadDataPluginOptions( getProperties: item.getPropertiesValue, ), ), - logger: logger, ); unawaited(downloadTask.start()); diff --git a/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/task/s3_upload_task_test.dart b/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/task/s3_upload_task_test.dart index 2d87e7b9f9..094aef27c4 100644 --- a/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/task/s3_upload_task_test.dart +++ b/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/task/s3_upload_task_test.dart @@ -19,7 +19,7 @@ import 'package:test/test.dart'; import '../../test_utils/io_mocks.dart'; import '../../test_utils/mocks.dart'; -import '../../test_utils/test_custom_prefix_resolver.dart'; +import '../../test_utils/test_path_resolver.dart'; void main() { group('S3UploadTask', () { @@ -28,11 +28,8 @@ void main() { late transfer.TransferDatabase transferDatabase; const testBucket = 'fake-bucket'; const defaultS3ClientConfig = smithy_aws.S3ClientConfig(); - final testPrefixResolver = TestCustomPrefixResolver(); - const testUploadDataOptions = StorageUploadDataOptions( - accessLevel: StorageAccessLevel.private, - ); - const testDefaultAccessLevel = StorageAccessLevel.guest; + final pathResolver = TestPathResolver(); + const testUploadDataOptions = StorageUploadDataOptions(); setUpAll(() { s3Client = MockS3Client(); @@ -91,7 +88,7 @@ void main() { group('Uploading S3DataPayload', () { final testDataPayload = S3DataPayload.string('Upload me please!'); final testDataPayloadBytes = S3DataPayload.bytes([101, 102]); - const testKey = 'object-upload-to'; + const testPath = StoragePath.fromString('object-upload-to'); test( 'should invoke S3Client.putObject API with expected parameters and default access level', @@ -116,10 +113,9 @@ void main() { testDataPayload, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: testPath, options: const StorageUploadDataOptions(), logger: logger, transferDatabase: transferDatabase, @@ -129,7 +125,7 @@ void main() { final result = await uploadDataTask.result; - expect(result.key, testKey); + expect(result.path, TestPathResolver.path); final capturedRequest = verify( () => s3Client.putObject( @@ -143,9 +139,7 @@ void main() { expect(request.bucket, testBucket); expect( request.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: testDefaultAccessLevel, - )}$testKey', + TestPathResolver.path, ); expect(request.body, testDataPayload); }); @@ -154,7 +148,6 @@ void main() { 'should invoke S3Client.putObject API with correct useAcceleration parameters', () async { const testUploadDataOptions = StorageUploadDataOptions( - accessLevel: StorageAccessLevel.private, pluginOptions: S3UploadDataPluginOptions( useAccelerateEndpoint: true, ), @@ -179,10 +172,9 @@ void main() { testDataPayload, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: testPath, options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -230,10 +222,9 @@ void main() { testDataPayloadBytes, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: testPath, options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -265,7 +256,6 @@ void main() { 'should invoke S3Client.headObject API with correct parameters when getProperties is set to true in the options', () async { const testUploadDataOptions = StorageUploadDataOptions( - accessLevel: StorageAccessLevel.private, pluginOptions: S3UploadDataPluginOptions( getProperties: true, ), @@ -301,10 +291,9 @@ void main() { testDataPayload, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: testPath, options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -322,31 +311,8 @@ void main() { expect(request.bucket, testBucket); expect( request.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: testUploadDataOptions.accessLevel!, - )}$testKey', - ); - }); - - test('should throw StorageException when prefix resolving fails', () { - final prefixResolverThrowsException = - TestCustomPrefixResolverThrowsException(); - - final uploadDataTask = S3UploadTask.fromDataPayload( - testDataPayload, - s3Client: s3Client, - defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: prefixResolverThrowsException, - bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, - options: testUploadDataOptions, - logger: logger, - transferDatabase: transferDatabase, + TestPathResolver.path, ); - - unawaited(uploadDataTask.start()); - expect(uploadDataTask.result, throwsA(isA())); }); test( @@ -368,10 +334,9 @@ void main() { testDataPayload, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: testPath, options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -388,9 +353,7 @@ void main() { test( 'should throw NetworkException when S3Client.putObject' ' returned AWSHttpException', () { - const testUploadDataOptions = StorageUploadDataOptions( - accessLevel: StorageAccessLevel.private, - ); + const testUploadDataOptions = StorageUploadDataOptions(); final testException = AWSHttpException( AWSHttpRequest(method: AWSHttpMethod.put, uri: Uri()), ); @@ -406,10 +369,9 @@ void main() { testDataPayload, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: testPath, options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -452,10 +414,9 @@ void main() { testDataPayload, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: testPath, options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -505,10 +466,9 @@ void main() { testLocalFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -518,7 +478,7 @@ void main() { final result = await uploadDataTask.result; - expect(result.key, testKey); + expect(result.path, TestPathResolver.path); final capturedRequest = verify( () => s3Client.putObject( @@ -532,9 +492,7 @@ void main() { expect(request.bucket, testBucket); expect( request.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: testUploadDataOptions.accessLevel!, - )}$testKey', + TestPathResolver.path, ); expect(request.contentType, await testLocalFile.contentType); expect(await request.body.toList(), equals([testBytes])); @@ -544,7 +502,6 @@ void main() { 'should invoke S3Client.putObject with correct useAcceleration parameter', () async { const testUploadDataOptions = StorageUploadDataOptions( - accessLevel: StorageAccessLevel.private, pluginOptions: S3UploadDataPluginOptions( useAccelerateEndpoint: true, ), @@ -569,10 +526,9 @@ void main() { testLocalFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -626,10 +582,9 @@ void main() { testLocalFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -681,10 +636,9 @@ void main() { testLocalFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -722,7 +676,6 @@ void main() { } const testUploadDataOptions = StorageUploadDataOptions( - accessLevel: StorageAccessLevel.protected, metadata: {'filename': 'png.png'}, pluginOptions: S3UploadDataPluginOptions( getProperties: true, @@ -821,10 +774,9 @@ void main() { testLocalFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -855,9 +807,7 @@ void main() { ); expect( createMultipartUploadRequest.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: testUploadDataOptions.accessLevel!, - )}$testKey', + TestPathResolver.path, ); expect( capturedCreateMultipartUploadRequest.metadata?['filename'], @@ -895,9 +845,7 @@ void main() { expect(request.bucket, testBucket); expect( request.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: testUploadDataOptions.accessLevel!, - )}$testKey', + TestPathResolver.path, ); partNumbers.add(request.partNumber!); bytes.add( @@ -931,9 +879,7 @@ void main() { expect(completeMultipartUploadRequest.bucket, testBucket); expect( completeMultipartUploadRequest.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: testUploadDataOptions.accessLevel!, - )}$testKey', + TestPathResolver.path, ); final capturedTransferDBDeleteParam = verify( @@ -951,7 +897,6 @@ void main() { 'should invoke S3Client uploadPart API with correct useAcceleration parameter', () async { const testUploadDataOptions = StorageUploadDataOptions( - accessLevel: StorageAccessLevel.protected, pluginOptions: S3UploadDataPluginOptions( useAccelerateEndpoint: true, ), @@ -1011,10 +956,9 @@ void main() { testLocalFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -1104,10 +1048,9 @@ void main() { testLocalFileWithoutContentType, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -1206,10 +1149,9 @@ void main() { testLocalFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -1244,10 +1186,9 @@ void main() { testLocalFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -1277,10 +1218,9 @@ void main() { testBadFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -1307,10 +1247,9 @@ void main() { testLocalFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -1352,13 +1291,10 @@ void main() { testLocalFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, - options: const StorageUploadDataOptions( - accessLevel: StorageAccessLevel.guest, - ), + path: const StoragePath.fromString(testKey), + options: const StorageUploadDataOptions(), logger: logger, transferDatabase: transferDatabase, onProgress: (progress) { @@ -1398,10 +1334,9 @@ void main() { testLocalFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -1442,10 +1377,9 @@ void main() { testLocalFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -1524,10 +1458,9 @@ void main() { testLocalFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -1617,13 +1550,10 @@ void main() { testLocalFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, - options: const StorageUploadDataOptions( - accessLevel: StorageAccessLevel.guest, - ), + path: const StoragePath.fromString(testKey), + options: const StorageUploadDataOptions(), logger: logger, transferDatabase: transferDatabase, onProgress: (progress) { @@ -1711,10 +1641,9 @@ void main() { testLocalFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -1782,10 +1711,9 @@ void main() { testLocalFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -1939,10 +1867,9 @@ void main() { testLocalFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -1998,10 +1925,9 @@ void main() { testLocalFile, s3Client: s3Client, defaultS3ClientConfig: defaultS3ClientConfig, - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: testKey, + path: const StoragePath.fromString(testKey), options: testUploadDataOptions, logger: logger, transferDatabase: transferDatabase, @@ -2057,16 +1983,16 @@ void main() { }); group('path style URL', () { + const testKey = 'object-upload-to'; test('throw exception when attempt to use accelerate endpoint', () { final uploadTask = S3UploadTask.fromAWSFile( AWSFile.fromPath('fake/file.jpg'), s3Client: s3Client, defaultS3ClientConfig: const smithy_aws.S3ClientConfig(usePathStyle: true), - prefixResolver: testPrefixResolver, + pathResolver: pathResolver, bucket: testBucket, - defaultAccessLevel: testDefaultAccessLevel, - key: 'fake-key', + path: const StoragePath.fromString(testKey), options: const StorageUploadDataOptions( pluginOptions: S3UploadDataPluginOptions( useAccelerateEndpoint: true, diff --git a/packages/storage/amplify_storage_s3_dart/test/test_utils/test_path_resolver.dart b/packages/storage/amplify_storage_s3_dart/test/test_utils/test_path_resolver.dart new file mode 100644 index 0000000000..546d84033a --- /dev/null +++ b/packages/storage/amplify_storage_s3_dart/test/test_utils/test_path_resolver.dart @@ -0,0 +1,24 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:amplify_core/amplify_core.dart'; +import 'package:amplify_storage_s3_dart/src/path_resolver/s3_path_resolver.dart'; +import 'package:mocktail/mocktail.dart'; + +class TestPathResolver extends Mock implements S3PathResolver { + @override + Future resolvePath({ + required StoragePath path, + String? identityId, + }) async { + return TestPathResolver.path; + } + + static String path = 'mock/path'; + + @override + Future> resolvePaths({required List paths}) async { + // ignore: invalid_use_of_internal_member + return paths.map((path) => path.resolvePath()).toList(); + } +}