From 251c18447439be718bd1c07a96ac3d120032d4c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 22:24:33 +0000 Subject: [PATCH 1/2] chore(deps): bump serve-static and express Bumps [serve-static](https://github.com/expressjs/serve-static) to 1.16.2 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together. Updates `serve-static` from 1.15.0 to 1.16.2 - [Release notes](https://github.com/expressjs/serve-static/releases) - [Changelog](https://github.com/expressjs/serve-static/blob/v1.16.2/HISTORY.md) - [Commits](https://github.com/expressjs/serve-static/compare/v1.15.0...v1.16.2) Updates `express` from 4.19.2 to 4.21.0 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md) - [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0) --- updated-dependencies: - dependency-name: serve-static dependency-type: indirect - dependency-name: express dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .../LocalServer/package-lock.json | 100 ++++++++++-------- 1 file changed, 57 insertions(+), 43 deletions(-) diff --git a/AmplifyPlugins/Notifications/Push/Tests/PushNotificationHostApp/LocalServer/package-lock.json b/AmplifyPlugins/Notifications/Push/Tests/PushNotificationHostApp/LocalServer/package-lock.json index 90c039c020..7608478472 100644 --- a/AmplifyPlugins/Notifications/Push/Tests/PushNotificationHostApp/LocalServer/package-lock.json +++ b/AmplifyPlugins/Notifications/Push/Tests/PushNotificationHostApp/LocalServer/package-lock.json @@ -30,9 +30,9 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -42,7 +42,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -157,9 +157,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } @@ -197,36 +197,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -238,12 +238,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -399,9 +399,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/methods": { "version": "1.1.2", @@ -455,9 +458,12 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -482,9 +488,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -499,11 +505,11 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -559,9 +565,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -581,20 +587,28 @@ "node": ">= 0.8.0" } }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" From c229b654fa6d5bf3c2e9738a14097290c33caf28 Mon Sep 17 00:00:00 2001 From: Sebastian Villena <97059974+ruisebas@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:38:39 -0400 Subject: [PATCH 2/2] feat(Storage): Implementing support for multiple buckets (#3839) --- .../Request/StorageDownloadDataRequest.swift | 26 +- .../Request/StorageDownloadFileRequest.swift | 27 +- .../Request/StorageGetURLRequest.swift | 36 +- .../Request/StorageListRequest.swift | 40 +- .../Request/StorageRemoveRequest.swift | 22 +- .../Request/StorageUploadDataRequest.swift | 40 +- .../Request/StorageUploadFileRequest.swift | 40 +- .../Categories/Storage/StorageBucket.swift | 63 + .../Configuration/AmplifyOutputsData.swift | 19 + ...SS3StoragePlugin+AsyncClientBehavior.swift | 23 +- .../AWSS3StoragePlugin+ClientBehavior.swift | 2 +- .../AWSS3StoragePlugin+Configure.swift | 76 +- .../AWSS3StoragePlugin+Reset.swift | 13 +- .../AWSS3StoragePlugin+StorageBucket.swift | 74 + .../AWSS3StoragePlugin.swift | 23 +- .../AWSS3StorageDownloadDataOperation.swift | 14 +- .../AWSS3StorageDownloadFileOperation.swift | 14 +- .../AWSS3StorageUploadDataOperation.swift | 16 +- .../AWSS3StorageUploadFileOperation.swift | 16 +- .../Service/Storage/AWSS3StorageService.swift | 6 +- .../Storage/AWSS3StorageServiceBehavior.swift | 1 + .../Internal/StorageConfiguration.swift | 10 +- .../Tasks/AWSS3StorageListObjectsTask.swift | 1 + .../AWSS3StoragePluginResetTests.swift | 2 +- ...AWSS3StoragePluginStorageBucketTests.swift | 174 ++ .../AWSS3StoragePluginTestBase.swift | 23 +- ...ageOperations+StorageServiceProvider.swift | 99 + ...WSS3StoragePluginMultipleBucketTests.swift | 498 +++++ .../AWSS3StoragePluginTestBase.swift | 46 +- .../StorageHostApp.xcodeproj/project.pbxproj | 4 + .../StorageStressTests.swift | 16 +- api-dump/AWSDataStorePlugin.json | 2 +- api-dump/AWSPluginsCore.json | 2 +- api-dump/Amplify.json | 1725 ++++++++++++++++- api-dump/CoreMLPredictionsPlugin.json | 2 +- 35 files changed, 3075 insertions(+), 120 deletions(-) create mode 100644 Amplify/Categories/Storage/StorageBucket.swift create mode 100644 AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+StorageBucket.swift create mode 100644 AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginStorageBucketTests.swift create mode 100644 AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/AWSS3StorageOperations+StorageServiceProvider.swift create mode 100644 AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginMultipleBucketTests.swift diff --git a/Amplify/Categories/Storage/Operation/Request/StorageDownloadDataRequest.swift b/Amplify/Categories/Storage/Operation/Request/StorageDownloadDataRequest.swift index 1a8ed260b9..73cf9f57fc 100644 --- a/Amplify/Categories/Storage/Operation/Request/StorageDownloadDataRequest.swift +++ b/Amplify/Categories/Storage/Operation/Request/StorageDownloadDataRequest.swift @@ -64,6 +64,11 @@ public extension StorageDownloadDataRequest { @available(*, deprecated, message: "Use `path` in Storage API instead of `Options`") public let targetIdentityId: String? + /// A Storage Bucket that contains the object to download. Defaults to `nil`, in which case the default one will be used. + /// + /// - Tag: StorageDownloadDataRequest.bucket + public let bucket: (any StorageBucket)? + /// Extra plugin specific options, only used in special circumstances when the existing options do not provide /// a way to utilize the underlying storage system's functionality. See plugin documentation for expected /// key/values @@ -91,11 +96,14 @@ public extension StorageDownloadDataRequest { /// /// - Tag: StorageDownloadDataRequestOptions.init @available(*, deprecated, message: "Use init(pluginOptions)") - public init(accessLevel: StorageAccessLevel = .guest, - targetIdentityId: String? = nil, - pluginOptions: Any? = nil) { + public init( + accessLevel: StorageAccessLevel = .guest, + targetIdentityId: String? = nil, + pluginOptions: Any? = nil + ) { self.accessLevel = accessLevel self.targetIdentityId = targetIdentityId + self.bucket = nil self.pluginOptions = pluginOptions } @@ -104,6 +112,18 @@ public extension StorageDownloadDataRequest { public init(pluginOptions: Any? = nil) { self.accessLevel = .guest self.targetIdentityId = nil + self.bucket = nil + self.pluginOptions = pluginOptions + } + + /// - Tag: StorageDownloadDataRequestOptions.init + public init( + bucket: some StorageBucket, + pluginOptions: Any? = nil + ) { + self.accessLevel = .guest + self.targetIdentityId = nil + self.bucket = bucket self.pluginOptions = pluginOptions } } diff --git a/Amplify/Categories/Storage/Operation/Request/StorageDownloadFileRequest.swift b/Amplify/Categories/Storage/Operation/Request/StorageDownloadFileRequest.swift index 7ec34c222f..cc68d539bd 100644 --- a/Amplify/Categories/Storage/Operation/Request/StorageDownloadFileRequest.swift +++ b/Amplify/Categories/Storage/Operation/Request/StorageDownloadFileRequest.swift @@ -71,6 +71,11 @@ public extension StorageDownloadFileRequest { @available(*, deprecated, message: "Use `path` in Storage API instead of `Options`") public let targetIdentityId: String? + /// A Storage Bucket that contains the object to download. Defaults to `nil`, in which case the default one will be used. + /// + /// - Tag: StorageDownloadDataRequest.bucket + public let bucket: (any StorageBucket)? + /// Extra plugin specific options, only used in special circumstances when the existing options do not provide /// a way to utilize the underlying storage system's functionality. See plugin documentation for expected /// key/values @@ -80,19 +85,33 @@ public extension StorageDownloadFileRequest { /// - Tag: StorageDownloadFileRequestOptions.init @available(*, deprecated, message: "Use init(pluginOptions)") - public init(accessLevel: StorageAccessLevel = .guest, - targetIdentityId: String? = nil, - pluginOptions: Any? = nil) { + public init( + accessLevel: StorageAccessLevel = .guest, + targetIdentityId: String? = nil, + pluginOptions: Any? = nil + ) { self.accessLevel = accessLevel self.targetIdentityId = targetIdentityId + self.bucket = nil self.pluginOptions = pluginOptions } /// - Tag: StorageDownloadFileRequestOptions.init - @available(*, deprecated, message: "Use init(pluginOptions)") public init(pluginOptions: Any? = nil) { self.accessLevel = .guest self.targetIdentityId = nil + self.bucket = nil + self.pluginOptions = pluginOptions + } + + /// - Tag: StorageDownloadFileRequestOptions.init + public init( + bucket: some StorageBucket, + pluginOptions: Any? = nil + ) { + self.accessLevel = .guest + self.targetIdentityId = nil + self.bucket = bucket self.pluginOptions = pluginOptions } } diff --git a/Amplify/Categories/Storage/Operation/Request/StorageGetURLRequest.swift b/Amplify/Categories/Storage/Operation/Request/StorageGetURLRequest.swift index e8ffd22c00..c86db0b4ba 100644 --- a/Amplify/Categories/Storage/Operation/Request/StorageGetURLRequest.swift +++ b/Amplify/Categories/Storage/Operation/Request/StorageGetURLRequest.swift @@ -73,6 +73,11 @@ public extension StorageGetURLRequest { /// - Tag: StorageGetURLRequest.Options.expires public let expires: Int + /// A Storage Bucket that contains the object. Defaults to `nil`, in which case the default one will be used. + /// + /// - Tag: StorageDownloadDataRequest.bucket + public let bucket: (any StorageBucket)? + /// Extra plugin specific options, only used in special circumstances when the existing options do /// not provide a way to utilize the underlying storage system's functionality. See plugin /// documentation or @@ -84,20 +89,39 @@ public extension StorageGetURLRequest { /// - Tag: StorageGetURLRequest.Options.init @available(*, deprecated, message: "Use init(expires:pluginOptions)") - public init(accessLevel: StorageAccessLevel = .guest, - targetIdentityId: String? = nil, - expires: Int = Options.defaultExpireInSeconds, - pluginOptions: Any? = nil) { + public init( + accessLevel: StorageAccessLevel = .guest, + targetIdentityId: String? = nil, + expires: Int = Options.defaultExpireInSeconds, + pluginOptions: Any? = nil + ) { self.accessLevel = accessLevel self.targetIdentityId = targetIdentityId self.expires = expires + self.bucket = nil self.pluginOptions = pluginOptions } /// - Tag: StorageGetURLRequest.Options.init - public init(expires: Int = Options.defaultExpireInSeconds, - pluginOptions: Any? = nil) { + public init( + expires: Int = Options.defaultExpireInSeconds, + pluginOptions: Any? = nil + ) { + self.expires = expires + self.bucket = nil + self.pluginOptions = pluginOptions + self.accessLevel = .guest + self.targetIdentityId = nil + } + + /// - Tag: StorageGetURLRequest.Options.init + public init( + expires: Int = Options.defaultExpireInSeconds, + bucket: some StorageBucket, + pluginOptions: Any? = nil + ) { self.expires = expires + self.bucket = bucket self.pluginOptions = pluginOptions self.accessLevel = .guest self.targetIdentityId = nil diff --git a/Amplify/Categories/Storage/Operation/Request/StorageListRequest.swift b/Amplify/Categories/Storage/Operation/Request/StorageListRequest.swift index 71119f71f8..0c66976aeb 100644 --- a/Amplify/Categories/Storage/Operation/Request/StorageListRequest.swift +++ b/Amplify/Categories/Storage/Operation/Request/StorageListRequest.swift @@ -78,6 +78,11 @@ public extension StorageListRequest { /// - Tag: StorageListRequestOptions.pageSize public let pageSize: UInt + /// A Storage Bucket that contains the objects to list. Defaults to `nil`, in which case the default one will be used. + /// + /// - Tag: StorageDownloadDataRequest.bucket + public let bucket: (any StorageBucket)? + /// Opaque string indicating the page offset at which to resume a listing. This is usually a copy of /// the value from [StorageListResult.nextToken](x-source-tag://StorageListResult.nextToken). /// @@ -96,18 +101,39 @@ public extension StorageListRequest { public let pluginOptions: Any? /// - Tag: StorageListRequestOptions.init - public init(accessLevel: StorageAccessLevel = .guest, - targetIdentityId: String? = nil, - path: String? = nil, - subpathStrategy: SubpathStrategy = .include, - pageSize: UInt = 1000, - nextToken: String? = nil, - pluginOptions: Any? = nil) { + public init( + accessLevel: StorageAccessLevel = .guest, + targetIdentityId: String? = nil, + path: String? = nil, + subpathStrategy: SubpathStrategy = .include, + pageSize: UInt = 1000, + nextToken: String? = nil, + pluginOptions: Any? = nil + ) { self.accessLevel = accessLevel self.targetIdentityId = targetIdentityId self.path = path self.subpathStrategy = subpathStrategy self.pageSize = pageSize + self.bucket = nil + self.nextToken = nextToken + self.pluginOptions = pluginOptions + } + + /// - Tag: StorageListRequestOptions.init + public init( + subpathStrategy: SubpathStrategy = .include, + pageSize: UInt = 1000, + bucket: some StorageBucket, + nextToken: String? = nil, + pluginOptions: Any? = nil + ) { + self.accessLevel = .guest + self.targetIdentityId = nil + self.path = nil + self.subpathStrategy = subpathStrategy + self.pageSize = pageSize + self.bucket = bucket self.nextToken = nextToken self.pluginOptions = pluginOptions } diff --git a/Amplify/Categories/Storage/Operation/Request/StorageRemoveRequest.swift b/Amplify/Categories/Storage/Operation/Request/StorageRemoveRequest.swift index 31ae1de4f3..39b10e099d 100644 --- a/Amplify/Categories/Storage/Operation/Request/StorageRemoveRequest.swift +++ b/Amplify/Categories/Storage/Operation/Request/StorageRemoveRequest.swift @@ -56,6 +56,11 @@ public extension StorageRemoveRequest { @available(*, deprecated, message: "Use `path` in Storage API instead of `Options`") public let accessLevel: StorageAccessLevel + /// A Storage Bucket that contains the object to remove. Defaults to `nil`, in which case the default one will be used. + /// + /// - Tag: StorageDownloadDataRequest.bucket + public let bucket: (any StorageBucket)? + /// Extra plugin specific options, only used in special circumstances when the existing options do not provide /// a way to utilize the underlying storage system's functionality. See plugin documentation for expected /// key/values @@ -64,9 +69,22 @@ public extension StorageRemoveRequest { public let pluginOptions: Any? /// - Tag: StorageRemoveRequestOptions.init - public init(accessLevel: StorageAccessLevel = .guest, - pluginOptions: Any? = nil) { + public init( + accessLevel: StorageAccessLevel = .guest, + pluginOptions: Any? = nil + ) { self.accessLevel = accessLevel + self.bucket = nil + self.pluginOptions = pluginOptions + } + + /// - Tag: StorageRemoveRequestOptions.init + public init( + bucket: some StorageBucket, + pluginOptions: Any? = nil + ) { + self.accessLevel = .guest + self.bucket = bucket self.pluginOptions = pluginOptions } } diff --git a/Amplify/Categories/Storage/Operation/Request/StorageUploadDataRequest.swift b/Amplify/Categories/Storage/Operation/Request/StorageUploadDataRequest.swift index 572b160bf8..174aa06fc9 100644 --- a/Amplify/Categories/Storage/Operation/Request/StorageUploadDataRequest.swift +++ b/Amplify/Categories/Storage/Operation/Request/StorageUploadDataRequest.swift @@ -75,6 +75,11 @@ public extension StorageUploadDataRequest { /// - Tag: StorageUploadDataRequestOptions.metadata public let metadata: [String: String]? + /// A specific Storage Bucket to upload the data. Defaults to `nil`, in which case the default one will be used. + /// + /// - Tag: StorageDownloadDataRequest.bucket + public let bucket: (any StorageBucket)? + /// The standard MIME type describing the format of the object to store /// /// - Tag: StorageUploadDataRequestOptions.contentType @@ -89,27 +94,46 @@ public extension StorageUploadDataRequest { /// - Tag: StorageUploadDataRequestOptions.init @available(*, deprecated, message: "Use init(metadata:contentType:options)") - public init(accessLevel: StorageAccessLevel = .guest, - targetIdentityId: String? = nil, - metadata: [String: String]? = nil, - contentType: String? = nil, - pluginOptions: Any? = nil + public init( + accessLevel: StorageAccessLevel = .guest, + targetIdentityId: String? = nil, + metadata: [String: String]? = nil, + contentType: String? = nil, + pluginOptions: Any? = nil ) { self.accessLevel = accessLevel self.targetIdentityId = targetIdentityId self.metadata = metadata + self.bucket = nil + self.contentType = contentType + self.pluginOptions = pluginOptions + } + + /// - Tag: StorageUploadDataRequestOptions.init + public init( + metadata: [String: String]? = nil, + contentType: String? = nil, + pluginOptions: Any? = nil + ) { + self.accessLevel = .guest + self.targetIdentityId = nil + self.metadata = metadata + self.bucket = nil self.contentType = contentType self.pluginOptions = pluginOptions } /// - Tag: StorageUploadDataRequestOptions.init - public init(metadata: [String: String]? = nil, - contentType: String? = nil, - pluginOptions: Any? = nil + public init( + metadata: [String: String]? = nil, + bucket: some StorageBucket, + contentType: String? = nil, + pluginOptions: Any? = nil ) { self.accessLevel = .guest self.targetIdentityId = nil self.metadata = metadata + self.bucket = bucket self.contentType = contentType self.pluginOptions = pluginOptions } diff --git a/Amplify/Categories/Storage/Operation/Request/StorageUploadFileRequest.swift b/Amplify/Categories/Storage/Operation/Request/StorageUploadFileRequest.swift index 23c8d159f6..b7c2e73830 100644 --- a/Amplify/Categories/Storage/Operation/Request/StorageUploadFileRequest.swift +++ b/Amplify/Categories/Storage/Operation/Request/StorageUploadFileRequest.swift @@ -72,6 +72,11 @@ public extension StorageUploadFileRequest { /// - Tag: StorageUploadFileRequestOptions.metadata public let metadata: [String: String]? + /// A specific Storage Bucket to upload the file. Defaults to `nil`, in which case the default one will be used. + /// + /// - Tag: StorageUploadFileRequestOptions.bucket + public let bucket: (any StorageBucket)? + /// The standard MIME type describing the format of the object to store /// /// - Tag: StorageUploadFileRequestOptions.contentType @@ -86,27 +91,46 @@ public extension StorageUploadFileRequest { /// - Tag: StorageUploadFileRequestOptions.init @available(*, deprecated, message: "Use init(metadata:contentType:pluginOptions)") - public init(accessLevel: StorageAccessLevel = .guest, - targetIdentityId: String? = nil, - metadata: [String: String]? = nil, - contentType: String? = nil, - pluginOptions: Any? = nil + public init( + accessLevel: StorageAccessLevel = .guest, + targetIdentityId: String? = nil, + metadata: [String: String]? = nil, + contentType: String? = nil, + pluginOptions: Any? = nil ) { self.accessLevel = accessLevel self.targetIdentityId = targetIdentityId self.metadata = metadata + self.bucket = nil + self.contentType = contentType + self.pluginOptions = pluginOptions + } + + /// - Tag: StorageUploadFileRequestOptions.init + public init( + metadata: [String: String]? = nil, + contentType: String? = nil, + pluginOptions: Any? = nil + ) { + self.accessLevel = .guest + self.targetIdentityId = nil + self.metadata = metadata + self.bucket = nil self.contentType = contentType self.pluginOptions = pluginOptions } /// - Tag: StorageUploadFileRequestOptions.init - public init(metadata: [String: String]? = nil, - contentType: String? = nil, - pluginOptions: Any? = nil + public init( + metadata: [String: String]? = nil, + bucket: some StorageBucket, + contentType: String? = nil, + pluginOptions: Any? = nil ) { self.accessLevel = .guest self.targetIdentityId = nil self.metadata = metadata + self.bucket = bucket self.contentType = contentType self.pluginOptions = pluginOptions } diff --git a/Amplify/Categories/Storage/StorageBucket.swift b/Amplify/Categories/Storage/StorageBucket.swift new file mode 100644 index 0000000000..f987f2b949 --- /dev/null +++ b/Amplify/Categories/Storage/StorageBucket.swift @@ -0,0 +1,63 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation + +/// Protocol that represents a Storage bucket. +/// +/// - Tag: StorageBucket +public protocol StorageBucket { } + +/// Represents information about a Storage bucket +/// +/// - Tag: BucketInfo +public struct BucketInfo: Hashable { + /// The name of the bucket + /// - Tag: BucketInfo.bucketName + public let bucketName: String + + /// The region of the bucket + /// - Tag: BucketInfo.region + public let region: String + + public init(bucketName: String, region: String) { + self.bucketName = bucketName + self.region = region + } +} + +public extension StorageBucket where Self == OutputsStorageBucket { + /// References a `StorageBucket` in the AmplifyOutputs file using the given name. + /// + /// - Parameter name: The name of the bucket + static func fromOutputs(name: String) -> Self { + return OutputsStorageBucket(name: name) + } +} + +public extension StorageBucket where Self == ResolvedStorageBucket { + /// References a `StorageBucket` using the data from the given `BucketInfo`. + /// + /// - Parameter bucketInfo: A `BucketInfo` instance + static func fromBucketInfo(_ bucketInfo: BucketInfo) -> Self { + return ResolvedStorageBucket(bucketInfo: bucketInfo) + } +} + +/// Conforms to `StorageBucket`. Represents a Storage Bucket defined by a name in the AmplifyOutputs file. +/// +/// - Tag: OutputsStorageBucket +public struct OutputsStorageBucket: StorageBucket { + public let name: String +} + +/// Conforms to `StorageBucket`. Represents a Storage Bucket defined by a name and a region defined in `BucketInfo`. +/// +/// - Tag: ResolvedStorageBucket +public struct ResolvedStorageBucket: StorageBucket { + public let bucketInfo: BucketInfo +} diff --git a/Amplify/Core/Configuration/AmplifyOutputsData.swift b/Amplify/Core/Configuration/AmplifyOutputsData.swift index 57e388a3ba..8362306eda 100644 --- a/Amplify/Core/Configuration/AmplifyOutputsData.swift +++ b/Amplify/Core/Configuration/AmplifyOutputsData.swift @@ -176,6 +176,25 @@ public struct AmplifyOutputsData: Codable { public struct Storage: Codable { public let awsRegion: AWSRegion public let bucketName: String + public let buckets: [Bucket]? + + @_spi(InternalAmplifyConfiguration) + public struct Bucket: Codable { + public let name: String + public let bucketName: String + public let awsRegion: AWSRegion + } + + // Internal init used for testing + init( + awsRegion: AWSRegion, + bucketName: String, + buckets: [Bucket]? = nil + ) { + self.awsRegion = awsRegion + self.bucketName = bucketName + self.buckets = buckets + } } @_spi(InternalAmplifyConfiguration) diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+AsyncClientBehavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+AsyncClientBehavior.swift index a960ec1e07..80e454554f 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+AsyncClientBehavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+AsyncClientBehavior.swift @@ -27,6 +27,8 @@ extension AWSS3StoragePlugin { let prefix = try await prefixResolver.resolvePrefix(for: options.accessLevel, targetIdentityId: options.targetIdentityId) let serviceKey = prefix + request.key + + let storageService = try storageService(for: options.bucket) if let pluginOptions = options.pluginOptions as? AWSStorageGetURLOptions, pluginOptions.validateObjectExistence { try await storageService.validateObjectExistence(serviceKey: serviceKey) } @@ -51,6 +53,7 @@ extension AWSS3StoragePlugin { ) async throws -> URL { let options = options ?? StorageGetURLRequest.Options() let request = StorageGetURLRequest(path: path, options: options) + let storageService = try storageService(for: options.bucket) let task = AWSS3StorageGetURLTask( request, storageBehaviour: storageService) @@ -65,7 +68,7 @@ extension AWSS3StoragePlugin { let request = StorageDownloadDataRequest(path: path, options: options) let operation = AWSS3StorageDownloadDataOperation(request, storageConfiguration: storageConfiguration, - storageService: storageService, + storageServiceProvider: storageServiceProvider(for: options.bucket), authService: authService) let taskAdapter = AmplifyInProcessReportingOperationTaskAdapter(operation: operation) queue.addOperation(operation) @@ -82,7 +85,7 @@ extension AWSS3StoragePlugin { let request = StorageDownloadDataRequest(key: key, options: options) let operation = AWSS3StorageDownloadDataOperation(request, storageConfiguration: storageConfiguration, - storageService: storageService, + storageServiceProvider: storageServiceProvider(for: options.bucket), authService: authService) let taskAdapter = AmplifyInProcessReportingOperationTaskAdapter(operation: operation) queue.addOperation(operation) @@ -100,7 +103,7 @@ extension AWSS3StoragePlugin { let request = StorageDownloadFileRequest(key: key, local: local, options: options) let operation = AWSS3StorageDownloadFileOperation(request, storageConfiguration: storageConfiguration, - storageService: storageService, + storageServiceProvider: storageServiceProvider(for: options.bucket), authService: authService) let taskAdapter = AmplifyInProcessReportingOperationTaskAdapter(operation: operation) queue.addOperation(operation) @@ -118,7 +121,7 @@ extension AWSS3StoragePlugin { let request = StorageDownloadFileRequest(path: path, local: local, options: options) let operation = AWSS3StorageDownloadFileOperation(request, storageConfiguration: storageConfiguration, - storageService: storageService, + storageServiceProvider: storageServiceProvider(for: options.bucket), authService: authService) let taskAdapter = AmplifyInProcessReportingOperationTaskAdapter(operation: operation) queue.addOperation(operation) @@ -136,7 +139,7 @@ extension AWSS3StoragePlugin { let request = StorageUploadDataRequest(key: key, data: data, options: options) let operation = AWSS3StorageUploadDataOperation(request, storageConfiguration: storageConfiguration, - storageService: storageService, + storageServiceProvider: storageServiceProvider(for: options.bucket), authService: authService) let taskAdapter = AmplifyInProcessReportingOperationTaskAdapter(operation: operation) queue.addOperation(operation) @@ -154,7 +157,7 @@ extension AWSS3StoragePlugin { let request = StorageUploadDataRequest(path: path, data: data, options: options) let operation = AWSS3StorageUploadDataOperation(request, storageConfiguration: storageConfiguration, - storageService: storageService, + storageServiceProvider: storageServiceProvider(for: options.bucket), authService: authService) let taskAdapter = AmplifyInProcessReportingOperationTaskAdapter(operation: operation) queue.addOperation(operation) @@ -172,7 +175,7 @@ extension AWSS3StoragePlugin { let request = StorageUploadFileRequest(key: key, local: local, options: options) let operation = AWSS3StorageUploadFileOperation(request, storageConfiguration: storageConfiguration, - storageService: storageService, + storageServiceProvider: storageServiceProvider(for: options.bucket), authService: authService) let taskAdapter = AmplifyInProcessReportingOperationTaskAdapter(operation: operation) queue.addOperation(operation) @@ -190,7 +193,7 @@ extension AWSS3StoragePlugin { let request = StorageUploadFileRequest(path: path, local: local, options: options) let operation = AWSS3StorageUploadFileOperation(request, storageConfiguration: storageConfiguration, - storageService: storageService, + storageServiceProvider: storageServiceProvider(for: options.bucket), authService: authService) let taskAdapter = AmplifyInProcessReportingOperationTaskAdapter(operation: operation) queue.addOperation(operation) @@ -205,6 +208,7 @@ extension AWSS3StoragePlugin { ) async throws -> String { let options = options ?? StorageRemoveRequest.Options() let request = StorageRemoveRequest(key: key, options: options) + let storageService = try storageService(for: options.bucket) let operation = AWSS3StorageRemoveOperation(request, storageConfiguration: storageConfiguration, storageService: storageService, @@ -222,6 +226,7 @@ extension AWSS3StoragePlugin { ) async throws -> String { let options = options ?? StorageRemoveRequest.Options() let request = StorageRemoveRequest(path: path, options: options) + let storageService = try storageService(for: options.bucket) let task = AWSS3StorageRemoveTask( request, storageConfiguration: storageConfiguration, @@ -233,6 +238,7 @@ extension AWSS3StoragePlugin { options: StorageListRequest.Options? = nil ) async throws -> StorageListResult { let options = options ?? StorageListRequest.Options() + let storageService = try storageService(for: options.bucket) let prefixResolver = storageConfiguration.prefixResolver ?? StorageAccessLevelAwarePrefixResolver(authService: authService) let prefix = try await prefixResolver.resolvePrefix(for: options.accessLevel, targetIdentityId: options.targetIdentityId) let result = try await storageService.list(prefix: prefix, options: options) @@ -250,6 +256,7 @@ extension AWSS3StoragePlugin { ) async throws -> StorageListResult { let options = options ?? StorageListRequest.Options() let request = StorageListRequest(path: path, options: options) + let storageService = try storageService(for: options.bucket) let task = AWSS3StorageListObjectsTask( request, storageConfiguration: storageConfiguration, diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+ClientBehavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+ClientBehavior.swift index 9778b9cab0..67ad544afb 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+ClientBehavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+ClientBehavior.swift @@ -19,6 +19,6 @@ extension AWSS3StoragePlugin { /// /// - Tag: AWSS3StoragePlugin.getEscapeHatch public func getEscapeHatch() -> S3Client { - return storageService.getEscapeHatch() + return defaultStorageService.getEscapeHatch() } } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+Configure.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+Configure.swift index 9b40e63906..c589208366 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+Configure.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+Configure.swift @@ -8,6 +8,7 @@ import Foundation @_spi(InternalAmplifyConfiguration) import Amplify import AWSPluginsCore +import InternalAmplifyCredentials extension AWSS3StoragePlugin { @@ -27,6 +28,7 @@ extension AWSS3StoragePlugin { let configClosures: ConfigurationClosures if let config = configuration as? AmplifyOutputsData { configClosures = try retrieveConfiguration(config) + additionalBucketsByName = retrieveAdditionalBucketsByName(from: config.storage) } else if let config = configuration as? JSONValue { configClosures = try retrieveConfiguration(config) } else { @@ -38,15 +40,24 @@ extension AWSS3StoragePlugin { do { let authService = AWSAuthService() let defaultAccessLevel = try configClosures.retrieveDefaultAccessLevel() - let storageService = try AWSS3StorageService(authService: authService, - region: configClosures.retrieveRegion(), - bucket: configClosures.retrieveBucket(), - httpClientEngineProxy: self.httpClientEngineProxy) - storageService.urlRequestDelegate = self.urlRequestDelegate - - configure(storageService: storageService, - authService: authService, - defaultAccessLevel: defaultAccessLevel) + let defaultBucket: ResolvedStorageBucket = try .fromBucketInfo( + .init( + bucketName: configClosures.retrieveBucket(), + region: configClosures.retrieveRegion() + ) + ) + + let storageService = try createStorageService( + authService: authService, + bucketInfo: defaultBucket.bucketInfo + ) + + configure( + defaultBucket: defaultBucket, + storageService: storageService, + authService: authService, + defaultAccessLevel: defaultAccessLevel + ) } catch let storageError as StorageError { throw storageError } catch { @@ -67,18 +78,38 @@ extension AWSS3StoragePlugin { /// Called from the configure method which implements the Plugin protocol. Useful for testing by passing in mocks. /// /// - Parameters: - /// - storageService: The S3 storage service object. + /// - defaultBucket: The bucket to be used for all API calls by default. + /// - storageService: The S3 storage service object associated with the default bucket /// - authService: The authentication service object. /// - defaultAccessLevel: The access level to be used for all API calls by default. /// - queue: The queue which operations are stored and dispatched for asychronous processing. - func configure(storageService: AWSS3StorageServiceBehavior, - authService: AWSAuthServiceBehavior, - defaultAccessLevel: StorageAccessLevel, - queue: OperationQueue = OperationQueue()) { - self.storageService = storageService + func configure( + defaultBucket: ResolvedStorageBucket, + storageService: AWSS3StorageServiceBehavior, + authService: AWSAuthCredentialsProviderBehavior, + defaultAccessLevel: StorageAccessLevel, + queue: OperationQueue = OperationQueue() + ) { + self.defaultBucket = defaultBucket self.authService = authService self.queue = queue self.defaultAccessLevel = defaultAccessLevel + self.storageServicesByBucket[defaultBucket.bucketInfo.bucketName] = storageService + } + + /// Creates a new AWSS3StorageServiceBehavior for the given BucketInfo + func createStorageService( + authService: AWSAuthCredentialsProviderBehavior, + bucketInfo: BucketInfo + ) throws -> AWSS3StorageServiceBehavior { + let storageService = try AWSS3StorageService( + authService: authService, + region: bucketInfo.region, + bucket: bucketInfo.bucketName, + httpClientEngineProxy: httpClientEngineProxy + ) + storageService.urlRequestDelegate = urlRequestDelegate + return storageService } // MARK: Private helper methods @@ -127,6 +158,21 @@ extension AWSS3StoragePlugin { retrieveDefaultAccessLevel: defaultAccessLevelClosure) } + /// Retrieves the configured buckets from the configuration grouped by their names. + /// If no buckets are provided in the configuration, an empty dictionary is returned instead. + private func retrieveAdditionalBucketsByName( + from configuration: AmplifyOutputsData.Storage? + ) -> [String: AmplifyOutputsData.Storage.Bucket] { + guard let configuration, + let buckets = configuration.buckets else { + return [:] + } + + return buckets.reduce(into: [:]) { dictionary, bucket in + dictionary[bucket.name] = bucket + } + } + /// Retrieves the region from configuration, validates, and returns it. private static func getRegion(_ configuration: [String: JSONValue]) throws -> String { guard let region = configuration[PluginConstants.region] else { diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+Reset.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+Reset.swift index 5bbceb00b9..6d4c1afaa8 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+Reset.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+Reset.swift @@ -18,10 +18,19 @@ extension AWSS3StoragePlugin { /// /// - Tag: AWSS3StoragePlugin.reset public func reset() async { - if storageService != nil { + if defaultBucket != nil { + defaultBucket = nil + } + + for storageService in storageServicesByBucket.values { storageService.reset() - storageService = nil } + storageServicesByBucket.removeAll() + + if additionalBucketsByName != nil { + additionalBucketsByName = nil + } + if authService != nil { authService = nil } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+StorageBucket.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+StorageBucket.swift new file mode 100644 index 0000000000..19b7fb2063 --- /dev/null +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+StorageBucket.swift @@ -0,0 +1,74 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +@_spi(InternalAmplifyConfiguration) import Amplify +import Foundation + +extension AWSS3StoragePlugin { + /// Returns a AWSS3StorageServiceBehavior instance for the given StorageBucket + func storageService(for bucket: (any StorageBucket)?) throws -> AWSS3StorageServiceBehavior { + guard let bucket else { + // When no bucket is provided, use the default one + return defaultStorageService + } + + let bucketInfo = try bucketInfo(from: bucket) + guard let storageService = storageServicesByBucket[bucketInfo.bucketName] else { + // If no service was found for the bucket, create one + let storageService = try createStorageService( + authService: authService, + bucketInfo: bucketInfo + ) + storageServicesByBucket[bucketInfo.bucketName] = storageService + return storageService + } + + return storageService + } + + /// Returns a AWSS3StorageServiceProvider callback for the given StorageBucket + func storageServiceProvider(for bucket: (any StorageBucket)?) -> AWSS3StorageServiceProvider { + let storageServiceResolver: () throws -> AWSS3StorageServiceBehavior = { [weak self] in + guard let self = self else { + throw StorageError.unknown("AWSS3StoragePlugin was deallocated", nil) + } + return try self.storageService(for: bucket) + } + return storageServiceResolver + } + + /// Returns a valid `BucketInfo` instance from the given StorageBucket + private func bucketInfo(from bucket: any StorageBucket) throws -> BucketInfo { + switch bucket { + case let outputsBucket as OutputsStorageBucket: + guard let additionalBucketsByName else { + let errorDescription = "Amplify was not configured using an Amplify Outputs file" + let recoverySuggestion = "Make sure that `Amplify.configure(with:)` is invoked" + throw StorageError.validation("bucket", errorDescription, recoverySuggestion, nil) + } + + guard let awsBucket = additionalBucketsByName[outputsBucket.name] else { + let errorDescription = "Unable to lookup bucket from provided name in Amplify Outputs" + let recoverySuggestion = "Make sure the bucket name exists in the Amplify Outputs file" + throw StorageError.validation("bucket", errorDescription, recoverySuggestion, nil) + } + + return .init( + bucketName: awsBucket.bucketName, + region: awsBucket.awsRegion + ) + + case let resolvedBucket as ResolvedStorageBucket: + return resolvedBucket.bucketInfo + + default: + let errorDescription = "The specified StorageBucket is not supported" + let recoverySuggestion = "Please specify a StorageBucket from the Outputs file or from BucketInfo" + throw StorageError.validation("bucket", errorDescription, recoverySuggestion, nil) + } + } +} diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin.swift index dc4bbe3f09..d4bd01f795 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin.swift @@ -5,9 +5,10 @@ // SPDX-License-Identifier: Apache-2.0 // -import Amplify +@_spi(InternalAmplifyConfiguration) import Amplify import Foundation import AWSPluginsCore +import InternalAmplifyCredentials /// The AWSS3StoragePlugin which conforms to the Amplify plugin protocols and implements the Storage /// Plugin APIs for AWS S3. @@ -15,11 +16,25 @@ import AWSPluginsCore /// - Tag: AWSS3StoragePlugin final public class AWSS3StoragePlugin: StorageCategoryPlugin { - /// An instance of the S3 storage service. - var storageService: AWSS3StorageServiceBehavior! + /// The default S3 storage service. + var defaultStorageService: AWSS3StorageServiceBehavior! { + guard let defaultBucket else { + return nil + } + return storageServicesByBucket[defaultBucket.bucketInfo.bucketName] + } + + /// The default bucket + var defaultBucket: ResolvedStorageBucket! + + /// A dictionary of S3 storage service instances grouped by a specific bucket + var storageServicesByBucket: AtomicDictionary = [:] + + /// A dictionary of additional Outputs-based buckets, grouped by their names + var additionalBucketsByName: [String: AmplifyOutputsData.Storage.Bucket]? /// An instance of the authentication service. - var authService: AWSAuthServiceBehavior! + var authService: AWSAuthCredentialsProviderBehavior! /// A queue that regulates the execution of operations. var queue: OperationQueue! diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageDownloadDataOperation.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageDownloadDataOperation.swift index 2e56183048..0a1bc25de8 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageDownloadDataOperation.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageDownloadDataOperation.swift @@ -22,7 +22,7 @@ class AWSS3StorageDownloadDataOperation: AmplifyInProcessReportingOperation< >, StorageDownloadDataOperation { let storageConfiguration: AWSS3StoragePluginConfiguration - let storageService: AWSS3StorageServiceBehavior + let storageServiceProvider: AWSS3StorageServiceProvider let authService: AWSAuthServiceBehavior var storageTaskReference: StorageTaskReference? @@ -30,15 +30,21 @@ class AWSS3StorageDownloadDataOperation: AmplifyInProcessReportingOperation< // Serial queue for synchronizing access to `storageTaskReference`. private let storageTaskActionQueue = DispatchQueue(label: "com.amazonaws.amplify.StorageTaskActionQueue") + private var storageService: AWSS3StorageServiceBehavior { + get throws { + return try storageServiceProvider() + } + } + init(_ request: StorageDownloadDataRequest, storageConfiguration: AWSS3StoragePluginConfiguration, - storageService: AWSS3StorageServiceBehavior, + storageServiceProvider: @escaping AWSS3StorageServiceProvider, authService: AWSAuthServiceBehavior, progressListener: InProcessListener? = nil, resultListener: ResultListener? = nil) { self.storageConfiguration = storageConfiguration - self.storageService = storageService + self.storageServiceProvider = storageServiceProvider self.authService = authService super.init(categoryType: .storage, eventName: HubPayload.EventName.Storage.downloadData, @@ -97,7 +103,7 @@ class AWSS3StorageDownloadDataOperation: AmplifyInProcessReportingOperation< } let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions) - storageService.download(serviceKey: serviceKey, fileURL: nil, accelerate: accelerate) { [weak self] event in + try storageService.download(serviceKey: serviceKey, fileURL: nil, accelerate: accelerate) { [weak self] event in self?.onServiceEvent(event: event) } } catch { diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageDownloadFileOperation.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageDownloadFileOperation.swift index 86d75956b6..4605459215 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageDownloadFileOperation.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageDownloadFileOperation.swift @@ -25,23 +25,29 @@ class AWSS3StorageDownloadFileOperation: AmplifyInProcessReportingOperation< >, StorageDownloadFileOperation { let storageConfiguration: AWSS3StoragePluginConfiguration - let storageService: AWSS3StorageServiceBehavior + let storageServiceProvider: AWSS3StorageServiceProvider let authService: AWSAuthServiceBehavior var storageTaskReference: StorageTaskReference? // Serial queue for synchronizing access to `storageTaskReference`. private let storageTaskActionQueue = DispatchQueue(label: "com.amazonaws.amplify.StorageTaskActionQueue") + private var storageService: AWSS3StorageServiceBehavior { + get throws { + return try storageServiceProvider() + } + } + init(_ request: StorageDownloadFileRequest, storageConfiguration: AWSS3StoragePluginConfiguration, - storageService: AWSS3StorageServiceBehavior, + storageServiceProvider: @escaping AWSS3StorageServiceProvider, authService: AWSAuthServiceBehavior, progressListener: InProcessListener? = nil, resultListener: ResultListener? = nil ) { self.storageConfiguration = storageConfiguration - self.storageService = storageService + self.storageServiceProvider = storageServiceProvider self.authService = authService super.init(categoryType: .storage, eventName: HubPayload.EventName.Storage.downloadFile, @@ -99,7 +105,7 @@ class AWSS3StorageDownloadFileOperation: AmplifyInProcessReportingOperation< serviceKey = prefix + request.key } let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions) - storageService.download( + try storageService.download( serviceKey: serviceKey, fileURL: self.request.local, accelerate: accelerate diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadDataOperation.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadDataOperation.swift index 7dd70d4f28..a2751a4a1b 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadDataOperation.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadDataOperation.swift @@ -22,7 +22,7 @@ class AWSS3StorageUploadDataOperation: AmplifyInProcessReportingOperation< >, StorageUploadDataOperation { let storageConfiguration: AWSS3StoragePluginConfiguration - let storageService: AWSS3StorageServiceBehavior + let storageServiceProvider: AWSS3StorageServiceProvider let authService: AWSAuthServiceBehavior var storageTaskReference: StorageTaskReference? @@ -30,15 +30,21 @@ class AWSS3StorageUploadDataOperation: AmplifyInProcessReportingOperation< /// Serial queue for synchronizing access to `storageTaskReference`. private let storageTaskActionQueue = DispatchQueue(label: "com.amazonaws.amplify.StorageTaskActionQueue") + private var storageService: AWSS3StorageServiceBehavior { + get throws { + return try storageServiceProvider() + } + } + init(_ request: StorageUploadDataRequest, storageConfiguration: AWSS3StoragePluginConfiguration, - storageService: AWSS3StorageServiceBehavior, + storageServiceProvider: @escaping AWSS3StorageServiceProvider, authService: AWSAuthServiceBehavior, progressListener: InProcessListener? = nil, resultListener: ResultListener? = nil) { self.storageConfiguration = storageConfiguration - self.storageService = storageService + self.storageServiceProvider = storageServiceProvider self.authService = authService super.init(categoryType: .storage, eventName: HubPayload.EventName.Storage.uploadData, @@ -102,7 +108,7 @@ class AWSS3StorageUploadDataOperation: AmplifyInProcessReportingOperation< let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions) if request.data.count > StorageUploadDataRequest.Options.multiPartUploadSizeThreshold { - storageService.multiPartUpload( + try storageService.multiPartUpload( serviceKey: serviceKey, uploadSource: .data(request.data), contentType: request.options.contentType, @@ -112,7 +118,7 @@ class AWSS3StorageUploadDataOperation: AmplifyInProcessReportingOperation< self?.onServiceEvent(event: event) } } else { - storageService.upload( + try storageService.upload( serviceKey: serviceKey, uploadSource: .data(request.data), contentType: request.options.contentType, diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadFileOperation.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadFileOperation.swift index db8ced505c..10d3ceb685 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadFileOperation.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadFileOperation.swift @@ -22,7 +22,7 @@ class AWSS3StorageUploadFileOperation: AmplifyInProcessReportingOperation< >, StorageUploadFileOperation { let storageConfiguration: AWSS3StoragePluginConfiguration - let storageService: AWSS3StorageServiceBehavior + let storageServiceProvider: AWSS3StorageServiceProvider let authService: AWSAuthServiceBehavior var storageTaskReference: StorageTaskReference? @@ -30,15 +30,21 @@ class AWSS3StorageUploadFileOperation: AmplifyInProcessReportingOperation< /// Serial queue for synchronizing access to `storageTaskReference`. private let storageTaskActionQueue = DispatchQueue(label: "com.amazonaws.amplify.StorageTaskActionQueue") + private var storageService: AWSS3StorageServiceBehavior { + get throws { + return try storageServiceProvider() + } + } + init(_ request: StorageUploadFileRequest, storageConfiguration: AWSS3StoragePluginConfiguration, - storageService: AWSS3StorageServiceBehavior, + storageServiceProvider: @escaping AWSS3StorageServiceProvider, authService: AWSAuthServiceBehavior, progressListener: InProcessListener? = nil, resultListener: ResultListener? = nil) { self.storageConfiguration = storageConfiguration - self.storageService = storageService + self.storageServiceProvider = storageServiceProvider self.authService = authService super.init(categoryType: .storage, eventName: HubPayload.EventName.Storage.uploadFile, @@ -124,7 +130,7 @@ class AWSS3StorageUploadFileOperation: AmplifyInProcessReportingOperation< let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions) if uploadSize > StorageUploadFileRequest.Options.multiPartUploadSizeThreshold { - storageService.multiPartUpload( + try storageService.multiPartUpload( serviceKey: serviceKey, uploadSource: .local(request.local), contentType: request.options.contentType, @@ -134,7 +140,7 @@ class AWSS3StorageUploadFileOperation: AmplifyInProcessReportingOperation< self?.onServiceEvent(event: event) } } else { - storageService.upload( + try storageService.upload( serviceKey: serviceKey, uploadSource: .local(request.local), contentType: request.options.contentType, diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService.swift index c311eaaeb8..a07858a643 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService.swift @@ -57,13 +57,14 @@ class AWSS3StorageService: AWSS3StorageServiceBehavior, StorageServiceProxy { region: String, bucket: String, httpClientEngineProxy: HttpClientEngineProxy? = nil, - storageConfiguration: StorageConfiguration = .default, + storageConfiguration: StorageConfiguration? = nil, storageTransferDatabase: StorageTransferDatabase = .default, fileSystem: FileSystem = .default, sessionConfiguration: URLSessionConfiguration? = nil, delegateQueue: OperationQueue? = nil, logger: Logger = storageLogger) throws { let credentialsProvider = authService.getCredentialsProvider() + let storageConfiguration = storageConfiguration ?? .init(forBucket: bucket) let clientConfig = try S3Client.S3ClientConfiguration( region: region, credentialsProvider: credentialsProvider, @@ -113,7 +114,7 @@ class AWSS3StorageService: AWSS3StorageServiceBehavior, StorageServiceProxy { } init(authService: AWSAuthCredentialsProviderBehavior, - storageConfiguration: StorageConfiguration = .default, + storageConfiguration: StorageConfiguration? = nil, storageTransferDatabase: StorageTransferDatabase = .default, fileSystem: FileSystem = .default, sessionConfiguration: URLSessionConfiguration, @@ -123,6 +124,7 @@ class AWSS3StorageService: AWSS3StorageServiceBehavior, StorageServiceProxy { preSignedURLBuilder: AWSS3PreSignedURLBuilderBehavior, awsS3: AWSS3Behavior, bucket: String) { + let storageConfiguration = storageConfiguration ?? .init(forBucket: bucket) self.storageConfiguration = storageConfiguration self.storageTransferDatabase = storageTransferDatabase self.fileSystem = fileSystem diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageServiceBehavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageServiceBehavior.swift index 6491546d8d..9fd748454a 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageServiceBehavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageServiceBehavior.swift @@ -9,6 +9,7 @@ import Foundation import Amplify import AWSS3 +typealias AWSS3StorageServiceProvider = () throws -> AWSS3StorageServiceBehavior protocol AWSS3StorageServiceBehavior { typealias StorageServiceDownloadEventHandler = (StorageServiceDownloadEvent) -> Void typealias StorageServiceDownloadEvent = diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageConfiguration.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageConfiguration.swift index 71e9bcfd06..b1b722f065 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageConfiguration.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageConfiguration.swift @@ -20,10 +20,6 @@ struct StorageConfiguration { let allowsCellularAccess: Bool let timeoutIntervalForResource: TimeInterval - static let `default`: StorageConfiguration = { - StorageConfiguration() - }() - init(sessionIdentifier: String = Defaults.sessionIdentifier, sharedContainerIdentifier: String = Defaults.sharedContainerIdentifier, allowsCellularAccess: Bool = Defaults.allowsCellularAccess, @@ -33,4 +29,10 @@ struct StorageConfiguration { self.allowsCellularAccess = allowsCellularAccess self.timeoutIntervalForResource = timeoutIntervalForResource } + + init(forBucket bucket: String) { + self.init( + sessionIdentifier: "\(Defaults.sessionIdentifier).\(bucket)" + ) + } } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Tasks/AWSS3StorageListObjectsTask.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Tasks/AWSS3StorageListObjectsTask.swift index ed01a7a1bb..8b860798d0 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Tasks/AWSS3StorageListObjectsTask.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Tasks/AWSS3StorageListObjectsTask.swift @@ -56,6 +56,7 @@ class AWSS3StorageListObjectsTask: StorageListObjectsTask, DefaultLogger { } return StorageListResult.Item( path: path, + size: s3Object.size, eTag: s3Object.eTag, lastModified: s3Object.lastModified ) diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginResetTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginResetTests.swift index 7fa01ac1a4..3983a0cc7d 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginResetTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginResetTests.swift @@ -16,7 +16,7 @@ class AWSS3StoragePluginResetTests: AWSS3StoragePluginTests { await resettable.reset() XCTAssertNil(storagePlugin.authService) - XCTAssertNil(storagePlugin.storageService) + XCTAssertNil(storagePlugin.defaultStorageService) XCTAssertNil(storagePlugin.queue) } } diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginStorageBucketTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginStorageBucketTests.swift new file mode 100644 index 0000000000..d5b9b2072c --- /dev/null +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginStorageBucketTests.swift @@ -0,0 +1,174 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +@_spi(InternalAmplifyConfiguration) @testable import Amplify +@testable import AWSS3StoragePlugin +@testable import AWSPluginsTestCommon + +class AWSS3StoragePluginStorageBucketTests: XCTestCase { + private var storagePlugin: AWSS3StoragePlugin! + private var defaultService: MockAWSS3StorageService! + private var authService: MockAWSAuthService! + private var queue: OperationQueue! + private let defaultBucketInfo = BucketInfo( + bucketName: "bucketName", + region: "us-east-1" + ) + private let additionalBucketInfo = BucketInfo( + bucketName: "anotherBucketName", + region: "us-east-2" + ) + + private var additionalS3Bucket: AmplifyOutputsData.Storage.Bucket { + return .init( + name: "anotherBucket", + bucketName: additionalBucketInfo.bucketName, + awsRegion: additionalBucketInfo.region + ) + } + + override func setUp() { + storagePlugin = AWSS3StoragePlugin() + defaultService = MockAWSS3StorageService() + authService = MockAWSAuthService() + queue = OperationQueue() + storagePlugin.configure( + defaultBucket: .fromBucketInfo(defaultBucketInfo), + storageService: defaultService, + authService: authService, + defaultAccessLevel: .guest, + queue: queue + ) + } + + override func tearDown() async throws { + try await Task.sleep(seconds: 0.1) // This is unfortunate but necessary to give the DB time to recover the URLSession tasks + await storagePlugin.reset() + queue.cancelAllOperations() + storagePlugin = nil + defaultService = nil + authService = nil + queue = nil + } + + /// Given: A configured AWSS3StoragePlugin + /// When: storageService(for:) is invoked with nil + /// Then: The default storage service should be returned + func testStorageService_withNil_shouldReturnDefaultService() throws { + let storageService = try storagePlugin.storageService(for: nil) + guard let mockService = storageService as? MockAWSS3StorageService else { + XCTFail("Expected a MockAWSS3StorageService, got \(type(of: storageService))") + return + } + XCTAssertTrue(mockService === defaultService) + } + + /// Given: A AWSS3StoragePlugin configured with additional bucket names + /// When: storageService(for:) is invoked with .fromOutputs with an existing value + /// Then: A valid AWSS3StorageService should be returned pointing to that bucket + func testStorageService_withBucketFromOutputs_shouldReturnStorageService() throws { + storagePlugin.additionalBucketsByName = [ + additionalS3Bucket.name: additionalS3Bucket + ] + let storageService = try storagePlugin.storageService(for: .fromOutputs(name: additionalS3Bucket.name)) + guard let newService = storageService as? AWSS3StorageService else { + XCTFail("Expected a AWSS3StorageService, got \(type(of: storageService))") + return + } + XCTAssertFalse(newService === defaultService) + XCTAssertEqual(newService.bucket, additionalS3Bucket.bucketName) + } + + /// Given: A AWSS3StoragePlugin configured without additional buckets (i.e. no AmplifyOutputs) + /// When: storageService(for:) is invoked with .fromOutputs + /// Then: A StorageError.validation error is thrown + func testStorageService_withBucketFromOutputs_withoutConfiguringOutputs_shouldThrowValidationException() { + storagePlugin.additionalBucketsByName = nil + do { + _ = try storagePlugin.storageService(for: .fromOutputs(name: "anotherBucket")) + XCTFail("Expected StorageError.validation to be thrown") + } catch { + guard let storageError = error as? StorageError, + case .validation(let field, _, _, _) = storageError else { + XCTFail("Expected StorageError.validation, got \(error)") + return + } + XCTAssertEqual(field, "bucket") + } + } + + /// Given: A AWSS3StoragePlugin configured with additional bucket names + /// When: storageService(for:) is invoked with .fromOutputs with a non-existing value + /// Then: A StorageError.validation error is thrown + func testStorageService_withInvalidBucketFromOutputs_shouldThrowValidationException() { + storagePlugin.additionalBucketsByName = [ + additionalS3Bucket.name: additionalS3Bucket + ] + do { + _ = try storagePlugin.storageService(for: .fromOutputs(name: "invalidBucket")) + XCTFail("Expected StorageError.validation to be thrown") + } catch { + guard let storageError = error as? StorageError, + case .validation(let field, _, _, _) = storageError else { + XCTFail("Expected StorageError.validation, got \(error)") + return + } + XCTAssertEqual(field, "bucket") + } + } + + /// Given: A configured AWSS3StoragePlugin + /// When: storageService(for:) is invoked with .fromBucketInfo + /// Then: A valid AWSS3StorageService should be returned pointing to that bucket + func testStorageService_withBucketFromBucketInfo_shouldReturnStorageService() throws { + let storageService = try storagePlugin.storageService(for: .fromBucketInfo(additionalBucketInfo)) + guard let newService = storageService as? AWSS3StorageService else { + XCTFail("Expected a AWSS3StorageService, got \(type(of: storageService))") + return + } + XCTAssertFalse(newService === defaultService) + XCTAssertEqual(newService.bucket, additionalBucketInfo.bucketName) + } + + /// Given: A configured AWSS3StoragePlugin + /// When: storageService(for:) is invoked with an invalid instance that conforms to StorageBucket + /// Then: A StorageError.validation error is thrown + func testStorageService_withInvalidStorageBucket_shouldThrowValidationException() { + do { + _ = try storagePlugin.storageService(for: InvalidBucket()) + XCTFail("Expected StorageError.validation to be thrown") + } catch { + guard let storageError = error as? StorageError, + case .validation(let field, _, _, _) = storageError else { + XCTFail("Expected StorageError.validation, got \(error)") + return + } + XCTAssertEqual(field, "bucket") + } + } + + /// Given: A configured AWSS3StoragePlugin + /// When: storageService(for:) is invoked for a bucket that was not accessed before (i.e. a new one) + /// Then: A new Storage Service should be created + func testStorageService_withNewBucket_shouldReturnNewService() throws { + XCTAssertEqual(storagePlugin.storageServicesByBucket.count, 1) + _ = try storagePlugin.storageService(for: .fromBucketInfo(additionalBucketInfo)) + XCTAssertEqual(storagePlugin.storageServicesByBucket.count, 2) + } + + /// Given: A configured AWSS3StoragePlugin + /// When: storageService(for:) is invoked for a bucket that was accessed before (e.g. the default one) + /// Then: A new Storage Service should not be created + func testStorageService_withPreviouslyAccessedBucket_shouldReturnExistingService() throws { + XCTAssertEqual(storagePlugin.storageServicesByBucket.count, 1) + _ = try storagePlugin.storageService(for: .fromBucketInfo(defaultBucketInfo)) + XCTAssertEqual(storagePlugin.storageServicesByBucket.count, 1) + } + + private struct InvalidBucket: StorageBucket {} +} diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginTestBase.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginTestBase.swift index 5ed47beb9e..423f99399e 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginTestBase.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginTestBase.swift @@ -10,6 +10,7 @@ import Amplify @testable import AWSS3StoragePlugin @testable import AmplifyTestCommon @testable import AWSPluginsTestCommon +import InternalAmplifyCredentials class AWSS3StoragePluginTests: XCTestCase { var storagePlugin: AWSS3StoragePlugin! @@ -33,9 +34,29 @@ class AWSS3StoragePluginTests: XCTestCase { authService = MockAWSAuthService() queue = MockOperationQueue() - storagePlugin.configure(storageService: storageService, + storagePlugin.configure(defaultBucket: .fromBucketInfo(.init(bucketName: testBucket, region: testRegion)), + storageService: storageService, authService: authService, defaultAccessLevel: defaultAccessLevel, queue: queue) } } + + +extension AWSS3StoragePlugin { + /// Convenience configuration method for testing purposes, with a default bucket with name "test-bucket" and region "us-east-1" + func configure( + storageService: AWSS3StorageServiceBehavior, + authService: AWSAuthCredentialsProviderBehavior, + defaultAccessLevel: StorageAccessLevel, + queue: OperationQueue = OperationQueue() + ) { + configure( + defaultBucket: .fromBucketInfo(.init(bucketName: "test-bucket", region: "us-east-1")), + storageService: storageService, + authService: authService, + defaultAccessLevel: defaultAccessLevel, + queue: queue + ) + } +} diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/AWSS3StorageOperations+StorageServiceProvider.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/AWSS3StorageOperations+StorageServiceProvider.swift new file mode 100644 index 0000000000..71931f237a --- /dev/null +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/AWSS3StorageOperations+StorageServiceProvider.swift @@ -0,0 +1,99 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +@testable import Amplify +@testable import AWSPluginsCore +@testable import AWSS3StoragePlugin +import Foundation + +extension AWSS3StorageDownloadFileOperation { + convenience init( + _ request: StorageDownloadFileRequest, + storageConfiguration: AWSS3StoragePluginConfiguration, + storageService: AWSS3StorageServiceBehavior, + authService: AWSAuthServiceBehavior, + progressListener: InProcessListener? = nil, + resultListener: ResultListener? = nil + ) { + self.init( + request, + storageConfiguration: storageConfiguration, + storageServiceProvider: { + return storageService + }, + authService: authService, + progressListener: progressListener, + resultListener: resultListener + ) + } +} + +extension AWSS3StorageDownloadDataOperation { + convenience init( + _ request: StorageDownloadDataRequest, + storageConfiguration: AWSS3StoragePluginConfiguration, + storageService: AWSS3StorageServiceBehavior, + authService: AWSAuthServiceBehavior, + progressListener: InProcessListener? = nil, + resultListener: ResultListener? = nil + ) { + self.init( + request, + storageConfiguration: storageConfiguration, + storageServiceProvider: { + return storageService + }, + authService: authService, + progressListener: progressListener, + resultListener: resultListener + ) + } +} + +extension AWSS3StorageUploadDataOperation { + convenience init( + _ request: StorageUploadDataRequest, + storageConfiguration: AWSS3StoragePluginConfiguration, + storageService: AWSS3StorageServiceBehavior, + authService: AWSAuthServiceBehavior, + progressListener: InProcessListener? = nil, + resultListener: ResultListener? = nil + ) { + self.init( + request, + storageConfiguration: storageConfiguration, + storageServiceProvider: { + return storageService + }, + authService: authService, + progressListener: progressListener, + resultListener: resultListener + ) + } +} + +extension AWSS3StorageUploadFileOperation { + convenience init( + _ request: StorageUploadFileRequest, + storageConfiguration: AWSS3StoragePluginConfiguration, + storageService: AWSS3StorageServiceBehavior, + authService: AWSAuthServiceBehavior, + progressListener: InProcessListener? = nil, + resultListener: ResultListener? = nil + ) { + self.init( + request, + storageConfiguration: storageConfiguration, + storageServiceProvider: { + return storageService + }, + authService: authService, + progressListener: progressListener, + resultListener: resultListener + ) + } +} diff --git a/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginMultipleBucketTests.swift b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginMultipleBucketTests.swift new file mode 100644 index 0000000000..e1e52e3789 --- /dev/null +++ b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginMultipleBucketTests.swift @@ -0,0 +1,498 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +@_spi(InternalAmplifyConfiguration) @testable import Amplify +@testable import AWSS3StoragePlugin +import XCTest + +class AWSS3StoragePluginMultipleBucketTests: AWSS3StoragePluginTestBase { + private var customBucket: ResolvedStorageBucket! + + override func setUp() async throws { + guard let outputs = try? AmplifyOutputs.amplifyOutputs.resolveConfiguration(), + let additionalBucket = outputs.storage?.buckets?.first else { + throw XCTSkip("Multibucket has not been configured. Skipping test") + } + customBucket = .fromBucketInfo(.init( + bucketName: additionalBucket.bucketName, + region: additionalBucket.awsRegion + )) + try await super.setUp() + } + + override func tearDown() async throws { + try await Task.sleep(seconds: 0.1) + try await super.tearDown() + } + + /// Given: An data object + /// When: Upload the data to a custom buckets using keys + /// Then: The operation completes successfully + func testUploadData_toCustomBucket_usingKey_shouldSucceed() async throws { + let key = UUID().uuidString + let data = Data(key.utf8) + + let uploaded = try await Amplify.Storage.uploadData( + key: key, + data: data, + options: .init(bucket: customBucket) + ).value + XCTAssertEqual(uploaded, key) + + let deleted = try await Amplify.Storage.remove( + key: key, + options: .init(bucket: customBucket) + ) + XCTAssertEqual(deleted, key) + } + + /// Given: An data object + /// When: Upload the data to a custom bucket using StoragePath + /// Then: The operation completes successfully + func testUploadData_toCustomBucket_usingStoragePath_shouldSucceed() async throws { + let id = UUID().uuidString + let data = Data(id.utf8) + let path: StringStoragePath = .fromString("public/\(id)") + + let uploaded = try await Amplify.Storage.uploadData( + path: path, + data: data, + options: .init(bucket: customBucket) + ).value + XCTAssertEqual(uploaded, path.string) + + let deleted = try await Amplify.Storage.remove( + path: path, + options: .init(bucket: customBucket) + ) + XCTAssertEqual(deleted, path.string) + } + + /// Given: A file with contents + /// When: Upload the file to a custom bucket using key + /// Then: The operation completes successfully and all URLSession and SDK requests include a user agent + func testUploadFile_toCustomBucket_usingKey_shouldSucceed() async throws { + let key = UUID().uuidString + let filePath = NSTemporaryDirectory() + key + ".tmp" + + let fileURL = URL(fileURLWithPath: filePath) + FileManager.default.createFile(atPath: filePath, contents: Data(key.utf8), attributes: nil) + + let uploaded = try await Amplify.Storage.uploadFile( + key: key, + local: fileURL, + options: .init(bucket: customBucket) + ).value + XCTAssertEqual(uploaded, key) + + let deleted = try await Amplify.Storage.remove( + key: key, + options: .init(bucket: customBucket) + ) + XCTAssertEqual(deleted, key) + } + + /// Given: A file with contents + /// When: Upload the file to a custom bucket using StoragePath + /// Then: The operation completes successfully and all URLSession and SDK requests include a user agent + func testUploadFile_toCustomBucket_usingStoragePath_shouldSucceed() async throws { + let id = UUID().uuidString + let filePath = NSTemporaryDirectory() + id + ".tmp" + let path: StringStoragePath = .fromString("public/\(id)") + + let fileURL = URL(fileURLWithPath: filePath) + FileManager.default.createFile(atPath: filePath, contents: Data(id.utf8), attributes: nil) + + let uploaded = try await Amplify.Storage.uploadFile( + path: path, + local: fileURL, + options: .init(bucket: customBucket) + ).value + XCTAssertEqual(uploaded, path.string) + + let deleted = try await Amplify.Storage.remove( + path: path, + options: .init(bucket: customBucket) + ) + XCTAssertEqual(deleted, path.string) + } + + /// Given: A large data object + /// When: Upload the data to a custom bucket using key + /// Then: The operation completes successfully + func testUploadLargeData_toCustomBucket_usingKey_shouldSucceed() async throws { + let key = UUID().uuidString + + let uploaded = try await Amplify.Storage.uploadData( + key: key, + data: AWSS3StoragePluginTestBase.largeDataObject, + options: .init(bucket: customBucket) + ).value + XCTAssertEqual(uploaded, key) + + let deleted = try await Amplify.Storage.remove( + key: key, + options: .init(bucket: customBucket) + ) + XCTAssertEqual(deleted, key) + } + + /// Given: A large data object + /// When: Upload the data to a custom bucket using StoragePath + /// Then: The operation completes successfully + func testUploadLargeData_toCustomBucket_usingStoragePath_shouldSucceed() async throws { + let id = UUID().uuidString + let path: StringStoragePath = .fromString("public/\(id)") + + let uploaded = try await Amplify.Storage.uploadData( + path: path, + data: AWSS3StoragePluginTestBase.largeDataObject, + options: .init(bucket: customBucket) + ).value + XCTAssertEqual(uploaded, path.string) + + let deleted = try await Amplify.Storage.remove( + path: path, + options: .init(bucket: customBucket) + ) + XCTAssertEqual(deleted, path.string) + } + + /// Given: A large file + /// When: Upload the file to a custom bucket using key + /// Then: The operation completes successfully + func testUploadLargeFile_toCustomBucket_usingKey_shouldSucceed() async throws { + let key = UUID().uuidString + let filePath = NSTemporaryDirectory() + key + ".tmp" + let fileURL = URL(fileURLWithPath: filePath) + + FileManager.default.createFile( + atPath: filePath, + contents: AWSS3StoragePluginTestBase.largeDataObject, + attributes: nil + ) + + let uploaded = try await Amplify.Storage.uploadFile( + key: key, + local: fileURL, + options: .init(bucket: customBucket) + ).value + XCTAssertEqual(uploaded, key) + + + let deleted = try await Amplify.Storage.remove( + key: key, + options: .init(bucket: customBucket) + ) + XCTAssertEqual(deleted, key) + } + + /// Given: A large file + /// When: Upload the file to a custom bucket using key + /// Then: The operation completes successfully + func testUploadLargeFile_toCustomBucket_usingStoragePath_shouldSucceed() async throws { + let id = UUID().uuidString + let filePath = NSTemporaryDirectory() + id + ".tmp" + let fileURL = URL(fileURLWithPath: filePath) + let path: StringStoragePath = .fromString("public/\(id)") + + FileManager.default.createFile( + atPath: filePath, + contents: AWSS3StoragePluginTestBase.largeDataObject, + attributes: nil + ) + + let uploaded = try await Amplify.Storage.uploadFile( + path: path, + local: fileURL, + options: .init(bucket: customBucket) + ).value + XCTAssertEqual(uploaded, path.string) + + + let deleted = try await Amplify.Storage.remove( + path: path, + options: .init(bucket: customBucket) + ) + XCTAssertEqual(deleted, path.string) + } + + /// Given: An object in storage in a custom bucket + /// When: Call the downloadData API using key + /// Then: The operation completes successfully with the data retrieved + func testDownloadData_fromCustomBucket_usingKey_shouldSucceed() async throws { + let key = UUID().uuidString + let data = Data(key.utf8) + try await uploadData( + key: key, + data: data, + options: .init(bucket: customBucket) + ) + + let downloaded = try await Amplify.Storage.downloadData( + key: key, + options: .init(bucket: customBucket) + ).value + XCTAssertEqual(data.count, downloaded.count) + + let deleted = try await Amplify.Storage.remove( + key: key, + options: .init(bucket: customBucket) + ) + XCTAssertEqual(deleted, key) + } + + /// Given: An object in storage in a custom bucket + /// When: Call the downloadData API using StoragePath + /// Then: The operation completes successfully with the data retrieved + func testDownloadData_fromCustomBucket_usingStoragePath_shouldSucceed() async throws { + let id = UUID().uuidString + let data = Data(id.utf8) + let path: StringStoragePath = .fromString("public/\(id)") + try await uploadData( + path: path, + data: data, + options: .init(bucket: customBucket) + ) + + let downloaded = try await Amplify.Storage.downloadData( + path: path, + options: .init(bucket: customBucket) + ).value + XCTAssertEqual(data.count, downloaded.count) + + let deleted = try await Amplify.Storage.remove( + path: path, + options: .init(bucket: customBucket) + ) + XCTAssertEqual(deleted, path.string) + } + + /// Given: An object in storage in a custom bucket + /// When: Call the downloadFile API using key + /// Then: The operation completes successfully the local file containing the data from the object + func testDownloadFile_fromCustomBucket_usingKey_shouldSucceed() async throws { + let key = UUID().uuidString + let timestamp = String(Date().timeIntervalSince1970) + let timestampData = Data(timestamp.utf8) + try await uploadData( + key: key, + data: timestampData, + options: .init(bucket: customBucket) + ) + let filePath = NSTemporaryDirectory() + key + ".tmp" + let fileURL = URL(fileURLWithPath: filePath) + removeFileIfExisting(fileURL) + + try await Amplify.Storage.downloadFile( + key: key, + local: fileURL, + options: .init(bucket: customBucket) + ).value + + XCTAssertTrue(FileManager.default.fileExists(atPath: fileURL.path)) + do { + let result = try String(contentsOf: fileURL, encoding: .utf8) + XCTAssertEqual(result, timestamp) + } catch { + XCTFail("Failed to read downloaded file") + } + + removeFileIfExisting(fileURL) + let deleted = try await Amplify.Storage.remove( + key: key, + options: .init(bucket: customBucket) + ) + XCTAssertEqual(deleted, key) + } + + /// Given: An object in storage in a custom bucket + /// When: Call the downloadFile API using StoragePath + /// Then: The operation completes successfully the local file containing the data from the object + func testDownloadFile_fromCustomBucket_usingStoragePath_shouldSucceed() async throws { + let id = UUID().uuidString + let timestamp = String(Date().timeIntervalSince1970) + let timestampData = Data(timestamp.utf8) + let path: StringStoragePath = .fromString("public/\(id)") + try await uploadData( + path: path, + data: timestampData, + options: .init(bucket: customBucket) + ) + let filePath = NSTemporaryDirectory() + id + ".tmp" + let fileURL = URL(fileURLWithPath: filePath) + removeFileIfExisting(fileURL) + + try await Amplify.Storage.downloadFile( + path: path, + local: fileURL, + options: .init(bucket: customBucket) + ).value + + XCTAssertTrue(FileManager.default.fileExists(atPath: fileURL.path)) + do { + let result = try String(contentsOf: fileURL, encoding: .utf8) + XCTAssertEqual(result, timestamp) + } catch { + XCTFail("Failed to read downloaded file") + } + + removeFileIfExisting(fileURL) + let deleted = try await Amplify.Storage.remove( + path: path, + options: .init(bucket: customBucket) + ) + XCTAssertEqual(deleted, path.string) + } + + /// Given: An object in storage in a custom bucket + /// When: Call the getURL API using key + /// Then: The operation completes successfully with the URL retrieved + func testGetRemoteURL_fromCustomBucket_usingKey_shouldSucceed() async throws { + let key = UUID().uuidString + try await uploadData( + key: key, + data: Data(key.utf8), + options: .init(bucket: customBucket) + ) + + let remoteURL = try await Amplify.Storage.getURL( + key: key, + options: .init(bucket: customBucket) + ) + + let (data, response) = try await URLSession.shared.data(from: remoteURL) + let httpResponse = try XCTUnwrap(response as? HTTPURLResponse) + XCTAssertEqual(httpResponse.statusCode, 200) + + let dataString = try XCTUnwrap(String(data: data, encoding: .utf8)) + XCTAssertEqual(dataString, key) + + let deleted = try await Amplify.Storage.remove( + key: key, + options: .init(bucket: customBucket) + ) + XCTAssertEqual(deleted, key) + } + + /// Given: An object in storage in a custom bucket + /// When: Call the getURL API using StoragePath + /// Then: The operation completes successfully with the URL retrieved + func testGetRemoteURL_fromCustomBucket_usingStoragePath_shouldSucceed() async throws { + let id = UUID().uuidString + let path: StringStoragePath = .fromString("public/\(id)") + try await uploadData( + path: path, + data: Data(id.utf8), + options: .init(bucket: customBucket) + ) + + let remoteURL = try await Amplify.Storage.getURL( + path: path, + options: .init(bucket: customBucket) + ) + + let (data, response) = try await URLSession.shared.data(from: remoteURL) + let httpResponse = try XCTUnwrap(response as? HTTPURLResponse) + XCTAssertEqual(httpResponse.statusCode, 200) + + let dataString = try XCTUnwrap(String(data: data, encoding: .utf8)) + XCTAssertEqual(dataString, id) + + let deleted = try await Amplify.Storage.remove( + path: path, + options: .init(bucket: customBucket) + ) + XCTAssertEqual(deleted, path.string) + } + + /// Given: An object in storage in a custom bucket + /// When: Call the list API using StoragePath + /// Then: The operation completes successfully with the key retrieved + func testList_fromOtherBucket_usingStoragePath_shouldSucceed() async throws { + let id = UUID().uuidString + let path: StringStoragePath = .fromString("public/\(id)") + try await uploadData( + path: path, + data: Data(id.utf8), + options: .init(bucket: customBucket) + ) + + let result = try await Amplify.Storage.list( + path: path, + options: .init(bucket: customBucket) + ) + let items = try XCTUnwrap(result.items) + + XCTAssertEqual(items.count, 1) + let item = try XCTUnwrap(items.first) + XCTAssertEqual(item.path, path.string) + XCTAssertNotNil(item.eTag) + XCTAssertNotNil(item.lastModified) + XCTAssertNotNil(item.size) + + let deleted = try await Amplify.Storage.remove( + path: path, + options: .init(bucket: customBucket) + ) + XCTAssertEqual(deleted, path.string) + } + + /// Given: An object in storage in a custom bucket + /// When: Call the remove API using key + /// Then: The operation completes successfully with the key removed from storage + func testRemoveKey_fromCustomBucket_usingKey_shouldSucceed() async throws { + let key = UUID().uuidString + try await uploadData( + key: key, + data: Data(key.utf8), + options: .init(bucket: customBucket) + ) + + let deleted = try await Amplify.Storage.remove( + key: key, + options: .init(bucket: customBucket) + ) + XCTAssertEqual(deleted, key) + } + + /// Given: An object in storage in a custom bucket + /// When: Call the remove API using StoragePath + /// Then: The operation completes successfully with the key removed from storage + func testRemoveKey_fromCustomBucket_usingStoragePath_shouldSucceed() async throws { + let id = UUID().uuidString + let path: StringStoragePath = .fromString("public/\(id)") + try await uploadData( + path: path, + data: Data(id.utf8), + options: .init(bucket: customBucket) + ) + + let deleted = try await Amplify.Storage.remove( + path: path, + options: .init(bucket: customBucket) + ) + XCTAssertEqual(deleted, path.string) + } + + private func removeFileIfExisting(_ fileURL: URL) { + guard FileManager.default.fileExists(atPath: fileURL.path) else { + return + } + do { + try FileManager.default.removeItem(at: fileURL) + } catch { + XCTFail("Failed to remove file at \(fileURL)") + } + } +} + +private extension StringStoragePath { + var string: String { + return resolve("") + } +} diff --git a/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginTestBase.swift b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginTestBase.swift index 35e4721be2..fde9054957 100644 --- a/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginTestBase.swift +++ b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginTestBase.swift @@ -87,13 +87,17 @@ class AWSS3StoragePluginTestBase: XCTestCase { Amplify.Storage.downloadData(key: key) } - func uploadData(key: String, data: Data) async throws { + func uploadData( + key: String, + data: Data, + options: StorageUploadDataRequest.Options? = nil + ) async throws { let completeInvoked = expectation(description: "Completed is invoked") Task { let result = try await Amplify.Storage.uploadData( key: key, data: data, - options: nil + options: options ).value XCTAssertNotNil(result) @@ -102,7 +106,27 @@ class AWSS3StoragePluginTestBase: XCTestCase { await fulfillment(of: [completeInvoked], timeout: 60) } - + + func uploadData( + path: any StoragePath, + data: Data, + options: StorageUploadDataRequest.Options? = nil + ) async throws { + let completeInvoked = expectation(description: "Completed is invoked") + Task { + let result = try await Amplify.Storage.uploadData( + path: path, + data: data, + options: options + ).value + + XCTAssertNotNil(result) + completeInvoked.fulfill() + } + + await fulfillment(of: [completeInvoked], timeout: 60) + } + func remove(key: String, accessLevel: StorageAccessLevel? = nil) async { var removeOptions: StorageRemoveRequest.Options? = nil if let accessLevel = accessLevel { @@ -196,15 +220,19 @@ class AWSS3StoragePluginTestBase: XCTestCase { private func invalidateCurrentSession() { Self.logger.debug("Invalidating URLSession") - guard let plugin = try? Amplify.Storage.getPlugin(for: "awsS3StoragePlugin") as? AWSS3StoragePlugin, - let service = plugin.storageService as? AWSS3StorageService else { - print("Unable to to cast to AWSS3StorageService") + guard let plugin = try? Amplify.Storage.getPlugin(for: "awsS3StoragePlugin") as? AWSS3StoragePlugin else { + print("Unable to to cast to AWSS3StoragePlugin") return } - if let delegate = service.urlSession.delegate as? StorageServiceSessionDelegate { - delegate.storageService = nil + for serviceBehaviour in plugin.storageServicesByBucket.values { + guard let service = serviceBehaviour as? AWSS3StorageService else { + continue + } + if let delegate = service.urlSession.delegate as? StorageServiceSessionDelegate { + delegate.storageService = nil + } + service.urlSession.invalidateAndCancel() } - service.urlSession.invalidateAndCancel() } } diff --git a/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.pbxproj b/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.pbxproj index c363202fbf..a15257b61c 100644 --- a/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.pbxproj +++ b/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.pbxproj @@ -81,6 +81,7 @@ 68828E4628C2736C006E7C0A /* AWSS3StoragePluginProgressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684FB08C28BEAF8E00C8A6EB /* AWSS3StoragePluginProgressTests.swift */; }; 68828E4728C27745006E7C0A /* AWSS3StoragePluginPutDataResumabilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684FB08828BEAF8E00C8A6EB /* AWSS3StoragePluginPutDataResumabilityTests.swift */; }; 68828E4828C2AAA6006E7C0A /* AWSS3StoragePluginGetDataResumabilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684FB08B28BEAF8E00C8A6EB /* AWSS3StoragePluginGetDataResumabilityTests.swift */; }; + 68DAFA7A2C7796CD00346A43 /* AWSS3StoragePluginMultipleBucketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68DAFA792C7796CD00346A43 /* AWSS3StoragePluginMultipleBucketTests.swift */; }; 734605222BACB5CC0039F0EB /* AWSS3StoragePluginUploadIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 734605212BACB5CC0039F0EB /* AWSS3StoragePluginUploadIntegrationTests.swift */; }; 734605242BACB60E0039F0EB /* AWSS3StoragePluginDownloadIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 734605232BACB60E0039F0EB /* AWSS3StoragePluginDownloadIntegrationTests.swift */; }; 901AB3E92AE2C2DC000F825B /* AWSS3StoragePluginUploadMetadataTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 901AB3E82AE2C2DC000F825B /* AWSS3StoragePluginUploadMetadataTestCase.swift */; }; @@ -166,6 +167,7 @@ 684FB0A928BEB07200C8A6EB /* AWSS3StoragePluginIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AWSS3StoragePluginIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 684FB0C228BEB45600C8A6EB /* AuthSignInHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthSignInHelper.swift; sourceTree = ""; }; 684FB0C528BEB84800C8A6EB /* StorageHostApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StorageHostApp.entitlements; sourceTree = ""; }; + 68DAFA792C7796CD00346A43 /* AWSS3StoragePluginMultipleBucketTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSS3StoragePluginMultipleBucketTests.swift; sourceTree = ""; }; 734605212BACB5CC0039F0EB /* AWSS3StoragePluginUploadIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSS3StoragePluginUploadIntegrationTests.swift; sourceTree = ""; }; 734605232BACB60E0039F0EB /* AWSS3StoragePluginDownloadIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSS3StoragePluginDownloadIntegrationTests.swift; sourceTree = ""; }; 901AB3E82AE2C2DC000F825B /* AWSS3StoragePluginUploadMetadataTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSS3StoragePluginUploadMetadataTestCase.swift; sourceTree = ""; }; @@ -321,6 +323,7 @@ 488C2A722BAE04DC009AD2BA /* AWSS3StoragePluginRemoveIntegrationTests.swift */, 488C2A742BAFCA7C009AD2BA /* AWSS3StoragePluginListObjectsIntegrationTests.swift */, 488C2A762BAFD4B3009AD2BA /* AWSS3StoragePluginGetURLIntegrationTests.swift */, + 68DAFA792C7796CD00346A43 /* AWSS3StoragePluginMultipleBucketTests.swift */, ); path = AWSS3StoragePluginIntegrationTests; sourceTree = ""; @@ -647,6 +650,7 @@ 21F7630F2BD6B8640048845A /* AsyncTesting.swift in Sources */, 21F763102BD6B8640048845A /* AWSS3StoragePluginGetDataResumabilityTests.swift in Sources */, 21F763112BD6B8640048845A /* AWSS3StoragePluginUploadMetadataTestCase.swift in Sources */, + 68DAFA7A2C7796CD00346A43 /* AWSS3StoragePluginMultipleBucketTests.swift in Sources */, 21F763122BD6B8640048845A /* AsyncExpectation.swift in Sources */, 21F763132BD6B8640048845A /* AWSS3StoragePluginProgressTests.swift in Sources */, 21F763142BD6B8640048845A /* AWSS3StoragePluginAccessLevelTests.swift in Sources */, diff --git a/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageStressTests/StorageStressTests.swift b/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageStressTests/StorageStressTests.swift index ffc25e3d2c..7e06693fbd 100644 --- a/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageStressTests/StorageStressTests.swift +++ b/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageStressTests/StorageStressTests.swift @@ -230,15 +230,19 @@ final class StorageStressTests: XCTestCase { private func invalidateCurrentSession() { Self.logger.debug("Invalidating URLSession") - guard let plugin = try? Amplify.Storage.getPlugin(for: "awsS3StoragePlugin") as? AWSS3StoragePlugin, - let service = plugin.storageService as? AWSS3StorageService else { - print("Unable to to cast to AWSS3StorageService") + guard let plugin = try? Amplify.Storage.getPlugin(for: "awsS3StoragePlugin") as? AWSS3StoragePlugin else { + print("Unable to to cast to AWSS3StoragePlugin") return } - if let delegate = service.urlSession.delegate as? StorageServiceSessionDelegate { - delegate.storageService = nil + for serviceBehaviour in plugin.storageServicesByBucket.values { + guard let service = serviceBehaviour as? AWSS3StorageService else { + continue + } + if let delegate = service.urlSession.delegate as? StorageServiceSessionDelegate { + delegate.storageService = nil + } + service.urlSession.invalidateAndCancel() } - service.urlSession.invalidateAndCancel() } } diff --git a/api-dump/AWSDataStorePlugin.json b/api-dump/AWSDataStorePlugin.json index 981969d8c6..6666c984bf 100644 --- a/api-dump/AWSDataStorePlugin.json +++ b/api-dump/AWSDataStorePlugin.json @@ -8205,7 +8205,7 @@ "-module", "AWSDataStorePlugin", "-o", - "\/var\/folders\/4d\/0gnh84wj53j7wyk695q0tc_80000gn\/T\/tmp.BZQxGLOyZz\/AWSDataStorePlugin.json", + "\/var\/folders\/k7\/4hy4p20s2rv4clw77d3qcl380000gr\/T\/tmp.nF2YaRsn2M\/AWSDataStorePlugin.json", "-I", ".build\/debug", "-sdk-version", diff --git a/api-dump/AWSPluginsCore.json b/api-dump/AWSPluginsCore.json index 088d2a9ea1..b9e3b9e454 100644 --- a/api-dump/AWSPluginsCore.json +++ b/api-dump/AWSPluginsCore.json @@ -24463,7 +24463,7 @@ "-module", "AWSPluginsCore", "-o", - "\/var\/folders\/4d\/0gnh84wj53j7wyk695q0tc_80000gn\/T\/tmp.BZQxGLOyZz\/AWSPluginsCore.json", + "\/var\/folders\/k7\/4hy4p20s2rv4clw77d3qcl380000gr\/T\/tmp.nF2YaRsn2M\/AWSPluginsCore.json", "-I", ".build\/debug", "-sdk-version", diff --git a/api-dump/Amplify.json b/api-dump/Amplify.json index 70967c7128..9fad52bcce 100644 --- a/api-dump/Amplify.json +++ b/api-dump/Amplify.json @@ -138551,6 +138551,84 @@ } ] }, + { + "kind": "Var", + "name": "bucket", + "printedName": "bucket", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "(any Amplify.StorageBucket)?", + "children": [ + { + "kind": "TypeNominal", + "name": "Paren", + "printedName": "(any Amplify.StorageBucket)", + "children": [ + { + "kind": "TypeNominal", + "name": "StorageBucket", + "printedName": "any Amplify.StorageBucket", + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "s:7Amplify26StorageDownloadDataRequestV7OptionsV6bucketAA0B6Bucket_pSgvp", + "mangledName": "$s7Amplify26StorageDownloadDataRequestV7OptionsV6bucketAA0B6Bucket_pSgvp", + "moduleName": "Amplify", + "declAttributes": [ + "HasStorage" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "(any Amplify.StorageBucket)?", + "children": [ + { + "kind": "TypeNominal", + "name": "Paren", + "printedName": "(any Amplify.StorageBucket)", + "children": [ + { + "kind": "TypeNominal", + "name": "StorageBucket", + "printedName": "any Amplify.StorageBucket", + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify26StorageDownloadDataRequestV7OptionsV6bucketAA0B6Bucket_pSgvg", + "mangledName": "$s7Amplify26StorageDownloadDataRequestV7OptionsV6bucketAA0B6Bucket_pSgvg", + "moduleName": "Amplify", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "get" + } + ] + }, { "kind": "Var", "name": "pluginOptions", @@ -138700,6 +138778,43 @@ "mangledName": "$s7Amplify26StorageDownloadDataRequestV7OptionsV06pluginF0AEypSg_tcfc", "moduleName": "Amplify", "init_kind": "Designated" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(bucket:pluginOptions:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Options", + "printedName": "Amplify.StorageDownloadDataRequest.Options", + "usr": "s:7Amplify26StorageDownloadDataRequestV7OptionsV" + }, + { + "kind": "TypeNominal", + "name": "GenericTypeParam", + "printedName": "some Amplify.StorageBucket" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Any?", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "hasDefaultArg": true, + "usr": "s:Sq" + } + ], + "declKind": "Constructor", + "usr": "s:7Amplify26StorageDownloadDataRequestV7OptionsV6bucket06pluginF0AEx_ypSgtcAA0B6BucketRzlufc", + "mangledName": "$s7Amplify26StorageDownloadDataRequestV7OptionsV6bucket06pluginF0AEx_ypSgtcAA0B6BucketRzlufc", + "moduleName": "Amplify", + "init_kind": "Designated" } ], "declKind": "Struct", @@ -139154,6 +139269,84 @@ } ] }, + { + "kind": "Var", + "name": "bucket", + "printedName": "bucket", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "(any Amplify.StorageBucket)?", + "children": [ + { + "kind": "TypeNominal", + "name": "Paren", + "printedName": "(any Amplify.StorageBucket)", + "children": [ + { + "kind": "TypeNominal", + "name": "StorageBucket", + "printedName": "any Amplify.StorageBucket", + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "s:7Amplify26StorageDownloadFileRequestV7OptionsV6bucketAA0B6Bucket_pSgvp", + "mangledName": "$s7Amplify26StorageDownloadFileRequestV7OptionsV6bucketAA0B6Bucket_pSgvp", + "moduleName": "Amplify", + "declAttributes": [ + "HasStorage" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "(any Amplify.StorageBucket)?", + "children": [ + { + "kind": "TypeNominal", + "name": "Paren", + "printedName": "(any Amplify.StorageBucket)", + "children": [ + { + "kind": "TypeNominal", + "name": "StorageBucket", + "printedName": "any Amplify.StorageBucket", + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify26StorageDownloadFileRequestV7OptionsV6bucketAA0B6Bucket_pSgvg", + "mangledName": "$s7Amplify26StorageDownloadFileRequestV7OptionsV6bucketAA0B6Bucket_pSgvg", + "moduleName": "Amplify", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "get" + } + ] + }, { "kind": "Var", "name": "pluginOptions", @@ -139302,10 +139495,43 @@ "usr": "s:7Amplify26StorageDownloadFileRequestV7OptionsV06pluginF0AEypSg_tcfc", "mangledName": "$s7Amplify26StorageDownloadFileRequestV7OptionsV06pluginF0AEypSg_tcfc", "moduleName": "Amplify", - "deprecated": true, - "declAttributes": [ - "Available" + "init_kind": "Designated" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(bucket:pluginOptions:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Options", + "printedName": "Amplify.StorageDownloadFileRequest.Options", + "usr": "s:7Amplify26StorageDownloadFileRequestV7OptionsV" + }, + { + "kind": "TypeNominal", + "name": "GenericTypeParam", + "printedName": "some Amplify.StorageBucket" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Any?", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "hasDefaultArg": true, + "usr": "s:Sq" + } ], + "declKind": "Constructor", + "usr": "s:7Amplify26StorageDownloadFileRequestV7OptionsV6bucket06pluginF0AEx_ypSgtcAA0B6BucketRzlufc", + "mangledName": "$s7Amplify26StorageDownloadFileRequestV7OptionsV6bucket06pluginF0AEx_ypSgtcAA0B6BucketRzlufc", + "moduleName": "Amplify", "init_kind": "Designated" } ], @@ -139798,6 +140024,84 @@ } ] }, + { + "kind": "Var", + "name": "bucket", + "printedName": "bucket", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "(any Amplify.StorageBucket)?", + "children": [ + { + "kind": "TypeNominal", + "name": "Paren", + "printedName": "(any Amplify.StorageBucket)", + "children": [ + { + "kind": "TypeNominal", + "name": "StorageBucket", + "printedName": "any Amplify.StorageBucket", + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "s:7Amplify20StorageGetURLRequestV7OptionsV6bucketAA0B6Bucket_pSgvp", + "mangledName": "$s7Amplify20StorageGetURLRequestV7OptionsV6bucketAA0B6Bucket_pSgvp", + "moduleName": "Amplify", + "declAttributes": [ + "HasStorage" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "(any Amplify.StorageBucket)?", + "children": [ + { + "kind": "TypeNominal", + "name": "Paren", + "printedName": "(any Amplify.StorageBucket)", + "children": [ + { + "kind": "TypeNominal", + "name": "StorageBucket", + "printedName": "any Amplify.StorageBucket", + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify20StorageGetURLRequestV7OptionsV6bucketAA0B6Bucket_pSgvg", + "mangledName": "$s7Amplify20StorageGetURLRequestV7OptionsV6bucketAA0B6Bucket_pSgvg", + "moduleName": "Amplify", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "get" + } + ] + }, { "kind": "Var", "name": "pluginOptions", @@ -139961,6 +140265,50 @@ "mangledName": "$s7Amplify20StorageGetURLRequestV7OptionsV7expires06pluginE0AESi_ypSgtcfc", "moduleName": "Amplify", "init_kind": "Designated" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(expires:bucket:pluginOptions:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Options", + "printedName": "Amplify.StorageGetURLRequest.Options", + "usr": "s:7Amplify20StorageGetURLRequestV7OptionsV" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "hasDefaultArg": true, + "usr": "s:Si" + }, + { + "kind": "TypeNominal", + "name": "GenericTypeParam", + "printedName": "some Amplify.StorageBucket" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Any?", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "hasDefaultArg": true, + "usr": "s:Sq" + } + ], + "declKind": "Constructor", + "usr": "s:7Amplify20StorageGetURLRequestV7OptionsV7expires6bucket06pluginE0AESi_xypSgtcAA0B6BucketRzlufc", + "mangledName": "$s7Amplify20StorageGetURLRequestV7OptionsV7expires6bucket06pluginE0AESi_xypSgtcAA0B6BucketRzlufc", + "moduleName": "Amplify", + "init_kind": "Designated" } ], "declKind": "Struct", @@ -140459,6 +140807,84 @@ } ] }, + { + "kind": "Var", + "name": "bucket", + "printedName": "bucket", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "(any Amplify.StorageBucket)?", + "children": [ + { + "kind": "TypeNominal", + "name": "Paren", + "printedName": "(any Amplify.StorageBucket)", + "children": [ + { + "kind": "TypeNominal", + "name": "StorageBucket", + "printedName": "any Amplify.StorageBucket", + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "s:7Amplify18StorageListRequestV7OptionsV6bucketAA0B6Bucket_pSgvp", + "mangledName": "$s7Amplify18StorageListRequestV7OptionsV6bucketAA0B6Bucket_pSgvp", + "moduleName": "Amplify", + "declAttributes": [ + "HasStorage" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "(any Amplify.StorageBucket)?", + "children": [ + { + "kind": "TypeNominal", + "name": "Paren", + "printedName": "(any Amplify.StorageBucket)", + "children": [ + { + "kind": "TypeNominal", + "name": "StorageBucket", + "printedName": "any Amplify.StorageBucket", + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify18StorageListRequestV7OptionsV6bucketAA0B6Bucket_pSgvg", + "mangledName": "$s7Amplify18StorageListRequestV7OptionsV6bucketAA0B6Bucket_pSgvg", + "moduleName": "Amplify", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "get" + } + ] + }, { "kind": "Var", "name": "nextToken", @@ -140679,6 +141105,72 @@ "moduleName": "Amplify", "init_kind": "Designated" }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(subpathStrategy:pageSize:bucket:nextToken:pluginOptions:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Options", + "printedName": "Amplify.StorageListRequest.Options", + "usr": "s:7Amplify18StorageListRequestV7OptionsV" + }, + { + "kind": "TypeNominal", + "name": "SubpathStrategy", + "printedName": "Amplify.StorageListRequest.Options.SubpathStrategy", + "hasDefaultArg": true, + "usr": "s:7Amplify18StorageListRequestV7OptionsV15SubpathStrategyO" + }, + { + "kind": "TypeNominal", + "name": "UInt", + "printedName": "Swift.UInt", + "hasDefaultArg": true, + "usr": "s:Su" + }, + { + "kind": "TypeNominal", + "name": "GenericTypeParam", + "printedName": "some Amplify.StorageBucket" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "hasDefaultArg": true, + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Any?", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "hasDefaultArg": true, + "usr": "s:Sq" + } + ], + "declKind": "Constructor", + "usr": "s:7Amplify18StorageListRequestV7OptionsV15subpathStrategy8pageSize6bucket9nextToken06pluginE0A2E07SubpathG0O_SuxSSSgypSgtcAA0B6BucketRzlufc", + "mangledName": "$s7Amplify18StorageListRequestV7OptionsV15subpathStrategy8pageSize6bucket9nextToken06pluginE0A2E07SubpathG0O_SuxSSSgypSgtcAA0B6BucketRzlufc", + "moduleName": "Amplify", + "init_kind": "Designated" + }, { "kind": "TypeDecl", "name": "SubpathStrategy", @@ -141170,6 +141662,84 @@ } ] }, + { + "kind": "Var", + "name": "bucket", + "printedName": "bucket", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "(any Amplify.StorageBucket)?", + "children": [ + { + "kind": "TypeNominal", + "name": "Paren", + "printedName": "(any Amplify.StorageBucket)", + "children": [ + { + "kind": "TypeNominal", + "name": "StorageBucket", + "printedName": "any Amplify.StorageBucket", + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "s:7Amplify20StorageRemoveRequestV7OptionsV6bucketAA0B6Bucket_pSgvp", + "mangledName": "$s7Amplify20StorageRemoveRequestV7OptionsV6bucketAA0B6Bucket_pSgvp", + "moduleName": "Amplify", + "declAttributes": [ + "HasStorage" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "(any Amplify.StorageBucket)?", + "children": [ + { + "kind": "TypeNominal", + "name": "Paren", + "printedName": "(any Amplify.StorageBucket)", + "children": [ + { + "kind": "TypeNominal", + "name": "StorageBucket", + "printedName": "any Amplify.StorageBucket", + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify20StorageRemoveRequestV7OptionsV6bucketAA0B6Bucket_pSgvg", + "mangledName": "$s7Amplify20StorageRemoveRequestV7OptionsV6bucketAA0B6Bucket_pSgvg", + "moduleName": "Amplify", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "get" + } + ] + }, { "kind": "Var", "name": "pluginOptions", @@ -141268,6 +141838,43 @@ "mangledName": "$s7Amplify20StorageRemoveRequestV7OptionsV11accessLevel06pluginE0AeA0b6AccessG0O_ypSgtcfc", "moduleName": "Amplify", "init_kind": "Designated" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(bucket:pluginOptions:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Options", + "printedName": "Amplify.StorageRemoveRequest.Options", + "usr": "s:7Amplify20StorageRemoveRequestV7OptionsV" + }, + { + "kind": "TypeNominal", + "name": "GenericTypeParam", + "printedName": "some Amplify.StorageBucket" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Any?", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "hasDefaultArg": true, + "usr": "s:Sq" + } + ], + "declKind": "Constructor", + "usr": "s:7Amplify20StorageRemoveRequestV7OptionsV6bucket06pluginE0AEx_ypSgtcAA0B6BucketRzlufc", + "mangledName": "$s7Amplify20StorageRemoveRequestV7OptionsV6bucket06pluginE0AEx_ypSgtcAA0B6BucketRzlufc", + "moduleName": "Amplify", + "init_kind": "Designated" } ], "declKind": "Struct", @@ -141812,6 +142419,84 @@ } ] }, + { + "kind": "Var", + "name": "bucket", + "printedName": "bucket", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "(any Amplify.StorageBucket)?", + "children": [ + { + "kind": "TypeNominal", + "name": "Paren", + "printedName": "(any Amplify.StorageBucket)", + "children": [ + { + "kind": "TypeNominal", + "name": "StorageBucket", + "printedName": "any Amplify.StorageBucket", + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "s:7Amplify24StorageUploadDataRequestV7OptionsV6bucketAA0B6Bucket_pSgvp", + "mangledName": "$s7Amplify24StorageUploadDataRequestV7OptionsV6bucketAA0B6Bucket_pSgvp", + "moduleName": "Amplify", + "declAttributes": [ + "HasStorage" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "(any Amplify.StorageBucket)?", + "children": [ + { + "kind": "TypeNominal", + "name": "Paren", + "printedName": "(any Amplify.StorageBucket)", + "children": [ + { + "kind": "TypeNominal", + "name": "StorageBucket", + "printedName": "any Amplify.StorageBucket", + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify24StorageUploadDataRequestV7OptionsV6bucketAA0B6Bucket_pSgvg", + "mangledName": "$s7Amplify24StorageUploadDataRequestV7OptionsV6bucketAA0B6Bucket_pSgvg", + "moduleName": "Amplify", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "get" + } + ] + }, { "kind": "Var", "name": "contentType", @@ -142111,6 +142796,87 @@ "mangledName": "$s7Amplify24StorageUploadDataRequestV7OptionsV8metadata11contentType06pluginF0AESDyS2SGSg_SSSgypSgtcfc", "moduleName": "Amplify", "init_kind": "Designated" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(metadata:bucket:contentType:pluginOptions:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Options", + "printedName": "Amplify.StorageUploadDataRequest.Options", + "usr": "s:7Amplify24StorageUploadDataRequestV7OptionsV" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:SD" + } + ], + "hasDefaultArg": true, + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "GenericTypeParam", + "printedName": "some Amplify.StorageBucket" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "hasDefaultArg": true, + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Any?", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "hasDefaultArg": true, + "usr": "s:Sq" + } + ], + "declKind": "Constructor", + "usr": "s:7Amplify24StorageUploadDataRequestV7OptionsV8metadata6bucket11contentType06pluginF0AESDyS2SGSg_xSSSgypSgtcAA0B6BucketRzlufc", + "mangledName": "$s7Amplify24StorageUploadDataRequestV7OptionsV8metadata6bucket11contentType06pluginF0AESDyS2SGSg_xSSSgypSgtcAA0B6BucketRzlufc", + "moduleName": "Amplify", + "init_kind": "Designated" } ], "declKind": "Struct", @@ -142655,6 +143421,84 @@ } ] }, + { + "kind": "Var", + "name": "bucket", + "printedName": "bucket", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "(any Amplify.StorageBucket)?", + "children": [ + { + "kind": "TypeNominal", + "name": "Paren", + "printedName": "(any Amplify.StorageBucket)", + "children": [ + { + "kind": "TypeNominal", + "name": "StorageBucket", + "printedName": "any Amplify.StorageBucket", + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "s:7Amplify24StorageUploadFileRequestV7OptionsV6bucketAA0B6Bucket_pSgvp", + "mangledName": "$s7Amplify24StorageUploadFileRequestV7OptionsV6bucketAA0B6Bucket_pSgvp", + "moduleName": "Amplify", + "declAttributes": [ + "HasStorage" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "(any Amplify.StorageBucket)?", + "children": [ + { + "kind": "TypeNominal", + "name": "Paren", + "printedName": "(any Amplify.StorageBucket)", + "children": [ + { + "kind": "TypeNominal", + "name": "StorageBucket", + "printedName": "any Amplify.StorageBucket", + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:7Amplify13StorageBucketP" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify24StorageUploadFileRequestV7OptionsV6bucketAA0B6Bucket_pSgvg", + "mangledName": "$s7Amplify24StorageUploadFileRequestV7OptionsV6bucketAA0B6Bucket_pSgvg", + "moduleName": "Amplify", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "get" + } + ] + }, { "kind": "Var", "name": "contentType", @@ -142954,6 +143798,87 @@ "mangledName": "$s7Amplify24StorageUploadFileRequestV7OptionsV8metadata11contentType06pluginF0AESDyS2SGSg_SSSgypSgtcfc", "moduleName": "Amplify", "init_kind": "Designated" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(metadata:bucket:contentType:pluginOptions:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Options", + "printedName": "Amplify.StorageUploadFileRequest.Options", + "usr": "s:7Amplify24StorageUploadFileRequestV7OptionsV" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:SD" + } + ], + "hasDefaultArg": true, + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "GenericTypeParam", + "printedName": "some Amplify.StorageBucket" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "hasDefaultArg": true, + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Any?", + "children": [ + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "hasDefaultArg": true, + "usr": "s:Sq" + } + ], + "declKind": "Constructor", + "usr": "s:7Amplify24StorageUploadFileRequestV7OptionsV8metadata6bucket11contentType06pluginF0AESDyS2SGSg_xSSSgypSgtcAA0B6BucketRzlufc", + "mangledName": "$s7Amplify24StorageUploadFileRequestV7OptionsV8metadata6bucket11contentType06pluginF0AESDyS2SGSg_xSSSgypSgtcAA0B6BucketRzlufc", + "moduleName": "Amplify", + "init_kind": "Designated" } ], "declKind": "Struct", @@ -144416,6 +145341,446 @@ } ] }, + { + "kind": "TypeDecl", + "name": "StorageBucket", + "printedName": "StorageBucket", + "children": [ + { + "kind": "Function", + "name": "fromOutputs", + "printedName": "fromOutputs(name:)", + "children": [ + { + "kind": "TypeNominal", + "name": "GenericTypeParam", + "printedName": "Self" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:7Amplify13StorageBucketPA2A07OutputsbC0VRszrlE04fromD04nameAESS_tFZ", + "mangledName": "$s7Amplify13StorageBucketPA2A07OutputsbC0VRszrlE04fromD04nameAESS_tFZ", + "moduleName": "Amplify", + "genericSig": "", + "static": true, + "isFromExtension": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "fromBucketInfo", + "printedName": "fromBucketInfo(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "GenericTypeParam", + "printedName": "Self" + }, + { + "kind": "TypeNominal", + "name": "BucketInfo", + "printedName": "Amplify.BucketInfo", + "usr": "s:7Amplify10BucketInfoV" + } + ], + "declKind": "Func", + "usr": "s:7Amplify13StorageBucketPA2A08ResolvedbC0VRszrlE04fromC4InfoyAeA0cF0VFZ", + "mangledName": "$s7Amplify13StorageBucketPA2A08ResolvedbC0VRszrlE04fromC4InfoyAeA0cF0VFZ", + "moduleName": "Amplify", + "genericSig": "", + "static": true, + "isFromExtension": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "s:7Amplify13StorageBucketP", + "mangledName": "$s7Amplify13StorageBucketP", + "moduleName": "Amplify" + }, + { + "kind": "TypeDecl", + "name": "BucketInfo", + "printedName": "BucketInfo", + "children": [ + { + "kind": "Var", + "name": "bucketName", + "printedName": "bucketName", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "s:7Amplify10BucketInfoV10bucketNameSSvp", + "mangledName": "$s7Amplify10BucketInfoV10bucketNameSSvp", + "moduleName": "Amplify", + "declAttributes": [ + "HasStorage" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify10BucketInfoV10bucketNameSSvg", + "mangledName": "$s7Amplify10BucketInfoV10bucketNameSSvg", + "moduleName": "Amplify", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "region", + "printedName": "region", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "s:7Amplify10BucketInfoV6regionSSvp", + "mangledName": "$s7Amplify10BucketInfoV6regionSSvp", + "moduleName": "Amplify", + "declAttributes": [ + "HasStorage" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify10BucketInfoV6regionSSvg", + "mangledName": "$s7Amplify10BucketInfoV6regionSSvg", + "moduleName": "Amplify", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(bucketName:region:)", + "children": [ + { + "kind": "TypeNominal", + "name": "BucketInfo", + "printedName": "Amplify.BucketInfo", + "usr": "s:7Amplify10BucketInfoV" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Constructor", + "usr": "s:7Amplify10BucketInfoV10bucketName6regionACSS_SStcfc", + "mangledName": "$s7Amplify10BucketInfoV10bucketName6regionACSS_SStcfc", + "moduleName": "Amplify", + "init_kind": "Designated" + }, + { + "kind": "Function", + "name": "hash", + "printedName": "hash(into:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Hasher", + "printedName": "Swift.Hasher", + "paramValueOwnership": "InOut", + "usr": "s:s6HasherV" + } + ], + "declKind": "Func", + "usr": "s:7Amplify10BucketInfoV4hash4intoys6HasherVz_tF", + "mangledName": "$s7Amplify10BucketInfoV4hash4intoys6HasherVz_tF", + "moduleName": "Amplify", + "implicit": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "__derived_struct_equals", + "printedName": "__derived_struct_equals(_:_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + }, + { + "kind": "TypeNominal", + "name": "BucketInfo", + "printedName": "Amplify.BucketInfo", + "usr": "s:7Amplify10BucketInfoV" + }, + { + "kind": "TypeNominal", + "name": "BucketInfo", + "printedName": "Amplify.BucketInfo", + "usr": "s:7Amplify10BucketInfoV" + } + ], + "declKind": "Func", + "usr": "s:7Amplify10BucketInfoV23__derived_struct_equalsySbAC_ACtFZ", + "mangledName": "$s7Amplify10BucketInfoV23__derived_struct_equalsySbAC_ACtFZ", + "moduleName": "Amplify", + "static": true, + "implicit": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Var", + "name": "hashValue", + "printedName": "hashValue", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Var", + "usr": "s:7Amplify10BucketInfoV9hashValueSivp", + "mangledName": "$s7Amplify10BucketInfoV9hashValueSivp", + "moduleName": "Amplify", + "implicit": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify10BucketInfoV9hashValueSivg", + "mangledName": "$s7Amplify10BucketInfoV9hashValueSivg", + "moduleName": "Amplify", + "implicit": true, + "accessorKind": "get" + } + ] + } + ], + "declKind": "Struct", + "usr": "s:7Amplify10BucketInfoV", + "mangledName": "$s7Amplify10BucketInfoV", + "moduleName": "Amplify", + "conformances": [ + { + "kind": "Conformance", + "name": "Hashable", + "printedName": "Hashable", + "usr": "s:SH", + "mangledName": "$sSH" + }, + { + "kind": "Conformance", + "name": "Equatable", + "printedName": "Equatable", + "usr": "s:SQ", + "mangledName": "$sSQ" + } + ] + }, + { + "kind": "TypeDecl", + "name": "OutputsStorageBucket", + "printedName": "OutputsStorageBucket", + "children": [ + { + "kind": "Var", + "name": "name", + "printedName": "name", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "s:7Amplify20OutputsStorageBucketV4nameSSvp", + "mangledName": "$s7Amplify20OutputsStorageBucketV4nameSSvp", + "moduleName": "Amplify", + "declAttributes": [ + "HasStorage" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify20OutputsStorageBucketV4nameSSvg", + "mangledName": "$s7Amplify20OutputsStorageBucketV4nameSSvg", + "moduleName": "Amplify", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "get" + } + ] + } + ], + "declKind": "Struct", + "usr": "s:7Amplify20OutputsStorageBucketV", + "mangledName": "$s7Amplify20OutputsStorageBucketV", + "moduleName": "Amplify", + "conformances": [ + { + "kind": "Conformance", + "name": "StorageBucket", + "printedName": "StorageBucket", + "usr": "s:7Amplify13StorageBucketP", + "mangledName": "$s7Amplify13StorageBucketP" + } + ] + }, + { + "kind": "TypeDecl", + "name": "ResolvedStorageBucket", + "printedName": "ResolvedStorageBucket", + "children": [ + { + "kind": "Var", + "name": "bucketInfo", + "printedName": "bucketInfo", + "children": [ + { + "kind": "TypeNominal", + "name": "BucketInfo", + "printedName": "Amplify.BucketInfo", + "usr": "s:7Amplify10BucketInfoV" + } + ], + "declKind": "Var", + "usr": "s:7Amplify21ResolvedStorageBucketV10bucketInfoAA0dF0Vvp", + "mangledName": "$s7Amplify21ResolvedStorageBucketV10bucketInfoAA0dF0Vvp", + "moduleName": "Amplify", + "declAttributes": [ + "HasStorage" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "BucketInfo", + "printedName": "Amplify.BucketInfo", + "usr": "s:7Amplify10BucketInfoV" + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify21ResolvedStorageBucketV10bucketInfoAA0dF0Vvg", + "mangledName": "$s7Amplify21ResolvedStorageBucketV10bucketInfoAA0dF0Vvg", + "moduleName": "Amplify", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "get" + } + ] + } + ], + "declKind": "Struct", + "usr": "s:7Amplify21ResolvedStorageBucketV", + "mangledName": "$s7Amplify21ResolvedStorageBucketV", + "moduleName": "Amplify", + "conformances": [ + { + "kind": "Conformance", + "name": "StorageBucket", + "printedName": "StorageBucket", + "usr": "s:7Amplify13StorageBucketP", + "mangledName": "$s7Amplify13StorageBucketP" + } + ] + }, { "kind": "TypeDecl", "name": "StorageCategory", @@ -153844,6 +155209,350 @@ } ] }, + { + "kind": "Var", + "name": "buckets", + "printedName": "buckets", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Amplify.AmplifyOutputsData.Storage.Bucket]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Amplify.AmplifyOutputsData.Storage.Bucket]", + "children": [ + { + "kind": "TypeNominal", + "name": "Bucket", + "printedName": "Amplify.AmplifyOutputsData.Storage.Bucket", + "usr": "s:7Amplify0A11OutputsDataV7StorageV6BucketV" + } + ], + "usr": "s:Sa" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "s:7Amplify0A11OutputsDataV7StorageV7bucketsSayAE6BucketVGSgvp", + "mangledName": "$s7Amplify0A11OutputsDataV7StorageV7bucketsSayAE6BucketVGSgvp", + "moduleName": "Amplify", + "declAttributes": [ + "HasStorage" + ], + "spi_group_names": [ + "InternalAmplifyConfiguration" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Amplify.AmplifyOutputsData.Storage.Bucket]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Amplify.AmplifyOutputsData.Storage.Bucket]", + "children": [ + { + "kind": "TypeNominal", + "name": "Bucket", + "printedName": "Amplify.AmplifyOutputsData.Storage.Bucket", + "usr": "s:7Amplify0A11OutputsDataV7StorageV6BucketV" + } + ], + "usr": "s:Sa" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify0A11OutputsDataV7StorageV7bucketsSayAE6BucketVGSgvg", + "mangledName": "$s7Amplify0A11OutputsDataV7StorageV7bucketsSayAE6BucketVGSgvg", + "moduleName": "Amplify", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "spi_group_names": [ + "InternalAmplifyConfiguration" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "TypeDecl", + "name": "Bucket", + "printedName": "Bucket", + "children": [ + { + "kind": "Var", + "name": "name", + "printedName": "name", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "s:7Amplify0A11OutputsDataV7StorageV6BucketV4nameSSvp", + "mangledName": "$s7Amplify0A11OutputsDataV7StorageV6BucketV4nameSSvp", + "moduleName": "Amplify", + "declAttributes": [ + "HasStorage" + ], + "spi_group_names": [ + "InternalAmplifyConfiguration" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify0A11OutputsDataV7StorageV6BucketV4nameSSvg", + "mangledName": "$s7Amplify0A11OutputsDataV7StorageV6BucketV4nameSSvg", + "moduleName": "Amplify", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "spi_group_names": [ + "InternalAmplifyConfiguration" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "bucketName", + "printedName": "bucketName", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "s:7Amplify0A11OutputsDataV7StorageV6BucketV10bucketNameSSvp", + "mangledName": "$s7Amplify0A11OutputsDataV7StorageV6BucketV10bucketNameSSvp", + "moduleName": "Amplify", + "declAttributes": [ + "HasStorage" + ], + "spi_group_names": [ + "InternalAmplifyConfiguration" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify0A11OutputsDataV7StorageV6BucketV10bucketNameSSvg", + "mangledName": "$s7Amplify0A11OutputsDataV7StorageV6BucketV10bucketNameSSvg", + "moduleName": "Amplify", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "spi_group_names": [ + "InternalAmplifyConfiguration" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "awsRegion", + "printedName": "awsRegion", + "children": [ + { + "kind": "TypeNameAlias", + "name": "AWSRegion", + "printedName": "Amplify.AmplifyOutputsData.AWSRegion", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ] + } + ], + "declKind": "Var", + "usr": "s:7Amplify0A11OutputsDataV7StorageV6BucketV9awsRegionSSvp", + "mangledName": "$s7Amplify0A11OutputsDataV7StorageV6BucketV9awsRegionSSvp", + "moduleName": "Amplify", + "declAttributes": [ + "HasStorage" + ], + "spi_group_names": [ + "InternalAmplifyConfiguration" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNameAlias", + "name": "AWSRegion", + "printedName": "Amplify.AmplifyOutputsData.AWSRegion", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ] + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify0A11OutputsDataV7StorageV6BucketV9awsRegionSSvg", + "mangledName": "$s7Amplify0A11OutputsDataV7StorageV6BucketV9awsRegionSSvg", + "moduleName": "Amplify", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "spi_group_names": [ + "InternalAmplifyConfiguration" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Function", + "name": "encode", + "printedName": "encode(to:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Encoder", + "printedName": "any Swift.Encoder", + "usr": "s:s7EncoderP" + } + ], + "declKind": "Func", + "usr": "s:7Amplify0A11OutputsDataV7StorageV6BucketV6encode2toys7Encoder_p_tKF", + "mangledName": "$s7Amplify0A11OutputsDataV7StorageV6BucketV6encode2toys7Encoder_p_tKF", + "moduleName": "Amplify", + "implicit": true, + "spi_group_names": [ + "InternalAmplifyConfiguration" + ], + "throwing": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(from:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Bucket", + "printedName": "Amplify.AmplifyOutputsData.Storage.Bucket", + "usr": "s:7Amplify0A11OutputsDataV7StorageV6BucketV" + }, + { + "kind": "TypeNominal", + "name": "Decoder", + "printedName": "any Swift.Decoder", + "usr": "s:s7DecoderP" + } + ], + "declKind": "Constructor", + "usr": "s:7Amplify0A11OutputsDataV7StorageV6BucketV4fromAGs7Decoder_p_tKcfc", + "mangledName": "$s7Amplify0A11OutputsDataV7StorageV6BucketV4fromAGs7Decoder_p_tKcfc", + "moduleName": "Amplify", + "implicit": true, + "spi_group_names": [ + "InternalAmplifyConfiguration" + ], + "throwing": true, + "init_kind": "Designated" + } + ], + "declKind": "Struct", + "usr": "s:7Amplify0A11OutputsDataV7StorageV6BucketV", + "mangledName": "$s7Amplify0A11OutputsDataV7StorageV6BucketV", + "moduleName": "Amplify", + "declAttributes": [ + "SPIAccessControl" + ], + "spi_group_names": [ + "InternalAmplifyConfiguration" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Decodable", + "printedName": "Decodable", + "usr": "s:Se", + "mangledName": "$sSe" + }, + { + "kind": "Conformance", + "name": "Encodable", + "printedName": "Encodable", + "usr": "s:SE", + "mangledName": "$sSE" + } + ] + }, { "kind": "Function", "name": "encode", @@ -178788,7 +180497,7 @@ ], "declKind": "Struct", "usr": "c:@S@CLLocationCoordinate2D", - "location": "\/Applications\/Xcode_15.4.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/CoreLocation.framework\/Headers\/CLLocation.h:79:8", + "location": "\/Applications\/Xcode-15.4.0.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/CoreLocation.framework\/Headers\/CLLocation.h:79:8", "moduleName": "CoreLocation", "isExternal": true }, @@ -179448,7 +181157,7 @@ ], "declKind": "Class", "usr": "c:objc(cs)OS_dispatch_source", - "location": "\/Applications\/Xcode_15.4.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/dispatch\/source.h:58:1", + "location": "\/Applications\/Xcode-15.4.0.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/usr\/include\/dispatch\/source.h:58:1", "moduleName": "Dispatch", "isOpen": true, "objc_name": "OS_dispatch_source", @@ -179728,7 +181437,7 @@ ], "declKind": "Protocol", "usr": "c:objc(pl)NSLocking", - "location": "\/Applications\/Xcode_15.4.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/Foundation.framework\/Headers\/NSLock.h:11:11", + "location": "\/Applications\/Xcode-15.4.0.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk\/System\/Library\/Frameworks\/Foundation.framework\/Headers\/NSLock.h:11:11", "moduleName": "Foundation", "genericSig": "", "objc_name": "NSLocking", @@ -179804,12 +181513,12 @@ "json_format_version": 8, "tool_arguments": [ "-sdk", - "\/Applications\/Xcode_15.4.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk", + "\/Applications\/Xcode-15.4.0.app\/Contents\/Developer\/Platforms\/MacOSX.platform\/Developer\/SDKs\/MacOSX.sdk", "-dump-sdk", "-module", "Amplify", "-o", - "\/var\/folders\/4d\/0gnh84wj53j7wyk695q0tc_80000gn\/T\/tmp.BZQxGLOyZz\/Amplify.json", + "\/var\/folders\/k7\/4hy4p20s2rv4clw77d3qcl380000gr\/T\/tmp.nF2YaRsn2M\/Amplify.json", "-I", ".build\/debug", "-sdk-version", diff --git a/api-dump/CoreMLPredictionsPlugin.json b/api-dump/CoreMLPredictionsPlugin.json index 618df7be0e..f3073090da 100644 --- a/api-dump/CoreMLPredictionsPlugin.json +++ b/api-dump/CoreMLPredictionsPlugin.json @@ -430,7 +430,7 @@ "-module", "CoreMLPredictionsPlugin", "-o", - "\/var\/folders\/4d\/0gnh84wj53j7wyk695q0tc_80000gn\/T\/tmp.BZQxGLOyZz\/CoreMLPredictionsPlugin.json", + "\/var\/folders\/k7\/4hy4p20s2rv4clw77d3qcl380000gr\/T\/tmp.nF2YaRsn2M\/CoreMLPredictionsPlugin.json", "-I", ".build\/debug", "-sdk-version",