diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+AsyncClientBehavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+AsyncClientBehavior.swift index 773f2bffb4..033af1a2e5 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+AsyncClientBehavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+AsyncClientBehavior.swift @@ -30,9 +30,13 @@ extension AWSS3StoragePlugin { if let pluginOptions = options.pluginOptions as? AWSStorageGetURLOptions, pluginOptions.validateObjectExistence { try await storageService.validateObjectExistence(serviceKey: serviceKey) } - let result = try await storageService.getPreSignedURL(serviceKey: serviceKey, - signingOperation: .getObject, - expires: options.expires) + let accelerate = try AWSS3PluginOptions.accelerateValue( + pluginOptions: options.pluginOptions) + let result = try await storageService.getPreSignedURL( + serviceKey: serviceKey, + signingOperation: .getObject, + accelerate: accelerate, + expires: options.expires) let channel = HubChannel(from: categoryType) let payload = HubPayload(eventName: HubPayload.EventName.Storage.getURL, context: options, data: result) diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Configuration/AWSS3PluginOptions.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Configuration/AWSS3PluginOptions.swift new file mode 100644 index 0000000000..fcec7bc47e --- /dev/null +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Configuration/AWSS3PluginOptions.swift @@ -0,0 +1,47 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Amplify +import Foundation + +/// - Tag: AWSS3PluginOptions +struct AWSS3PluginOptions { + + /// - Tag: AWSS3PluginOptionsCodingKeys + enum CodingKeys: String, CodingKey { + + /// See: https://docs.amplify.aws/lib/storage/transfer-acceleration/q/platform/js/ + /// - Tag: AWSS3PluginOptionsCodingKeys.useAccelerateEndpoint + case useAccelerateEndpoint + } + + /// Attempts to extract the boolean under the + /// [useAccelerateEndpoint](x-source-tag://AWSS3PluginOptionsCodingKeys.useAccelerateEndpoint) + /// contained in the given dictionary. + /// + /// In other words, a non-nil boolean is returned if: + /// + /// * The `pluginOptions` parameter is a dictionary ([String: Any]) + /// * The `pluginOptions` dictionary contains a boolean key under the [useAccelerateEndpoint](x-source-tag://AWSS3PluginOptionsCodingKeys.useAccelerateEndpoint) key. + /// + /// - Tag: AWSS3PluginOptions.accelerateValue + static func accelerateValue(pluginOptions: Any?) throws -> Bool? { + guard let pluginOptions = pluginOptions as? [String:Any] else { + return nil + } + guard let value = pluginOptions[CodingKeys.useAccelerateEndpoint.rawValue] else { + return nil + } + guard let boolValue = value as? Bool else { + throw StorageError.validation(CodingKeys.useAccelerateEndpoint.rawValue, + "Expecting boolean value for key \(CodingKeys.useAccelerateEndpoint.rawValue)", + "Ensure the value associated with \(CodingKeys.useAccelerateEndpoint.rawValue) is a boolean", + nil) + } + return boolValue + } +} diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3PreSignedURLBuilderAdapter.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3PreSignedURLBuilderAdapter.swift index 44d12c3599..0ff4a5db4b 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3PreSignedURLBuilderAdapter.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3PreSignedURLBuilderAdapter.swift @@ -34,9 +34,13 @@ class AWSS3PreSignedURLBuilderAdapter: AWSS3PreSignedURLBuilderBehavior { /// - Returns: Pre-Signed URL func getPreSignedURL(key: String, signingOperation: AWSS3SigningOperation, + accelerate: Bool? = nil, expires: Int64? = nil) async throws -> URL { let expiresDate = Date(timeIntervalSinceNow: Double(expires ?? defaultExpiration)) let expiration = expiresDate.timeIntervalSinceNow + let config = (accelerate == nil) ? self.config : S3ClientConfigurationProxy( + target: self.config, + accelerateOverride: accelerate) let preSignedUrl: URL? switch signingOperation { case .getObject: diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3PreSignedURLBuilderBehavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3PreSignedURLBuilderBehavior.swift index 9cd401e3ab..dddc19ed44 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3PreSignedURLBuilderBehavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/AWSS3PreSignedURLBuilderBehavior.swift @@ -12,14 +12,36 @@ import AWSS3 import ClientRuntime import AWSClientRuntime +/// - Tag: AWSS3PreSignedURLBuilderError enum AWSS3PreSignedURLBuilderError: Error { + + /// Returned by an implementation of a + /// [AWSS3PreSignedURLBuilderBehavior](x-source-tag://AWSS3PreSignedURLBuilderBehavior) + /// + /// - Tag: AWSS3PreSignedURLBuilderError.failed case failed(reason: String, error: Error?) } -// Behavior that the implemenation class for AWSS3PreSignedURLBuilder will use. +/// Behavior that the implemenation class for AWSS3PreSignedURLBuilder will use. +/// +/// - Tag: AWSS3PreSignedURLBuilderBehavior protocol AWSS3PreSignedURLBuilderBehavior { - /// Gets a pre-signed URL. + /// Attempts to generate a pre-signed URL. + /// + /// - Parameters: + /// - key: String represnting the key of an S3 object. + /// - signingOperation: [AWSS3SigningOperation](x-source-tag://AWSS3SigningOperation) + /// (get, put, upload part) for which the URL will be generated. + /// - accelerate: Optional boolean indicating wether or not to enable S3 bucket + /// [transfer acceleration](https://docs.amplify.aws/lib/storage/transfer-acceleration/q/platform/js/) + /// - expires: Int64 indicating the expiration as the number of milliseconds since the 1970 epoc. /// - Returns: Pre-Signed URL - func getPreSignedURL(key: String, signingOperation: AWSS3SigningOperation, expires: Int64?) async throws -> URL + /// + /// - Tag: AWSS3PreSignedURLBuilderBehavior.getPreSignedURL + func getPreSignedURL(key: String, + signingOperation: AWSS3SigningOperation, + accelerate: Bool?, + expires: Int64?) async throws -> URL + } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/S3ClientConfigurationProxy.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/S3ClientConfigurationProxy.swift new file mode 100644 index 0000000000..6a6ebb04d3 --- /dev/null +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/S3ClientConfigurationProxy.swift @@ -0,0 +1,161 @@ +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +import AWSS3 +import AWSClientRuntime +import ClientRuntime +import Foundation + +/// Convenience proxy class around a +/// [S3ClientConfigurationProtocol](x-source-tag://S3ClientConfigurationProtocol) +/// implementaitons that allows Amplify to change configuration values JIT. +/// +/// - Tag: S3ClientConfigurationProxy +struct S3ClientConfigurationProxy { + + /// - Tag: S3ClientConfigurationProxy.target + var target: S3ClientConfigurationProtocol + + /// - Tag: S3ClientConfigurationProxy.accelerateOverride + var accelerateOverride: Bool? +} + +extension S3ClientConfigurationProxy: S3ClientConfigurationProtocol { + + var accelerate: Bool? { + if let accelerateOverride = accelerateOverride { + return accelerateOverride + } + return target.accelerate + } + + var disableMultiRegionAccessPoints: Bool? { + return target.disableMultiRegionAccessPoints + } + + var endpointResolver: EndpointResolver { + return target.endpointResolver + } + + var forcePathStyle: Bool? { + return target.forcePathStyle + } + + var useArnRegion: Bool? { + return target.useArnRegion + } + + var useGlobalEndpoint: Bool? { + return target.useGlobalEndpoint + } + + var credentialsProvider: AWSClientRuntime.CredentialsProvider { + get { + return target.credentialsProvider + } + set(newValue) { + target.credentialsProvider = newValue + } + } + + var region: String? { + get { + return target.region + } + set(newValue) { + target.region = newValue + } + } + + var signingRegion: String? { + get { + return target.signingRegion + } + set(newValue) { + target.signingRegion = newValue + } + } + + var regionResolver: RegionResolver? { + get { + return target.regionResolver + } + set(newValue) { + target.regionResolver = newValue + } + } + + var frameworkMetadata: FrameworkMetadata? { + get { + return target.frameworkMetadata + } + set(newValue) { + target.frameworkMetadata = newValue + } + } + + var useFIPS: Bool? { + get { + return target.useFIPS + } + set(newValue) { + target.useFIPS = newValue + } + } + + var useDualStack: Bool? { + get { + return target.useDualStack + } + set(newValue) { + target.useDualStack = newValue + } + } + + var logger: LogAgent { + return target.logger + } + + var retryer: ClientRuntime.SDKRetryer { + return target.retryer + } + + var endpoint: String? { + get { + return target.endpoint + } + set(newValue) { + target.endpoint = newValue + } + } + + var encoder: ClientRuntime.RequestEncoder? { + return target.encoder + } + + var decoder: ClientRuntime.ResponseDecoder? { + return target.decoder + } + + var httpClientEngine: ClientRuntime.HttpClientEngine { + return target.httpClientEngine + } + + var httpClientConfiguration: ClientRuntime.HttpClientConfiguration { + return target.httpClientConfiguration + } + + var idempotencyTokenGenerator: ClientRuntime.IdempotencyTokenGenerator { + return target.idempotencyTokenGenerator + } + + var clientLogMode: ClientRuntime.ClientLogMode { + return target.clientLogMode + } + + var partitionID: String? { + return target.partitionID + } +} diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageDownloadDataOperation.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageDownloadDataOperation.swift index 059c8432aa..4df8672f9d 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageDownloadDataOperation.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageDownloadDataOperation.swift @@ -91,7 +91,8 @@ class AWSS3StorageDownloadDataOperation: AmplifyInProcessReportingOperation< do { let prefix = try await prefixResolver.resolvePrefix(for: request.options.accessLevel, targetIdentityId: request.options.targetIdentityId) let serviceKey = prefix + request.key - storageService.download(serviceKey: serviceKey, fileURL: nil) { [weak self] event in + let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions) + 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 94c59b7f0a..88616953c1 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageDownloadFileOperation.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageDownloadFileOperation.swift @@ -94,7 +94,8 @@ class AWSS3StorageDownloadFileOperation: AmplifyInProcessReportingOperation< do { let prefix = try await prefixResolver.resolvePrefix(for: request.options.accessLevel, targetIdentityId: request.options.targetIdentityId) let serviceKey = prefix + request.key - storageService.download(serviceKey: serviceKey, fileURL: self.request.local) { [weak self] event in + let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions) + storageService.download(serviceKey: serviceKey, fileURL: self.request.local, accelerate: accelerate) { [weak self] event in self?.onServiceEvent(event: event) } } catch { diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadDataOperation.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadDataOperation.swift index fa9f6ef485..48e428c425 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadDataOperation.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadDataOperation.swift @@ -92,18 +92,21 @@ class AWSS3StorageUploadDataOperation: AmplifyInProcessReportingOperation< let prefix = try await prefixResolver.resolvePrefix(for: request.options.accessLevel, targetIdentityId: request.options.targetIdentityId) let serviceKey = prefix + request.key let serviceMetadata = StorageRequestUtils.getServiceMetadata(request.options.metadata) + let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions) if request.data.count > StorageUploadDataRequest.Options.multiPartUploadSizeThreshold { storageService.multiPartUpload(serviceKey: serviceKey, uploadSource: .data(request.data), contentType: request.options.contentType, - metadata: serviceMetadata) { [weak self] event in + metadata: serviceMetadata, + accelerate: accelerate) { [weak self] event in self?.onServiceEvent(event: event) } } else { storageService.upload(serviceKey: serviceKey, uploadSource: .data(request.data), contentType: request.options.contentType, - metadata: serviceMetadata) { [weak self] event in + metadata: serviceMetadata, + accelerate: accelerate) { [weak self] event in self?.onServiceEvent(event: event) } } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadFileOperation.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadFileOperation.swift index 95c57390cc..fce5fdd94d 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadFileOperation.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadFileOperation.swift @@ -116,18 +116,21 @@ class AWSS3StorageUploadFileOperation: AmplifyInProcessReportingOperation< let prefix = try await prefixResolver.resolvePrefix(for: request.options.accessLevel, targetIdentityId: request.options.targetIdentityId) let serviceKey = prefix + request.key let serviceMetadata = StorageRequestUtils.getServiceMetadata(request.options.metadata) + let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions) if uploadSize > StorageUploadFileRequest.Options.multiPartUploadSizeThreshold { storageService.multiPartUpload(serviceKey: serviceKey, uploadSource: .local(request.local), contentType: request.options.contentType, - metadata: serviceMetadata) { [weak self] event in + metadata: serviceMetadata, + accelerate: accelerate) { [weak self] event in self?.onServiceEvent(event: event) } } else { storageService.upload(serviceKey: serviceKey, uploadSource: .local(request.local), contentType: request.options.contentType, - metadata: serviceMetadata) { [weak self] event in + metadata: serviceMetadata, + accelerate: accelerate) { [weak self] event in self?.onServiceEvent(event: event) } } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+DownloadBehavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+DownloadBehavior.swift index f2960ec9ca..c4893c86c9 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+DownloadBehavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+DownloadBehavior.swift @@ -12,7 +12,8 @@ extension AWSS3StorageService { func download(serviceKey: String, fileURL: URL?, - onEvent: @escaping StorageServiceDownloadEventHandler) { + accelerate: Bool?, + onEvent: @escaping StorageServiceDownloadEventHandler) { let fail: (Error) -> Void = { error in let storageError = StorageError(error: error) onEvent(.failed(storageError)) @@ -27,7 +28,10 @@ extension AWSS3StorageService { Task { do { - let preSignedURL = try await preSignedURLBuilder.getPreSignedURL(key: serviceKey, signingOperation: .getObject, expires: nil) + let preSignedURL = try await preSignedURLBuilder.getPreSignedURL(key: serviceKey, + signingOperation: .getObject, + accelerate: accelerate, + expires: nil) startDownload(preSignedURL: preSignedURL, transferTask: transferTask) } catch { onEvent(.failed(StorageError.unknown("Failed to get pre-signed URL", nil))) diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+GetPreSignedURLBehavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+GetPreSignedURLBehavior.swift index f91e546016..1716891ab1 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+GetPreSignedURLBehavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+GetPreSignedURLBehavior.swift @@ -14,8 +14,13 @@ extension AWSS3StorageService { func getPreSignedURL(serviceKey: String, signingOperation: AWSS3SigningOperation, + accelerate: Bool?, expires: Int) async throws -> URL { - return try await preSignedURLBuilder.getPreSignedURL(key: serviceKey, signingOperation: signingOperation, expires: Int64(expires)) + return try await preSignedURLBuilder.getPreSignedURL( + key: serviceKey, + signingOperation: signingOperation, + accelerate: accelerate, + expires: Int64(expires)) } func validateObjectExistence(serviceKey: String) async throws { diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+MultiPartUploadBehavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+MultiPartUploadBehavior.swift index 6c2a30dc11..77d7e4efb7 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+MultiPartUploadBehavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+MultiPartUploadBehavior.swift @@ -14,6 +14,7 @@ extension AWSS3StorageService { uploadSource: UploadSource, contentType: String?, metadata: [String: String]?, + accelerate: Bool?, onEvent: @escaping StorageServiceMultiPartUploadEventHandler) { let fail: (Error) -> Void = { error in let storageError = StorageError(error: error) diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+UploadBehavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+UploadBehavior.swift index 5e35e23d66..7bff9e94ba 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+UploadBehavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+UploadBehavior.swift @@ -14,6 +14,7 @@ extension AWSS3StorageService { uploadSource: UploadSource, contentType: String?, metadata: [String: String]?, + accelerate: Bool?, onEvent: @escaping StorageServiceUploadEventHandler) { let fail: (Error) -> Void = { error in let storageError = StorageError(error: error) @@ -35,6 +36,7 @@ extension AWSS3StorageService { do { let preSignedURL = try await preSignedURLBuilder.getPreSignedURL(key: serviceKey, signingOperation: .putObject, + accelerate: accelerate, expires: nil) startUpload(preSignedURL: preSignedURL, fileURL: uploadFileURL, diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageServiceBehavior.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageServiceBehavior.swift index 0cb419c68c..8226df07ff 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageServiceBehavior.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageServiceBehavior.swift @@ -39,10 +39,12 @@ protocol AWSS3StorageServiceBehavior { func download(serviceKey: String, fileURL: URL?, + accelerate: Bool?, onEvent: @escaping StorageServiceDownloadEventHandler) func getPreSignedURL(serviceKey: String, signingOperation: AWSS3SigningOperation, + accelerate: Bool?, expires: Int) async throws -> URL func validateObjectExistence(serviceKey: String) async throws @@ -51,12 +53,14 @@ protocol AWSS3StorageServiceBehavior { uploadSource: UploadSource, contentType: String?, metadata: [String: String]?, + accelerate: Bool?, onEvent: @escaping StorageServiceUploadEventHandler) func multiPartUpload(serviceKey: String, uploadSource: UploadSource, contentType: String?, metadata: [String: String]?, + accelerate: Bool?, onEvent: @escaping StorageServiceMultiPartUploadEventHandler) func list(prefix: String, diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadClient.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadClient.swift index b4dba8f289..7bedf97def 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadClient.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadClient.swift @@ -135,6 +135,7 @@ class DefaultStorageMultipartUploadClient: StorageMultipartUploadClient { let preSignedURL = try await serviceProxy.preSignedURLBuilder.getPreSignedURL( key: self.key, signingOperation: operation, + accelerate: nil, expires: nil ) startUploadPart(partialFileURL, preSignedURL) diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginGetPresignedUrlTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginGetPresignedUrlTests.swift index 3f7f44a7cc..49a5734754 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginGetPresignedUrlTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/AWSS3StoragePluginGetPresignedUrlTests.swift @@ -65,7 +65,7 @@ final class AWSS3StoragePluginGetPresignedUrlTests: XCTestCase { ]) let expectedServiceKey = "public/" + testKey XCTAssertEqual(storageService.interactions, [ - "getPreSignedURL(serviceKey:signingOperation:expires:) \(expectedServiceKey) getObject 18000" + "getPreSignedURL(serviceKey:signingOperation:accelerate:expires:) \(expectedServiceKey) getObject 18000" ]) } @@ -120,7 +120,7 @@ final class AWSS3StoragePluginGetPresignedUrlTests: XCTestCase { let expectedServiceKey = StorageAccessLevel.protected.rawValue + "/" + testIdentityId + "/" + testKey XCTAssertEqual(storageService.interactions, [ - "getPreSignedURL(serviceKey:signingOperation:expires:) \(expectedServiceKey) getObject \(expectedExpires)" + "getPreSignedURL(serviceKey:signingOperation:accelerate:expires:) \(expectedServiceKey) getObject \(expectedExpires)" ]) } @@ -152,7 +152,7 @@ final class AWSS3StoragePluginGetPresignedUrlTests: XCTestCase { let expectedServiceKey = StorageAccessLevel.protected.rawValue + "/" + testIdentityId + "/" + testKey XCTAssertEqual(storageService.interactions, [ - "getPreSignedURL(serviceKey:signingOperation:expires:) \(expectedServiceKey) getObject \(expectedExpires)" + "getPreSignedURL(serviceKey:signingOperation:accelerate:expires:) \(expectedServiceKey) getObject \(expectedExpires)" ]) } @@ -173,7 +173,7 @@ final class AWSS3StoragePluginGetPresignedUrlTests: XCTestCase { let expectedExpires = 18000 let expectedServiceKey = StorageAccessLevel.protected.rawValue + "/" + testIdentityId + "/" + testKey XCTAssertEqual(storageService.interactions, [ - "getPreSignedURL(serviceKey:signingOperation:expires:) \(expectedServiceKey) getObject \(expectedExpires)" + "getPreSignedURL(serviceKey:signingOperation:accelerate:expires:) \(expectedServiceKey) getObject \(expectedExpires)" ]) } diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Configuration/S3ClientConfigurationProxyTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Configuration/S3ClientConfigurationProxyTests.swift new file mode 100644 index 0000000000..19a38fb062 --- /dev/null +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Configuration/S3ClientConfigurationProxyTests.swift @@ -0,0 +1,79 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import AWSS3 +import AWSClientRuntime +import ClientRuntime +import Foundation +import XCTest + +@testable import AWSS3StoragePlugin + +final class S3ClientConfigurationProxyTests: XCTestCase { + + /// Given: A client configuration that has a value for a property such as `accelerate`. + /// When: An override is set on its proxy configuration. + /// Then: The proxy returns the value from the override. + func testPropertyOverrides() async throws { + let target = try await S3Client.S3ClientConfiguration() + target.accelerate = true + + let sut = S3ClientConfigurationProxy(target: target, accelerateOverride: false) + XCTAssertEqual(sut.accelerate, false) + XCTAssertEqual(target.accelerate, true) + } + + /// Given: A client configuration with random values. + /// When: A proxy configuration around it is created **without overrides**. + /// Then: The values returned by the proxy are equal to those from the **client configuration**. + func testPropertyBypass() async throws { + let target = try await S3Client.S3ClientConfiguration( + accelerate: Bool.random(), + credentialsProvider: nil, + disableMultiRegionAccessPoints: Bool.random(), + endpoint: UUID().uuidString, + endpointResolver: nil, + forcePathStyle: Bool.random(), + frameworkMetadata: nil, + regionResolver: nil, + signingRegion: UUID().uuidString, + useArnRegion: Bool.random(), + useDualStack: Bool.random(), + useFIPS: Bool.random(), + useGlobalEndpoint: Bool.random() + ) + + var sut = S3ClientConfigurationProxy(target: target, accelerateOverride: nil) + XCTAssertEqual(sut.accelerate, target.accelerate) + XCTAssertEqual(sut.disableMultiRegionAccessPoints, target.disableMultiRegionAccessPoints) + XCTAssertEqual(sut.forcePathStyle, target.forcePathStyle) + XCTAssertEqual(sut.useArnRegion, target.useArnRegion) + XCTAssertEqual(sut.useDualStack, target.useDualStack) + XCTAssertEqual(sut.region, target.region) + XCTAssertEqual(sut.signingRegion, target.signingRegion) + XCTAssertEqual(sut.useFIPS, target.useFIPS) + XCTAssertEqual(sut.useGlobalEndpoint, target.useGlobalEndpoint) + XCTAssertEqual(sut.endpoint, target.endpoint) + + sut.region = UUID().uuidString + sut.signingRegion = UUID().uuidString + sut.useFIPS = !(sut.useFIPS ?? false) + sut.useDualStack = !(sut.useDualStack ?? false) + sut.endpoint = UUID().uuidString + + XCTAssertEqual(sut.accelerate, target.accelerate) + XCTAssertEqual(sut.disableMultiRegionAccessPoints, target.disableMultiRegionAccessPoints) + XCTAssertEqual(sut.forcePathStyle, target.forcePathStyle) + XCTAssertEqual(sut.useArnRegion, target.useArnRegion) + XCTAssertEqual(sut.useDualStack, target.useDualStack) + XCTAssertEqual(sut.region, target.region) + XCTAssertEqual(sut.signingRegion, target.signingRegion) + XCTAssertEqual(sut.useFIPS, target.useFIPS) + XCTAssertEqual(sut.useGlobalEndpoint, target.useGlobalEndpoint) + XCTAssertEqual(sut.endpoint, target.endpoint) + } +} diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Mocks/MockAWSS3PreSignedURLBuilder.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Mocks/MockAWSS3PreSignedURLBuilder.swift index 2d4b72c3fc..0664ef453b 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Mocks/MockAWSS3PreSignedURLBuilder.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Mocks/MockAWSS3PreSignedURLBuilder.swift @@ -19,8 +19,12 @@ final class MockAWSS3PreSignedURLBuilder { } extension MockAWSS3PreSignedURLBuilder: AWSS3PreSignedURLBuilderBehavior { - func getPreSignedURL(key: String, signingOperation: AWSS3SigningOperation, expires: Int64?) async throws -> URL { - interactions.append("\(#function) \(key) \(signingOperation) \(String(describing: expires))") - return try await getPreSignedURLHandler(key, signingOperation, expires) - } + func getPreSignedURL( + key: String, + signingOperation: AWSS3SigningOperation, + accelerate: Bool?, + expires: Int64?) async throws -> URL { + interactions.append("\(#function) \(key) \(signingOperation) \(String(describing: expires))") + return try await getPreSignedURLHandler(key, signingOperation, expires) + } } diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Mocks/MockAWSS3StorageService.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Mocks/MockAWSS3StorageService.swift index 820636abd7..d11d5bd499 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Mocks/MockAWSS3StorageService.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Mocks/MockAWSS3StorageService.swift @@ -59,8 +59,10 @@ public class MockAWSS3StorageService: AWSS3StorageServiceBehavior { interactions.append(#function) } - public func download(serviceKey: String, fileURL: URL?, onEvent: @escaping StorageServiceDownloadEventHandler) { + + public func download(serviceKey: String, fileURL: URL?, accelerate: Bool?, onEvent: @escaping StorageServiceDownloadEventHandler) { interactions.append(#function) + downloadCalled += 1 downloadServiceKey = serviceKey @@ -75,10 +77,14 @@ public class MockAWSS3StorageService: AWSS3StorageServiceBehavior { return URL(fileURLWithPath: NSTemporaryDirectory()) } - public func getPreSignedURL(serviceKey: String, signingOperation: AWSS3SigningOperation, expires: Int) async throws -> URL { - interactions.append("\(#function) \(serviceKey) \(signingOperation) \(expires)") - return try await getPreSignedURLHandler(serviceKey, signingOperation, expires) - } + public func getPreSignedURL( + serviceKey: String, + signingOperation: AWSS3SigningOperation, + accelerate: Bool?, + expires: Int) async throws -> URL { + interactions.append("\(#function) \(serviceKey) \(signingOperation) \(expires)") + return try await getPreSignedURLHandler(serviceKey, signingOperation, expires) + } var validateObjectExistenceHandler: (String) async throws -> Void = { _ in } @@ -91,6 +97,7 @@ public class MockAWSS3StorageService: AWSS3StorageServiceBehavior { uploadSource: UploadSource, contentType: String?, metadata: [String: String]?, + accelerate: Bool?, onEvent: @escaping StorageServiceUploadEventHandler) { interactions.append(#function) uploadCalled += 1 @@ -109,6 +116,7 @@ public class MockAWSS3StorageService: AWSS3StorageServiceBehavior { uploadSource: UploadSource, contentType: String?, metadata: [String: String]?, + accelerate: Bool?, onEvent: @escaping StorageServiceMultiPartUploadEventHandler) { interactions.append(#function) multiPartUploadCalled += 1 diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Service/Storage/AWSS3StorageServiceGetPreSignedURLBehaviorTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Service/Storage/AWSS3StorageServiceGetPreSignedURLBehaviorTests.swift index 400ddaa16d..4f725a5780 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Service/Storage/AWSS3StorageServiceGetPreSignedURLBehaviorTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Service/Storage/AWSS3StorageServiceGetPreSignedURLBehaviorTests.swift @@ -68,10 +68,11 @@ class AWSS3StorageServiceGetPreSignedURLBehaviorTests: XCTestCase { func testForGetObject() async throws { let url = try await systemUnderTest.getPreSignedURL(serviceKey: serviceKey, signingOperation: .getObject, + accelerate: nil, expires: expires) XCTAssertEqual(url, presignedURL) XCTAssertEqual(builder.interactions, [ - "getPreSignedURL(key:signingOperation:expires:) \(serviceKey ?? "") \(AWSS3SigningOperation.getObject) \(String(describing: expires))" + "getPreSignedURL(key:signingOperation:accelerate:expires:) \(serviceKey ?? "") \(AWSS3SigningOperation.getObject) \(String(describing: expires))" ]) } @@ -81,10 +82,11 @@ class AWSS3StorageServiceGetPreSignedURLBehaviorTests: XCTestCase { func testForPutObject() async throws { let url = try await systemUnderTest.getPreSignedURL(serviceKey: serviceKey, signingOperation: .putObject, + accelerate: nil, expires: expires) XCTAssertEqual(url, presignedURL) XCTAssertEqual(builder.interactions, [ - "getPreSignedURL(key:signingOperation:expires:) \(serviceKey ?? "") \(AWSS3SigningOperation.putObject) \(String(describing: expires))" + "getPreSignedURL(key:signingOperation:accelerate:expires:) \(serviceKey ?? "") \(AWSS3SigningOperation.putObject) \(String(describing: expires))" ]) } @@ -95,10 +97,11 @@ class AWSS3StorageServiceGetPreSignedURLBehaviorTests: XCTestCase { let operation = AWSS3SigningOperation.uploadPart(partNumber: 0, uploadId: UUID().uuidString) let url = try await systemUnderTest.getPreSignedURL(serviceKey: serviceKey, signingOperation: operation, + accelerate: nil, expires: expires) XCTAssertEqual(url, presignedURL) XCTAssertEqual(builder.interactions, [ - "getPreSignedURL(key:signingOperation:expires:) \(serviceKey ?? "") \(operation) \(String(describing: expires))" + "getPreSignedURL(key:signingOperation:accelerate:expires:) \(serviceKey ?? "") \(operation) \(String(describing: expires))" ]) } diff --git a/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginAccelerateIntegrationTests.swift b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginAccelerateIntegrationTests.swift new file mode 100644 index 0000000000..45087632de --- /dev/null +++ b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginAccelerateIntegrationTests.swift @@ -0,0 +1,100 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +@testable import Amplify +import AWSS3StoragePlugin +import var CommonCrypto.CC_MD5_DIGEST_LENGTH +import func CommonCrypto.CC_MD5 +import typealias CommonCrypto.CC_LONG + +class AWSS3StoragePluginAccelerateIntegrationTests: AWSS3StoragePluginTestBase { + + var useAccelerateEndpoint = false + + /// Given: A data object. + /// When: It's uploaded with acceleration turned-off explicity. + /// Then: The operation completes successfully. + func testUploadDataWithAccelerateDisabledExplicitly() async throws { + let key = UUID().uuidString + let data = try XCTUnwrap(key.data(using: .utf8)) + let task = Amplify.Storage.uploadData(key: key, + data: data, + options: .init(pluginOptions:["useAccelerateEndpoint": useAccelerateEndpoint])) + _ = try await task.value + try await Amplify.Storage.remove(key: key) + } + + /// Given: A data object. + /// When: It's uploaded with acceleration misconfigured. + /// Then: The operation fails. + func testUploadDataWithAccelerateDisabledExplicitlyToWrongType() async throws { + let key = UUID().uuidString + let data = try XCTUnwrap(key.data(using: .utf8)) + do { + let task = Amplify.Storage.uploadData(key: key, + data: data, + options: .init(pluginOptions:["useAccelerateEndpoint": "false"])) + _ = try await task.value + XCTFail("Expecting error from bogus useAccelerateEndpoint value type (String)") + try await Amplify.Storage.remove(key: key) + } catch { + XCTAssertNotNil(error) + } + } + + /// Given: A file. + /// When: It's uploaded with acceleration turned-off explicity. + /// Then: The operation completes successfully. + func testUploadFile() async throws { + let key = UUID().uuidString + let filePath = NSTemporaryDirectory() + key + ".tmp" + + let fileURL = URL(fileURLWithPath: filePath) + FileManager.default.createFile(atPath: filePath, contents: key.data(using: .utf8), attributes: nil) + defer { + try? FileManager.default.removeItem(at: fileURL) + } + + let task = Amplify.Storage.uploadFile(key: key, + local: fileURL, + options: .init(pluginOptions:["useAccelerateEndpoint": useAccelerateEndpoint])) + _ = try await task.value + try await Amplify.Storage.remove(key: key) + } + + /// Given: A large data object. + /// When: It's uploaded with acceleration turned-off explicity. + /// Then: The operation completes successfully. + func testUploadLargeData() async throws { + let key = UUID().uuidString + let task = Amplify.Storage.uploadData(key: key, + data: AWSS3StoragePluginTestBase.largeDataObject, + options: .init(pluginOptions:["useAccelerateEndpoint": useAccelerateEndpoint])) + _ = try await task.value + try await Amplify.Storage.remove(key: key) + } + + /// Given: An object in storage. + /// When: It's downloaded with acceleration turned-off explicity. + /// Then: The operation completes successfully with the data retrieved. + func testDownloadDataToMemory() async throws { + let key = UUID().uuidString + let data = try XCTUnwrap(key.data(using: .utf8)) + let uploadTask = Amplify.Storage.uploadData(key: key, + data: data, + options: .init(pluginOptions:["useAccelerateEndpoint": useAccelerateEndpoint])) + _ = try await uploadTask.value + + let downloadTask = Amplify.Storage.downloadData(key: key, + options: .init(pluginOptions:["useAccelerateEndpoint": useAccelerateEndpoint])) + let downloadedData = try await downloadTask.value + XCTAssertEqual(downloadedData, data) + + try await Amplify.Storage.remove(key: key) + } +} diff --git a/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.pbxproj b/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.pbxproj index 4865e77fbe..944c6db9d6 100644 --- a/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.pbxproj +++ b/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.pbxproj @@ -39,6 +39,7 @@ 681D7D792A4264D200F7C310 /* AWSCognitoAuthPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 681D7D782A4264D200F7C310 /* AWSCognitoAuthPlugin */; }; 681D7D7B2A4264D200F7C310 /* AWSS3StoragePlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 681D7D7A2A4264D200F7C310 /* AWSS3StoragePlugin */; }; 681D7D852A426FF500F7C310 /* amplifyconfiguration.json in Resources */ = {isa = PBXBuildFile; fileRef = D5C0382101A0E23943FDF4CB /* amplifyconfiguration.json */; }; + 565DF1702953BAEA000DCCF7 /* AWSS3StoragePluginAccelerateIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565DF16F2953BAEA000DCCF7 /* AWSS3StoragePluginAccelerateIntegrationTests.swift */; }; 681DFEB228E748270000C36A /* AsyncTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFEAF28E748270000C36A /* AsyncTesting.swift */; }; 681DFEB328E748270000C36A /* AsyncExpectation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFEB028E748270000C36A /* AsyncExpectation.swift */; }; 681DFEB428E748270000C36A /* XCTestCase+AsyncTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFEB128E748270000C36A /* XCTestCase+AsyncTesting.swift */; }; @@ -103,6 +104,7 @@ 681D7D392A42637700F7C310 /* StorageWatchApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StorageWatchApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 681D7D502A4263CA00F7C310 /* StorageWatchApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StorageWatchApp.entitlements; sourceTree = ""; }; 681D7D6C2A4263E500F7C310 /* AWSS3StoragePluginIntegrationTestsWatch.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AWSS3StoragePluginIntegrationTestsWatch.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 565DF16F2953BAEA000DCCF7 /* AWSS3StoragePluginAccelerateIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSS3StoragePluginAccelerateIntegrationTests.swift; sourceTree = ""; }; 681DFEAF28E748270000C36A /* AsyncTesting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncTesting.swift; sourceTree = ""; }; 681DFEB028E748270000C36A /* AsyncExpectation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncExpectation.swift; sourceTree = ""; }; 681DFEB128E748270000C36A /* XCTestCase+AsyncTesting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCTestCase+AsyncTesting.swift"; sourceTree = ""; }; @@ -254,6 +256,7 @@ children = ( 684FB0C128BEB44700C8A6EB /* Helpers */, 684FB08628BEAF8E00C8A6EB /* AWSS3StoragePluginAccessLevelTests.swift */, + 565DF16F2953BAEA000DCCF7 /* AWSS3StoragePluginAccelerateIntegrationTests.swift */, 684FB08428BEAF8E00C8A6EB /* AWSS3StoragePluginBasicIntegrationTests.swift */, 684FB08028BEAF8E00C8A6EB /* AWSS3StoragePluginConfigurationTests.swift */, 684FB08328BEAF8E00C8A6EB /* AWSS3StoragePluginNegativeTests.swift */, @@ -601,6 +604,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 565DF1702953BAEA000DCCF7 /* AWSS3StoragePluginAccelerateIntegrationTests.swift in Sources */, 684FB0C328BEB45600C8A6EB /* AuthSignInHelper.swift in Sources */, 681DFEB228E748270000C36A /* AsyncTesting.swift in Sources */, 68828E4828C2AAA6006E7C0A /* AWSS3StoragePluginGetDataResumabilityTests.swift in Sources */, diff --git a/Package.resolved b/Package.resolved index 2ef4d7901c..118315515c 100644 --- a/Package.resolved +++ b/Package.resolved @@ -21,7 +21,7 @@ { "identity" : "aws-crt-swift", "kind" : "remoteSourceControl", - "location" : "https://github.com/awslabs/aws-crt-swift", + "location" : "https://github.com/awslabs/aws-crt-swift.git", "state" : { "revision" : "6feec6c3787877807aa9a00fad09591b96752376", "version" : "0.6.1" @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/mattgallagher/CwlCatchException.git", "state" : { - "revision" : "3b123999de19bf04905bc1dfdb76f817b0f2cc00", - "version" : "2.1.2" + "revision" : "35f9e770f54ce62dd8526470f14c6e137cef3eea", + "version" : "2.1.1" } }, { @@ -50,14 +50,14 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/mattgallagher/CwlPreconditionTesting.git", "state" : { - "revision" : "a23ded2c91df9156628a6996ab4f347526f17b6b", - "version" : "2.1.2" + "revision" : "c21f7bab5ca8eee0a9998bbd17ca1d0eb45d4688", + "version" : "2.1.0" } }, { "identity" : "smithy-swift", "kind" : "remoteSourceControl", - "location" : "https://github.com/awslabs/smithy-swift", + "location" : "https://github.com/awslabs/smithy-swift.git", "state" : { "revision" : "7b28da158d92cd06a3549140d43b8fbcf64a94a6", "version" : "0.15.0" @@ -95,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-log.git", "state" : { - "revision" : "32e8d724467f8fe623624570367e3d50c5638e46", - "version" : "1.5.2" + "revision" : "6fe203dc33195667ce1759bf0182975e4653ba1c", + "version" : "1.4.4" } }, { @@ -104,8 +104,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/MaxDesiatov/XMLCoder.git", "state" : { - "revision" : "b1e944cbd0ef33787b13f639a5418d55b3bed501", - "version" : "0.17.1" + "revision" : "c438dad94f6a243b411b70a4b4bac54595064808", + "version" : "0.15.0" } } ],