-
Notifications
You must be signed in to change notification settings - Fork 202
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Storage): Refactor list objects API to include
path
(#3580)
* feat(Storage): Refactor list objects API to include `path` * working on review comments
- Loading branch information
Showing
6 changed files
with
201 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
73 changes: 73 additions & 0 deletions
73
AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Tasks/AWSS3StorageListObjectsTask.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// | ||
// Copyright Amazon.com Inc. or its affiliates. | ||
// All Rights Reserved. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
import Amplify | ||
import Foundation | ||
import AWSS3 | ||
import AWSPluginsCore | ||
|
||
protocol StorageListObjectsTask: AmplifyTaskExecution where Request == StorageListRequest, Success == StorageListResult, Failure == StorageError {} | ||
|
||
class AWSS3StorageListObjectsTask: StorageListObjectsTask, DefaultLogger { | ||
|
||
let request: StorageListRequest | ||
let storageConfiguration: AWSS3StoragePluginConfiguration | ||
let storageBehaviour: AWSS3StorageServiceBehavior | ||
|
||
init(_ request: StorageListRequest, | ||
storageConfiguration: AWSS3StoragePluginConfiguration, | ||
storageBehaviour: AWSS3StorageServiceBehavior) { | ||
self.request = request | ||
self.storageConfiguration = storageConfiguration | ||
self.storageBehaviour = storageBehaviour | ||
} | ||
|
||
var eventName: HubPayloadEventName { | ||
HubPayload.EventName.Storage.list | ||
} | ||
|
||
var eventNameCategoryType: CategoryType { | ||
.storage | ||
} | ||
|
||
func execute() async throws -> StorageListResult { | ||
guard let path = try await request.path?.resolvePath() else { | ||
throw StorageError.validation( | ||
"path", | ||
"`path` is required for removing an object", | ||
"Make sure that a valid `path` is passed for removing an object") | ||
} | ||
let input = ListObjectsV2Input(bucket: storageBehaviour.bucket, | ||
continuationToken: request.options.nextToken, | ||
delimiter: nil, | ||
maxKeys: Int(request.options.pageSize), | ||
prefix: path, | ||
startAfter: nil) | ||
do { | ||
let response = try await storageBehaviour.client.listObjectsV2(input: input) | ||
let contents: S3BucketContents = response.contents ?? [] | ||
let items = try contents.map { s3Object in | ||
guard let key = s3Object.key else { | ||
throw StorageError.unknown("Missing key in response") | ||
} | ||
return StorageListResult.Item( | ||
path: path, | ||
key: key, | ||
eTag: s3Object.eTag, | ||
lastModified: s3Object.lastModified) | ||
} | ||
return StorageListResult(items: items, nextToken: response.nextContinuationToken) | ||
} catch let error as StorageErrorConvertible { | ||
throw error.storageError | ||
} catch { | ||
throw StorageError.service( | ||
"Service error occurred.", | ||
"Please inspect the underlying error for more details.", | ||
error) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
...lugins/Storage/Tests/AWSS3StoragePluginTests/Tasks/AWSS3StorageListObjectsTaskTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
// | ||
// Copyright Amazon.com Inc. or its affiliates. | ||
// All Rights Reserved. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
import XCTest | ||
@testable import Amplify | ||
@testable import AmplifyTestCommon | ||
@testable import AWSPluginsCore | ||
@testable import AWSS3StoragePlugin | ||
@testable import AWSPluginsTestCommon | ||
import AWSS3 | ||
|
||
class AWSS3StorageListObjectsTaskTests: XCTestCase { | ||
|
||
/// - Given: A configured Storage List Objects Task with mocked service | ||
/// - When: AWSS3StorageListObjectsTask value is invoked | ||
/// - Then: A list of keys should be returned. | ||
func testListObjectsTaskSuccess() async throws { | ||
let serviceMock = MockAWSS3StorageService() | ||
let client = serviceMock.client as! MockS3Client | ||
client.listObjectsV2Handler = { input in | ||
return .init( | ||
contents: [ | ||
.init(eTag: "tag", key: "key", lastModified: Date()), | ||
.init(eTag: "tag", key: "key", lastModified: Date())], | ||
nextContinuationToken: "continuationToken" | ||
) | ||
} | ||
|
||
let request = StorageListRequest( | ||
path: StringStoragePath.fromString("/path"), options: .init()) | ||
let task = AWSS3StorageListObjectsTask( | ||
request, | ||
storageConfiguration: AWSS3StoragePluginConfiguration(), | ||
storageBehaviour: serviceMock) | ||
let value = try await task.value | ||
XCTAssertEqual(value.items.count, 2) | ||
XCTAssertEqual(value.nextToken, "continuationToken") | ||
XCTAssertEqual(value.items[0].eTag, "tag") | ||
XCTAssertEqual(value.items[0].key, "key") | ||
XCTAssertNotNil(value.items[0].lastModified) | ||
|
||
} | ||
|
||
/// - Given: A configured ListObjects Remove Task with mocked service, throwing `NoSuchKey` exception | ||
/// - When: AWSS3StorageListObjectsTask value is invoked | ||
/// - Then: A storage service error should be returned, with an underlying service error | ||
func testListObjectsTaskNoBucket() async throws { | ||
let serviceMock = MockAWSS3StorageService() | ||
let client = serviceMock.client as! MockS3Client | ||
client.listObjectsV2Handler = { input in | ||
throw AWSS3.NoSuchKey() | ||
} | ||
|
||
let request = StorageListRequest( | ||
path: StringStoragePath.fromString("/path"), options: .init()) | ||
let task = AWSS3StorageListObjectsTask( | ||
request, | ||
storageConfiguration: AWSS3StoragePluginConfiguration(), | ||
storageBehaviour: serviceMock) | ||
do { | ||
_ = try await task.value | ||
XCTFail("Task should throw an exception") | ||
} | ||
catch { | ||
guard let storageError = error as? StorageError, | ||
case .service(_, _, let underlyingError) = storageError else { | ||
XCTFail("Should throw a Storage service error, instead threw \(error)") | ||
return | ||
} | ||
XCTAssertTrue(underlyingError is AWSS3.NoSuchKey, | ||
"Underlying error should be NoSuchKey, instead got \(String(describing: underlyingError))") | ||
} | ||
} | ||
|
||
/// - Given: A configured Storage ListObjects Task with invalid path | ||
/// - When: AWSS3StorageListObjectsTask value is invoked | ||
/// - Then: A storage validation error should be returned | ||
func testListObjectsTaskWithInvalidPath() async throws { | ||
let serviceMock = MockAWSS3StorageService() | ||
|
||
let request = StorageListRequest( | ||
path: StringStoragePath.fromString("path"), options: .init()) | ||
let task = AWSS3StorageListObjectsTask( | ||
request, | ||
storageConfiguration: AWSS3StoragePluginConfiguration(), | ||
storageBehaviour: serviceMock) | ||
do { | ||
_ = try await task.value | ||
XCTFail("Task should throw an exception") | ||
} | ||
catch { | ||
guard let storageError = error as? StorageError, | ||
case .validation(let field, _, _, _) = storageError else { | ||
XCTFail("Should throw a storage validation error, instead threw \(error)") | ||
return | ||
} | ||
|
||
XCTAssertEqual(field, "path", "Field in error should be `path`") | ||
} | ||
} | ||
|
||
} |