From 5c6cbfdc134e1d38e5976c9cb4583c26c9fa2f87 Mon Sep 17 00:00:00 2001 From: Simon Pilkington Date: Fri, 15 Aug 2025 12:50:43 +1000 Subject: [PATCH 1/2] Apply standard formatting. --- .swift-format | 62 ++ .swiftformat | 10 - Package.swift | 31 +- ...tePrimaryKeyTable+DynamoDBTableAsync.swift | 209 +++-- ...itePrimaryKeyTable+bulkUpdateSupport.swift | 105 ++- ...CompositePrimaryKeyTable+deleteItems.swift | 53 +- ...moDBCompositePrimaryKeyTable+execute.swift | 236 +++--- ...oDBCompositePrimaryKeyTable+getItems.swift | 76 +- ...ePrimaryKeyTable+polymorphicGetItems.swift | 71 +- ...CompositePrimaryKeyTable+updateItems.swift | 465 +++++++---- .../AWSDynamoDBCompositePrimaryKeyTable.swift | 131 ++-- ...ojection+DynamoDBKeysProjectionAsync.swift | 90 ++- ...namoDBCompositePrimaryKeysProjection.swift | 30 +- .../DynamoDBTables/CompositePrimaryKey.swift | 28 +- .../CustomRowTypeIdentifier.swift | 18 +- .../DynamoDBClientProtocol.swift | 8 +- ...DBClientTypes_AttributeValue+Codable.swift | 5 +- .../DynamoDBCompositePrimaryKeyGSILogic.swift | 47 +- ...lobberVersionedItemWithHistoricalRow.swift | 44 +- ...yKeyTable+conditionallyInTransaction.swift | 92 ++- ...maryKeyTable+conditionallyUpdateItem.swift | 86 ++- .../DynamoDBCompositePrimaryKeyTable.swift | 126 +-- ...maryKeyTableHistoricalItemExtensions.swift | 115 +-- ...namoDBCompositePrimaryKeysProjection.swift | 34 +- Sources/DynamoDBTables/DynamoDBDecoder.swift | 15 +- Sources/DynamoDBTables/DynamoDBEncoder.swift | 15 +- .../InMemoryDataRepresentations.swift | 14 +- ...moDBCompositePrimaryKeyTable+execute.swift | 97 ++- ...namoDBCompositePrimaryKeyTable+query.swift | 141 ++-- ...oDBCompositePrimaryKeyTable+transact.swift | 73 +- ...DBCompositePrimaryKeyTable+transform.swift | 268 ++++--- ...moryDynamoDBCompositePrimaryKeyTable.swift | 32 +- ...oDBCompositePrimaryKeyTableWithIndex.swift | 273 +++++-- ...namoDBCompositePrimaryKeysProjection.swift | 46 +- ...BCompositePrimaryKeysProjectionStore.swift | 65 +- .../InternalKeyedDecodingContainer.swift | 25 +- .../InternalKeyedEncodingContainer.swift | 83 +- ...InternalSingleValueDecodingContainer.swift | 35 +- ...InternalSingleValueEncodingContainer.swift | 22 +- .../InternalUnkeyedDecodingContainer.swift | 18 +- .../InternalUnkeyedEncodingContainer.swift | 14 +- Sources/DynamoDBTables/Macros.swift | 24 +- .../PolymorphicOperationReturnType.swift | 32 +- .../PolymorphicWriteEntry.swift | 49 +- .../QueryInput+forSortKeyCondition.swift | 61 +- .../DynamoDBTables/RetryConfiguration.swift | 38 +- Sources/DynamoDBTables/RowWithIndex.swift | 30 +- .../DynamoDBTables/RowWithItemVersion.swift | 33 +- .../RowWithItemVersionProtocol.swift | 16 +- .../DynamoDBTables/Sequence+concurrency.swift | 36 +- ...encyDynamoDBCompositePrimaryKeyTable.swift | 224 ++++-- .../DynamoDBTables/String+DynamoDBKey.swift | 12 +- Sources/DynamoDBTables/TimeToLive.swift | 10 +- ...imeToLive+RowWithItemVersionProtocol.swift | 20 +- .../TypedDatabaseItemWithTimeToLive.swift | 46 +- .../DynamoDBTablesMacros/BaseEntryMacro.swift | 50 +- Sources/DynamoDBTablesMacros/Plugin.swift | 20 +- .../PolymorphicOperationReturnTypeMacro.swift | 89 ++- ...rphicTransactionConstraintEntryMacro.swift | 16 +- .../PolymorphicWriteEntryMacro.swift | 16 +- ...rVersionedItemWithHistoricalRowTests.swift | 42 +- ...eyTableHistoricalItemExtensionsTests.swift | 186 +++-- ...ynamoDBCompositePrimaryKeyTableTests.swift | 35 +- ...bleUpdateItemConditionallyAtKeyTests.swift | 157 ++-- .../DynamoDBEncoderDecoderTests.swift | 14 +- ...ynamoDBCompositePrimaryKeyTableTests.swift | 534 ++++++++----- ...ynamoDBCompositePrimaryKeyTableTests.swift | 110 ++- .../SmokeDynamoDBTestInput.swift | 141 ++-- .../SmokeDynamoDBTests.swift | 101 ++- .../String+DynamoDBKeyTests.swift | 15 +- .../TestConfiguration.swift | 3 +- ...Item+RowWithItemVersionProtocolTests.swift | 728 +++++++++++++----- 72 files changed, 4083 insertions(+), 2213 deletions(-) create mode 100644 .swift-format delete mode 100644 .swiftformat diff --git a/.swift-format b/.swift-format new file mode 100644 index 0000000..876414a --- /dev/null +++ b/.swift-format @@ -0,0 +1,62 @@ +{ + "version" : 1, + "indentation" : { + "spaces" : 4 + }, + "tabWidth" : 4, + "fileScopedDeclarationPrivacy" : { + "accessLevel" : "private" + }, + "spacesAroundRangeFormationOperators" : false, + "indentConditionalCompilationBlocks" : false, + "indentSwitchCaseLabels" : false, + "lineBreakAroundMultilineExpressionChainComponents" : false, + "lineBreakBeforeControlFlowKeywords" : false, + "lineBreakBeforeEachArgument" : true, + "lineBreakBeforeEachGenericRequirement" : true, + "lineLength" : 120, + "maximumBlankLines" : 1, + "respectsExistingLineBreaks" : true, + "prioritizeKeepingFunctionOutputTogether" : true, + "rules" : { + "AllPublicDeclarationsHaveDocumentation" : false, + "AlwaysUseLiteralForEmptyCollectionInit" : false, + "AlwaysUseLowerCamelCase" : false, + "AmbiguousTrailingClosureOverload" : true, + "BeginDocumentationCommentWithOneLineSummary" : false, + "DoNotUseSemicolons" : true, + "DontRepeatTypeInStaticProperties" : true, + "FileScopedDeclarationPrivacy" : true, + "FullyIndirectEnum" : true, + "GroupNumericLiterals" : true, + "IdentifiersMustBeASCII" : true, + "NeverForceUnwrap" : false, + "NeverUseForceTry" : false, + "NeverUseImplicitlyUnwrappedOptionals" : false, + "NoAccessLevelOnExtensionDeclaration" : true, + "NoAssignmentInExpressions" : true, + "NoBlockComments" : true, + "NoCasesWithOnlyFallthrough" : true, + "NoEmptyTrailingClosureParentheses" : true, + "NoLabelsInCasePatterns" : true, + "NoLeadingUnderscores" : false, + "NoParensAroundConditions" : true, + "NoVoidReturnOnFunctionSignature" : true, + "OmitExplicitReturns" : false, + "OneCasePerLine" : true, + "OneVariableDeclarationPerLine" : true, + "OnlyOneTrailingClosureArgument" : true, + "OrderedImports" : true, + "ReplaceForEachWithForLoop" : true, + "ReturnVoidInsteadOfEmptyTuple" : true, + "UseEarlyExits" : false, + "UseExplicitNilCheckInConditions" : false, + "UseLetInEveryBoundCaseVariable" : false, + "UseShorthandTypeNames" : true, + "UseSingleLinePropertyGetter" : false, + "UseSynthesizedInitializer" : false, + "UseTripleSlashForDocumentationComments" : true, + "UseWhereClausesInForLoops" : false, + "ValidateDocumentationComments" : false + } +} diff --git a/.swiftformat b/.swiftformat deleted file mode 100644 index 1ebc37a..0000000 --- a/.swiftformat +++ /dev/null @@ -1,10 +0,0 @@ -# Swift version ---swiftversion 6.1 - -# file options ---exclude .build - -# format options ---allman false ---closingparen same-line ---self insert diff --git a/Package.swift b/Package.swift index bbecab1..9bcff8b 100644 --- a/Package.swift +++ b/Package.swift @@ -37,33 +37,42 @@ let package = Package( products: [ .library( name: "DynamoDBTables", - targets: ["DynamoDBTables"]), + targets: ["DynamoDBTables"] + ) ], dependencies: [ .package(url: "https://github.com/awslabs/aws-sdk-swift.git", from: "1.0.0"), .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-metrics.git", "1.0.0" ..< "3.0.0"), + .package(url: "https://github.com/apple/swift-metrics.git", "1.0.0"..<"3.0.0"), .package(url: "https://github.com/apple/swift-syntax", from: "601.0.0"), .package(url: "https://github.com/tachyonics/smockable", from: "0.4.0"), ], targets: [ - .macro(name: "DynamoDBTablesMacros", dependencies: [ - .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), - .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), - ]), + .macro( + name: "DynamoDBTablesMacros", + dependencies: [ + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), + ] + ), .target( - name: "DynamoDBTables", dependencies: [ + name: "DynamoDBTables", + dependencies: [ .target(name: "DynamoDBTablesMacros"), .product(name: "Logging", package: "swift-log"), .product(name: "Metrics", package: "swift-metrics"), .product(name: "AWSDynamoDB", package: "aws-sdk-swift"), .product(name: "Smockable", package: "smockable"), ], - swiftSettings: swiftSettings), + swiftSettings: swiftSettings + ), .testTarget( - name: "DynamoDBTablesTests", dependencies: [ + name: "DynamoDBTablesTests", + dependencies: [ .target(name: "DynamoDBTables"), .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), ], - swiftSettings: swiftSettings), - ]) + swiftSettings: swiftSettings + ), + ] +) diff --git a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+DynamoDBTableAsync.swift b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+DynamoDBTableAsync.swift index f24afd9..42519ed 100644 --- a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+DynamoDBTableAsync.swift +++ b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+DynamoDBTableAsync.swift @@ -29,32 +29,36 @@ import Foundation import Logging /// DynamoDBTable conformance async functions -public extension GenericAWSDynamoDBCompositePrimaryKeyTable { - func insertItem(_ item: TypedTTLDatabaseItem) async throws { +extension GenericAWSDynamoDBCompositePrimaryKeyTable { + public func insertItem(_ item: TypedTTLDatabaseItem) async throws { let putItemInput = try getInputForInsert(item) try await putItem(forInput: putItemInput, withKey: item.compositePrimaryKey) } - func clobberItem(_ item: TypedTTLDatabaseItem) async throws { + public func clobberItem(_ item: TypedTTLDatabaseItem) async throws { let attributes = try getAttributes(forItem: item) - let putItemInput = AWSDynamoDB.PutItemInput(item: attributes, - tableName: targetTableName) + let putItemInput = AWSDynamoDB.PutItemInput( + item: attributes, + tableName: targetTableName + ) try await self.putItem(forInput: putItemInput, withKey: item.compositePrimaryKey) } - func updateItem( + public func updateItem( newItem: TypedTTLDatabaseItem, - existingItem: TypedTTLDatabaseItem) async throws - { + existingItem: TypedTTLDatabaseItem + ) async throws { let putItemInput = try getInputForUpdateItem(newItem: newItem, existingItem: existingItem) try await putItem(forInput: putItemInput, withKey: newItem.compositePrimaryKey) } - func getItem(forKey key: CompositePrimaryKey) async throws + public func getItem( + forKey key: CompositePrimaryKey + ) async throws -> TypedTTLDatabaseItem? { let getItemInput = try getInputForGetItem(forKey: key) @@ -80,44 +84,51 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } } - func deleteItem(forKey key: CompositePrimaryKey) async throws { + public func deleteItem(forKey key: CompositePrimaryKey) async throws { let deleteItemInput = try getInputForDeleteItem(forKey: key) self.logger.trace("dynamodb.deleteItem with key: \(key) and table name \(targetTableName)") _ = try await self.dynamodb.deleteItem(input: deleteItemInput) } - func deleteItem(existingItem: TypedTTLDatabaseItem) async throws { + public func deleteItem(existingItem: TypedTTLDatabaseItem) async throws { let deleteItemInput = try getInputForDeleteItem(existingItem: existingItem) - let logMessage = "dynamodb.deleteItem with key: \(existingItem.compositePrimaryKey), " + let logMessage = + "dynamodb.deleteItem with key: \(existingItem.compositePrimaryKey), " + " version \(existingItem.rowStatus.rowVersion) and table name \(targetTableName)" self.logger.trace("\(logMessage)") _ = try await self.dynamodb.deleteItem(input: deleteItemInput) } - func polymorphicQuery(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?) async throws + public func polymorphicQuery( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition? + ) async throws -> [ReturnedType] { - try await self.polymorphicPartialQuery(forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition, - exclusiveStartKey: nil) + try await self.polymorphicPartialQuery( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition, + exclusiveStartKey: nil + ) } // function to return a future with the results of a query call and all future paginated calls private func polymorphicPartialQuery( forPartitionKey partitionKey: String, sortKeyCondition: AttributeCondition?, - exclusiveStartKey: String?) async throws -> [ReturnedType] - { + exclusiveStartKey: String? + ) async throws -> [ReturnedType] { let paginatedItems: ([ReturnedType], String?) = - try await polymorphicQuery(forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition, - limit: nil, - scanIndexForward: true, - exclusiveStartKey: exclusiveStartKey) + try await polymorphicQuery( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition, + limit: nil, + scanIndexForward: true, + exclusiveStartKey: exclusiveStartKey + ) // if there are more items if let lastEvaluatedKey = paginatedItems.1 { @@ -125,7 +136,8 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { let partialResult: [ReturnedType] = try await self.polymorphicPartialQuery( forPartitionKey: partitionKey, sortKeyCondition: sortKeyCondition, - exclusiveStartKey: lastEvaluatedKey) + exclusiveStartKey: lastEvaluatedKey + ) // return the results from 'this' call and all later paginated calls return paginatedItems.0 + partialResult @@ -135,35 +147,46 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } } - func polymorphicQuery(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - exclusiveStartKey: String?) async throws + public func polymorphicQuery( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + exclusiveStartKey: String? + ) async throws -> (items: [ReturnedType], lastEvaluatedKey: String?) { - try await self.polymorphicQuery(forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition, - limit: limit, - scanIndexForward: true, - exclusiveStartKey: exclusiveStartKey) + try await self.polymorphicQuery( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition, + limit: limit, + scanIndexForward: true, + exclusiveStartKey: exclusiveStartKey + ) } - func polymorphicQuery(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - scanIndexForward: Bool, - exclusiveStartKey: String?) async throws + public func polymorphicQuery( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + scanIndexForward: Bool, + exclusiveStartKey: String? + ) async throws -> (items: [ReturnedType], lastEvaluatedKey: String?) { - let queryInput = try AWSDynamoDB.QueryInput.forSortKeyCondition(partitionKey: partitionKey, targetTableName: targetTableName, - primaryKeyType: ReturnedType.AttributesType.self, - sortKeyCondition: sortKeyCondition, limit: limit, - scanIndexForward: scanIndexForward, - exclusiveStartKey: exclusiveStartKey, - consistentRead: self.tableConfiguration.consistentRead) - - let logMessage = "dynamodb.query with partitionKey: \(partitionKey), " + - "sortKeyCondition: \(sortKeyCondition.debugDescription), and table name \(targetTableName)." + let queryInput = try AWSDynamoDB.QueryInput.forSortKeyCondition( + partitionKey: partitionKey, + targetTableName: targetTableName, + primaryKeyType: ReturnedType.AttributesType.self, + sortKeyCondition: sortKeyCondition, + limit: limit, + scanIndexForward: scanIndexForward, + exclusiveStartKey: exclusiveStartKey, + consistentRead: self.tableConfiguration.consistentRead + ) + + let logMessage = + "dynamodb.query with partitionKey: \(partitionKey), " + + "sortKeyCondition: \(sortKeyCondition.debugDescription), and table name \(targetTableName)." self.logger.trace("\(logMessage)") let queryOutput = try await self.dynamodb.query(input: queryInput) @@ -204,18 +227,21 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } } - private func putItem(forInput putItemInput: AWSDynamoDB.PutItemInput, - withKey compositePrimaryKey: CompositePrimaryKey) async throws - { + private func putItem( + forInput putItemInput: AWSDynamoDB.PutItemInput, + withKey compositePrimaryKey: CompositePrimaryKey + ) async throws { let logMessage = "dynamodb.putItem with item: \(putItemInput) and table name \(targetTableName)." self.logger.trace("\(logMessage)") do { _ = try await self.dynamodb.putItem(input: putItemInput) } catch let error as ConditionalCheckFailedException { - throw DynamoDBTableError.conditionalCheckFailed(partitionKey: compositePrimaryKey.partitionKey, - sortKey: compositePrimaryKey.sortKey, - message: error.message) + throw DynamoDBTableError.conditionalCheckFailed( + partitionKey: compositePrimaryKey.partitionKey, + sortKey: compositePrimaryKey.sortKey, + message: error.message + ) } catch { self.logger.warning("Error from AWSDynamoDBTable: \(error)") @@ -223,35 +249,43 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } } - func query(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?) async throws + public func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition? + ) async throws -> [TypedTTLDatabaseItem] { - try await self.partialQuery(forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition, - exclusiveStartKey: nil) + try await self.partialQuery( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition, + exclusiveStartKey: nil + ) } // function to return a future with the results of a query call and all future paginated calls private func partialQuery( forPartitionKey partitionKey: String, sortKeyCondition: AttributeCondition?, - exclusiveStartKey _: String?) async throws -> [TypedTTLDatabaseItem] - { + exclusiveStartKey _: String? + ) async throws -> [TypedTTLDatabaseItem] { let paginatedItems: ([TypedTTLDatabaseItem], String?) = - try await query(forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition, - limit: nil, - scanIndexForward: true, - exclusiveStartKey: nil) + try await query( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition, + limit: nil, + scanIndexForward: true, + exclusiveStartKey: nil + ) // if there are more items if let lastEvaluatedKey = paginatedItems.1 { // returns a future with all the results from all later paginated calls - let partialResult: [TypedTTLDatabaseItem] = try await self.partialQuery( - forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition, - exclusiveStartKey: lastEvaluatedKey) + let partialResult: [TypedTTLDatabaseItem] = + try await self.partialQuery( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition, + exclusiveStartKey: lastEvaluatedKey + ) // return the results from 'this' call and all later paginated calls return paginatedItems.0 + partialResult @@ -261,22 +295,31 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } } - func query(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - scanIndexForward: Bool, - exclusiveStartKey: String?) async throws - -> (items: [TypedTTLDatabaseItem], lastEvaluatedKey: String?) + public func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + scanIndexForward: Bool, + exclusiveStartKey: String? + ) async throws + -> ( + items: [TypedTTLDatabaseItem], lastEvaluatedKey: String? + ) { let queryInput = try AWSDynamoDB.QueryInput.forSortKeyCondition( - partitionKey: partitionKey, targetTableName: targetTableName, + partitionKey: partitionKey, + targetTableName: targetTableName, primaryKeyType: AttributesType.self, - sortKeyCondition: sortKeyCondition, limit: limit, - scanIndexForward: scanIndexForward, exclusiveStartKey: exclusiveStartKey, - consistentRead: self.tableConfiguration.consistentRead) - - let logMessage = "dynamodb.query with partitionKey: \(partitionKey), " + - "sortKeyCondition: \(sortKeyCondition.debugDescription), and table name \(targetTableName)." + sortKeyCondition: sortKeyCondition, + limit: limit, + scanIndexForward: scanIndexForward, + exclusiveStartKey: exclusiveStartKey, + consistentRead: self.tableConfiguration.consistentRead + ) + + let logMessage = + "dynamodb.query with partitionKey: \(partitionKey), " + + "sortKeyCondition: \(sortKeyCondition.debugDescription), and table name \(targetTableName)." self.logger.trace("\(logMessage)") let queryOutput = try await self.dynamodb.query(input: queryInput) diff --git a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+bulkUpdateSupport.swift b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+bulkUpdateSupport.swift index f60a4e5..3078272 100644 --- a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+bulkUpdateSupport.swift +++ b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+bulkUpdateSupport.swift @@ -35,17 +35,19 @@ enum AttributeDifference: Equatable { var path: String { switch self { - case .update(path: let path, value: _): + case .update(let path, value: _): path case let .remove(path: path): path - case .listAppend(path: let path, value: _): + case .listAppend(let path, value: _): path } } } -func getAttributes(forItem item: TypedTTLDatabaseItem) throws +func getAttributes( + forItem item: TypedTTLDatabaseItem +) throws -> [String: DynamoDBClientTypes.AttributeValue] { let attributeValue = try DynamoDBEncoder().encode(item) @@ -64,10 +66,12 @@ extension GenericAWSDynamoDBCompositePrimaryKeyTable { func getUpdateExpression( tableName: String, newItem: TypedTTLDatabaseItem, - existingItem: TypedTTLDatabaseItem) throws -> String - { - let attributeDifferences = try diffItems(newItem: newItem, - existingItem: existingItem) + existingItem: TypedTTLDatabaseItem + ) throws -> String { + let attributeDifferences = try diffItems( + newItem: newItem, + existingItem: existingItem + ) // according to https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ql-reference.update.html let elements = attributeDifferences.map { attributeDifference -> String in @@ -89,9 +93,10 @@ extension GenericAWSDynamoDBCompositePrimaryKeyTable { + "AND \(RowStatus.CodingKeys.rowVersion.rawValue)=\(existingItem.rowStatus.rowVersion)" } - func getInsertExpression(tableName: String, - newItem: TypedTTLDatabaseItem) throws -> String - { + func getInsertExpression( + tableName: String, + newItem: TypedTTLDatabaseItem + ) throws -> String { let newAttributes = try getAttributes(forItem: newItem) let flattenedAttribute = try getFlattenedMapAttribute(attribute: newAttributes) @@ -99,26 +104,29 @@ extension GenericAWSDynamoDBCompositePrimaryKeyTable { return "INSERT INTO \"\(tableName)\" value \(flattenedAttribute)" } - func getDeleteExpression(tableName: String, - existingItem: TypedTTLDatabaseItem) throws -> String - { + func getDeleteExpression( + tableName: String, + existingItem: TypedTTLDatabaseItem + ) throws -> String { "DELETE FROM \"\(tableName)\" " + "WHERE \(AttributesType.partitionKeyAttributeName)='\(self.sanitizeString(existingItem.compositePrimaryKey.partitionKey))' " + "AND \(AttributesType.sortKeyAttributeName)='\(self.sanitizeString(existingItem.compositePrimaryKey.sortKey))' " + "AND \(RowStatus.CodingKeys.rowVersion.rawValue)=\(existingItem.rowStatus.rowVersion)" } - func getDeleteExpression(tableName: String, - existingKey: CompositePrimaryKey) throws -> String - { + func getDeleteExpression( + tableName: String, + existingKey: CompositePrimaryKey + ) throws -> String { "DELETE FROM \"\(tableName)\" " + "WHERE \(AttributesType.partitionKeyAttributeName)='\(self.sanitizeString(existingKey.partitionKey))' " + "AND \(AttributesType.sortKeyAttributeName)='\(self.sanitizeString(existingKey.sortKey))'" } - func getExistsExpression(tableName: String, - existingItem: TypedTTLDatabaseItem) -> String - { + func getExistsExpression( + tableName: String, + existingItem: TypedTTLDatabaseItem + ) -> String { // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ql-functions.exists.html "EXISTS(" + "SELECT * FROM \"\(tableName)\" " @@ -134,18 +142,19 @@ extension GenericAWSDynamoDBCompositePrimaryKeyTable { */ func diffItems( newItem: TypedTTLDatabaseItem, - existingItem: TypedTTLDatabaseItem) throws -> [AttributeDifference] - { + existingItem: TypedTTLDatabaseItem + ) throws -> [AttributeDifference] { let newAttributes = try getAttributes(forItem: newItem) let existingAttributes = try getAttributes(forItem: existingItem) return try self.diffMapAttribute(path: nil, newAttribute: newAttributes, existingAttribute: existingAttributes) } - private func diffAttribute(path: String, - newAttribute: DynamoDBClientTypes.AttributeValue, - existingAttribute: DynamoDBClientTypes.AttributeValue) throws -> [AttributeDifference] - { + private func diffAttribute( + path: String, + newAttribute: DynamoDBClientTypes.AttributeValue, + existingAttribute: DynamoDBClientTypes.AttributeValue + ) throws -> [AttributeDifference] { switch (newAttribute, existingAttribute) { case (.b, .b): throw DynamoDBTableError.unableToUpdateError(reason: "Unable to handle Binary types.") @@ -156,9 +165,17 @@ extension GenericAWSDynamoDBCompositePrimaryKeyTable { case (.bs, .bs): throw DynamoDBTableError.unableToUpdateError(reason: "Unable to handle Binary Set types.") case let (.l(newTypedAttribute), .l(existingTypedAttribute)): - return try self.diffListAttribute(path: path, newAttribute: newTypedAttribute, existingAttribute: existingTypedAttribute) + return try self.diffListAttribute( + path: path, + newAttribute: newTypedAttribute, + existingAttribute: existingTypedAttribute + ) case let (.m(newTypedAttribute), .m(existingTypedAttribute)): - return try self.diffMapAttribute(path: path, newAttribute: newTypedAttribute, existingAttribute: existingTypedAttribute) + return try self.diffMapAttribute( + path: path, + newAttribute: newTypedAttribute, + existingAttribute: existingTypedAttribute + ) case let (.n(newTypedAttribute), .n(existingTypedAttribute)): if newTypedAttribute != existingTypedAttribute { return [.update(path: path, value: String(newTypedAttribute))] @@ -183,18 +200,23 @@ extension GenericAWSDynamoDBCompositePrimaryKeyTable { return [] } - private func diffListAttribute(path: String, - newAttribute: [DynamoDBClientTypes.AttributeValue], - existingAttribute: [DynamoDBClientTypes.AttributeValue]) throws -> [AttributeDifference] - { + private func diffListAttribute( + path: String, + newAttribute: [DynamoDBClientTypes.AttributeValue], + existingAttribute: [DynamoDBClientTypes.AttributeValue] + ) throws -> [AttributeDifference] { let maxIndex = max(newAttribute.count, existingAttribute.count) - return try (0 ..< maxIndex).flatMap { index -> [AttributeDifference] in + return try (0.. [AttributeDifference] in let newPath = "\(path)[\(index)]" // if both new and existing attributes are present if index < newAttribute.count, index < existingAttribute.count { - return try self.diffAttribute(path: newPath, newAttribute: newAttribute[index], existingAttribute: existingAttribute[index]) + return try self.diffAttribute( + path: newPath, + newAttribute: newAttribute[index], + existingAttribute: existingAttribute[index] + ) } else if index < existingAttribute.count { return [.remove(path: newPath)] } else if index < newAttribute.count { @@ -205,11 +227,13 @@ extension GenericAWSDynamoDBCompositePrimaryKeyTable { } } - private func diffMapAttribute(path: String?, - newAttribute: [String: DynamoDBClientTypes.AttributeValue], - existingAttribute: [String: DynamoDBClientTypes.AttributeValue]) throws -> [AttributeDifference] - { - var combinedMap: [String: (new: DynamoDBClientTypes.AttributeValue?, existing: DynamoDBClientTypes.AttributeValue?)] = [:] + private func diffMapAttribute( + path: String?, + newAttribute: [String: DynamoDBClientTypes.AttributeValue], + existingAttribute: [String: DynamoDBClientTypes.AttributeValue] + ) throws -> [AttributeDifference] { + var combinedMap: + [String: (new: DynamoDBClientTypes.AttributeValue?, existing: DynamoDBClientTypes.AttributeValue?)] = [:] for (key, attribute) in newAttribute { var existingEntry = combinedMap[key] ?? (nil, nil) @@ -247,7 +271,10 @@ extension GenericAWSDynamoDBCompositePrimaryKeyTable { } } - private func updateAttribute(newPath: String, attribute: DynamoDBClientTypes.AttributeValue) throws -> [AttributeDifference] { + private func updateAttribute( + newPath: String, + attribute: DynamoDBClientTypes.AttributeValue + ) throws -> [AttributeDifference] { if let newValue = try getFlattenedAttribute(attribute: attribute) { [.update(path: newPath, value: newValue)] } else { diff --git a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+deleteItems.swift b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+deleteItems.swift index 22fcc18..c44ede6 100644 --- a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+deleteItems.swift +++ b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+deleteItems.swift @@ -32,18 +32,25 @@ import Logging private let maximumUpdatesPerExecuteStatement = 25 /// DynamoDBTable conformance updateItems function -public extension GenericAWSDynamoDBCompositePrimaryKeyTable { - private func deleteChunkedItems(_ keys: [CompositePrimaryKey]) async throws -> [DynamoDBClientTypes.BatchStatementResponse] { +extension GenericAWSDynamoDBCompositePrimaryKeyTable { + private func deleteChunkedItems( + _ keys: [CompositePrimaryKey] + ) async throws -> [DynamoDBClientTypes.BatchStatementResponse] { // if there are no keys, there is nothing to update guard keys.count > 0 else { return [] } let statements = try keys.map { existingKey -> DynamoDBClientTypes.BatchStatementRequest in - let statement = try getDeleteExpression(tableName: self.targetTableName, - existingKey: existingKey) - - return DynamoDBClientTypes.BatchStatementRequest(consistentRead: self.tableConfiguration.consistentRead, statement: statement) + let statement = try getDeleteExpression( + tableName: self.targetTableName, + existingKey: existingKey + ) + + return DynamoDBClientTypes.BatchStatementRequest( + consistentRead: self.tableConfiguration.consistentRead, + statement: statement + ) } let executeInput = BatchExecuteStatementInput(statements: statements) @@ -52,7 +59,9 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { return response.responses ?? [] } - private func deleteChunkedItems(_ existingItems: [TypedTTLDatabaseItem]) async throws + private func deleteChunkedItems( + _ existingItems: [TypedTTLDatabaseItem] + ) async throws -> [DynamoDBClientTypes.BatchStatementResponse] { // if there are no items, there is nothing to update @@ -61,10 +70,15 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } let statements = try existingItems.map { existingItem -> DynamoDBClientTypes.BatchStatementRequest in - let statement = try getDeleteExpression(tableName: self.targetTableName, - existingItem: existingItem) - - return DynamoDBClientTypes.BatchStatementRequest(consistentRead: self.tableConfiguration.consistentRead, statement: statement) + let statement = try getDeleteExpression( + tableName: self.targetTableName, + existingItem: existingItem + ) + + return DynamoDBClientTypes.BatchStatementRequest( + consistentRead: self.tableConfiguration.consistentRead, + statement: statement + ) } let executeInput = BatchExecuteStatementInput(statements: statements) @@ -73,7 +87,7 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { return response.responses ?? [] } - func deleteItems(forKeys keys: [CompositePrimaryKey]) async throws { + public func deleteItems(forKeys keys: [CompositePrimaryKey]) async throws { // BatchExecuteStatement has a maximum of 25 statements // This function handles pagination internally. let chunkedKeys = keys.chunked(by: maximumUpdatesPerExecuteStatement) @@ -84,7 +98,11 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } let errors = zippedResponses.compactMap { response, key in - response.error?.asDynamoDBTableError(partitionKey: key.partitionKey, sortKey: key.sortKey, entryCount: keys.count) + response.error?.asDynamoDBTableError( + partitionKey: key.partitionKey, + sortKey: key.sortKey, + entryCount: keys.count + ) } if !errors.isEmpty { @@ -92,7 +110,7 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } } - func deleteItems(existingItems: [TypedTTLDatabaseItem]) async throws { + public func deleteItems(existingItems: [TypedTTLDatabaseItem]) async throws { // BatchExecuteStatement has a maximum of 25 statements // This function handles pagination internally. let chunkedItems = existingItems.chunked(by: maximumUpdatesPerExecuteStatement) @@ -103,8 +121,11 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } let errors = zippedResponses.compactMap { response, item in - response.error?.asDynamoDBTableError(partitionKey: item.compositePrimaryKey.partitionKey, - sortKey: item.compositePrimaryKey.sortKey, entryCount: existingItems.count) + response.error?.asDynamoDBTableError( + partitionKey: item.compositePrimaryKey.partitionKey, + sortKey: item.compositePrimaryKey.sortKey, + entryCount: existingItems.count + ) } if !errors.isEmpty { diff --git a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift index 41a6c23..f4326c4 100644 --- a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift +++ b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift @@ -32,35 +32,40 @@ import Logging private let maximumKeysPerExecuteStatement = 50 /// DynamoDBTable conformance execute function -public extension GenericAWSDynamoDBCompositePrimaryKeyTable { - private func getStatement(partitionKeys: [String], - attributesFilter: [String]?, - partitionKeyAttributeName: String, - additionalWhereClause: String?) -> String - { +extension GenericAWSDynamoDBCompositePrimaryKeyTable { + private func getStatement( + partitionKeys: [String], + attributesFilter: [String]?, + partitionKeyAttributeName: String, + additionalWhereClause: String? + ) -> String { let attributesFilterString = attributesFilter?.joined(separator: ", ") ?? "*" - let partitionWhereClause = if partitionKeys.count == 1 { - "\(partitionKeyAttributeName)='\(partitionKeys[0])'" - } else { - "\(partitionKeyAttributeName) IN ['\(partitionKeys.joined(separator: "', '"))']" - } + let partitionWhereClause = + if partitionKeys.count == 1 { + "\(partitionKeyAttributeName)='\(partitionKeys[0])'" + } else { + "\(partitionKeyAttributeName) IN ['\(partitionKeys.joined(separator: "', '"))']" + } - let whereClausePostfix = if let additionalWhereClause { - " \(additionalWhereClause)" - } else { - "" - } + let whereClausePostfix = + if let additionalWhereClause { + " \(additionalWhereClause)" + } else { + "" + } return """ - SELECT \(attributesFilterString) FROM "\(self.targetTableName)" WHERE \(partitionWhereClause)\(whereClausePostfix) - """ + SELECT \(attributesFilterString) FROM "\(self.targetTableName)" WHERE \(partitionWhereClause)\(whereClausePostfix) + """ } - func polymorphicExecute( + public func polymorphicExecute( partitionKeys: [String], attributesFilter: [String]?, - additionalWhereClause: String?, nextToken: String?) async throws + additionalWhereClause: String?, + nextToken: String? + ) async throws -> (items: [ReturnedType], lastEvaluatedKey: String?) { // if there are no partitions, there will be no results to return @@ -72,15 +77,25 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { // ExecuteStatement API has a maximum limit on the number of decomposed read operations per request. // Caller of this function needs to handle pagination on their side. guard partitionKeys.count <= maximumKeysPerExecuteStatement else { - throw DynamoDBTableError.validation(partitionKey: nil, sortKey: nil, - message: "Execute API has a maximum limit of \(maximumKeysPerExecuteStatement) partition keys per request.") + throw DynamoDBTableError.validation( + partitionKey: nil, + sortKey: nil, + message: + "Execute API has a maximum limit of \(maximumKeysPerExecuteStatement) partition keys per request." + ) } - let statement = self.getStatement(partitionKeys: partitionKeys, - attributesFilter: attributesFilter, - partitionKeyAttributeName: ReturnedType.AttributesType.partitionKeyAttributeName, - additionalWhereClause: additionalWhereClause) - let executeInput = ExecuteStatementInput(consistentRead: self.tableConfiguration.consistentRead, nextToken: nextToken, statement: statement) + let statement = self.getStatement( + partitionKeys: partitionKeys, + attributesFilter: attributesFilter, + partitionKeyAttributeName: ReturnedType.AttributesType.partitionKeyAttributeName, + additionalWhereClause: additionalWhereClause + ) + let executeInput = ExecuteStatementInput( + consistentRead: self.tableConfiguration.consistentRead, + nextToken: nextToken, + statement: statement + ) let executeOutput = try await self.dynamodb.executeStatement(input: executeInput) @@ -107,30 +122,37 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } } - func polymorphicExecute( + public func polymorphicExecute( partitionKeys: [String], attributesFilter: [String]?, - additionalWhereClause: String?) async throws + additionalWhereClause: String? + ) async throws -> [ReturnedType] { // ExecuteStatement API has a maximum limit on the number of decomposed read operations per request. // This function handles pagination internally. let chunkedPartitionKeys = partitionKeys.chunked(by: maximumKeysPerExecuteStatement) let itemLists = try await chunkedPartitionKeys.concurrentMap { chunk -> [ReturnedType] in - try await self.polymorphicPartialExecute(partitionKeys: chunk, - attributesFilter: attributesFilter, - additionalWhereClause: additionalWhereClause, - nextToken: nil) + try await self.polymorphicPartialExecute( + partitionKeys: chunk, + attributesFilter: attributesFilter, + additionalWhereClause: additionalWhereClause, + nextToken: nil + ) } return itemLists.flatMap(\.self) } - func execute( + public func execute( partitionKeys: [String], attributesFilter: [String]?, - additionalWhereClause: String?, nextToken: String?) async throws - -> (items: [TypedTTLDatabaseItem], lastEvaluatedKey: String?) + additionalWhereClause: String?, + nextToken: String? + ) async throws + -> ( + items: [TypedTTLDatabaseItem], lastEvaluatedKey: String? + ) { // if there are no partitions, there will be no results to return // succeed immediately with empty results @@ -141,15 +163,25 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { // ExecuteStatement API has a maximum limit on the number of decomposed read operations per request. // Caller of this function needs to handle pagination on their side. guard partitionKeys.count <= maximumKeysPerExecuteStatement else { - throw DynamoDBTableError.validation(partitionKey: nil, sortKey: nil, - message: "Execute API has a maximum limit of \(maximumKeysPerExecuteStatement) partition keys per request.") + throw DynamoDBTableError.validation( + partitionKey: nil, + sortKey: nil, + message: + "Execute API has a maximum limit of \(maximumKeysPerExecuteStatement) partition keys per request." + ) } - let statement = self.getStatement(partitionKeys: partitionKeys, - attributesFilter: attributesFilter, - partitionKeyAttributeName: AttributesType.partitionKeyAttributeName, - additionalWhereClause: additionalWhereClause) - let executeInput = ExecuteStatementInput(consistentRead: self.tableConfiguration.consistentRead, nextToken: nextToken, statement: statement) + let statement = self.getStatement( + partitionKeys: partitionKeys, + attributesFilter: attributesFilter, + partitionKeyAttributeName: AttributesType.partitionKeyAttributeName, + additionalWhereClause: additionalWhereClause + ) + let executeInput = ExecuteStatementInput( + consistentRead: self.tableConfiguration.consistentRead, + nextToken: nextToken, + statement: statement + ) let executeOutput = try await self.dynamodb.executeStatement(input: executeInput) @@ -174,21 +206,25 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } } - func execute( + public func execute( partitionKeys: [String], attributesFilter: [String]?, - additionalWhereClause: String?) async throws + additionalWhereClause: String? + ) async throws -> [TypedTTLDatabaseItem] { // ExecuteStatement API has a maximum limit on the number of decomposed read operations per request. // This function handles pagination internally. let chunkedPartitionKeys = partitionKeys.chunked(by: maximumKeysPerExecuteStatement) - let itemLists = try await chunkedPartitionKeys.concurrentMap { chunk - -> [TypedTTLDatabaseItem] in - try await self.partialExecute(partitionKeys: chunk, - attributesFilter: attributesFilter, - additionalWhereClause: additionalWhereClause, - nextToken: nil) + let itemLists = try await chunkedPartitionKeys.concurrentMap { + chunk + -> [TypedTTLDatabaseItem] in + try await self.partialExecute( + partitionKeys: chunk, + attributesFilter: attributesFilter, + additionalWhereClause: additionalWhereClause, + nextToken: nil + ) } return itemLists.flatMap(\.self) @@ -199,22 +235,27 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { partitionKeys: [String], attributesFilter: [String]?, additionalWhereClause: String?, - nextToken: String?) async throws + nextToken: String? + ) async throws -> [ReturnedType] { let paginatedItems: ([ReturnedType], String?) = - try await polymorphicExecute(partitionKeys: partitionKeys, - attributesFilter: attributesFilter, - additionalWhereClause: additionalWhereClause, - nextToken: nextToken) + try await polymorphicExecute( + partitionKeys: partitionKeys, + attributesFilter: attributesFilter, + additionalWhereClause: additionalWhereClause, + nextToken: nextToken + ) // if there are more items if let returnedNextToken = paginatedItems.1 { // returns a future with all the results from all later paginated calls - let partialResult: [ReturnedType] = try await self.polymorphicPartialExecute(partitionKeys: partitionKeys, - attributesFilter: attributesFilter, - additionalWhereClause: additionalWhereClause, - nextToken: returnedNextToken) + let partialResult: [ReturnedType] = try await self.polymorphicPartialExecute( + partitionKeys: partitionKeys, + attributesFilter: attributesFilter, + additionalWhereClause: additionalWhereClause, + nextToken: returnedNextToken + ) // return the results from 'this' call and all later paginated calls return paginatedItems.0 + partialResult @@ -228,23 +269,28 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { partitionKeys: [String], attributesFilter: [String]?, additionalWhereClause: String?, - nextToken: String?) async throws + nextToken: String? + ) async throws -> [TypedTTLDatabaseItem] { let paginatedItems: ([TypedTTLDatabaseItem], String?) = - try await execute(partitionKeys: partitionKeys, - attributesFilter: attributesFilter, - additionalWhereClause: additionalWhereClause, - nextToken: nextToken) + try await execute( + partitionKeys: partitionKeys, + attributesFilter: attributesFilter, + additionalWhereClause: additionalWhereClause, + nextToken: nextToken + ) // if there are more items if let returnedNextToken = paginatedItems.1 { // returns a future with all the results from all later paginated calls - let partialResult: [TypedTTLDatabaseItem] = try await self.partialExecute( - partitionKeys: partitionKeys, - attributesFilter: attributesFilter, - additionalWhereClause: additionalWhereClause, - nextToken: returnedNextToken) + let partialResult: [TypedTTLDatabaseItem] = + try await self.partialExecute( + partitionKeys: partitionKeys, + attributesFilter: attributesFilter, + additionalWhereClause: additionalWhereClause, + nextToken: returnedNextToken + ) // return the results from 'this' call and all later paginated calls return paginatedItems.0 + partialResult @@ -266,34 +312,51 @@ extension DynamoDBClientTypes.BatchStatementError { case .accessdenied: DynamoDBTableError.accessDenied(message: self.message) case .conditionalcheckfailed: - DynamoDBTableError.conditionalCheckFailed(partitionKey: partitionKey, - sortKey: sortKey, - message: self.message) + DynamoDBTableError.conditionalCheckFailed( + partitionKey: partitionKey, + sortKey: sortKey, + message: self.message + ) case .duplicateitem: - DynamoDBTableError.duplicateItem(partitionKey: partitionKey, sortKey: sortKey, - message: self.message) + DynamoDBTableError.duplicateItem( + partitionKey: partitionKey, + sortKey: sortKey, + message: self.message + ) case .internalservererror: DynamoDBTableError.internalServerError(message: self.message) case .itemcollectionsizelimitexceeded: - DynamoDBTableError.itemCollectionSizeLimitExceeded(attemptedSize: entryCount, - maximumSize: AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement) + DynamoDBTableError.itemCollectionSizeLimitExceeded( + attemptedSize: entryCount, + maximumSize: AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement + ) case .provisionedthroughputexceeded: DynamoDBTableError.provisionedThroughputExceeded(message: self.message) case .requestlimitexceeded: DynamoDBTableError.requestLimitExceeded(message: self.message) case .resourcenotfound: - DynamoDBTableError.resourceNotFound(partitionKey: partitionKey, sortKey: sortKey, - message: self.message) + DynamoDBTableError.resourceNotFound( + partitionKey: partitionKey, + sortKey: sortKey, + message: self.message + ) case .throttlingerror: DynamoDBTableError.throttling(message: self.message) case .transactionconflict: DynamoDBTableError.transactionConflict(message: self.message) case .validationerror: - DynamoDBTableError.validation(partitionKey: partitionKey, sortKey: sortKey, - message: self.message) + DynamoDBTableError.validation( + partitionKey: partitionKey, + sortKey: sortKey, + message: self.message + ) case let .sdkUnknown(message): - DynamoDBTableError.unknown(code: message, partitionKey: partitionKey, - sortKey: sortKey, message: self.message) + DynamoDBTableError.unknown( + code: message, + partitionKey: partitionKey, + sortKey: sortKey, + message: self.message + ) } } } @@ -332,9 +395,10 @@ extension [DynamoDBTableError] { case .provisionedThroughputExceeded: canPassThrough(state: &seenProvisionedThroughputExceeded) ? error : nil case .conditionalCheckFailed, .duplicateItem, .concurrencyError, .validation, .throttling, .databaseError, - .unexpectedError, .unexpectedResponse, .resourceNotFound, .typeMismatch, .batchAPIExceededRetries, - .unexpectedType, .unableToUpdateError, .unrecognizedError, .multipleUnexpectedErrors, .transactionCanceled, - .transactionConflict, .batchFailures, .unknown: + .unexpectedError, .unexpectedResponse, .resourceNotFound, .typeMismatch, .batchAPIExceededRetries, + .unexpectedType, .unableToUpdateError, .unrecognizedError, .multipleUnexpectedErrors, + .transactionCanceled, + .transactionConflict, .batchFailures, .unknown: // always pass through these errors error } diff --git a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+getItems.swift b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+getItems.swift index 32a63a9..90966f7 100644 --- a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+getItems.swift +++ b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+getItems.swift @@ -34,16 +34,23 @@ private let maximumKeysPerGetItemBatch = 100 private let millisecondsToNanoSeconds: UInt64 = 1_000_000 /// DynamoDBTable conformance getItems function -public extension GenericAWSDynamoDBCompositePrimaryKeyTable { +extension GenericAWSDynamoDBCompositePrimaryKeyTable { /** Helper type that manages the state of a getItems request. - + As suggested here - https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html - this helper type monitors the unprocessed items returned in the response from DynamoDB and uses an exponential backoff algorithm to retry those items using the same retry configuration as the underlying DynamoDB client. */ - private class GetItemsRetriable { - typealias OutputType = [CompositePrimaryKey: TypedTTLDatabaseItem] + private class GetItemsRetriable< + AttributesType: PrimaryKeyAttributes, + ItemType: Codable & Sendable, + TimeToLiveAttributesType: TimeToLiveAttributes, + DynamoClient: DynamoDBClientProtocol + > { + typealias OutputType = [CompositePrimaryKey: TypedTTLDatabaseItem< + AttributesType, ItemType, TimeToLiveAttributesType + >] let dynamodb: DynamoClient let retryConfiguration: RetryConfiguration @@ -53,11 +60,12 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { var input: BatchGetItemInput var outputItems: OutputType = [:] - init(initialInput: BatchGetItemInput, - dynamodb: DynamoClient, - retryConfiguration: RetryConfiguration, - logger: Logging.Logger) - { + init( + initialInput: BatchGetItemInput, + dynamodb: DynamoClient, + retryConfiguration: RetryConfiguration, + logger: Logging.Logger + ) { self.dynamodb = dynamodb self.retryConfiguration = retryConfiguration self.retriesRemaining = retryConfiguration.numRetries @@ -69,22 +77,23 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { // submit the asynchronous request let output = try await self.dynamodb.batchGetItem(input: self.input) - let errors = output.responses?.flatMap { _, itemList -> [Error] in - return itemList.compactMap { values -> Error? in - do { - let attributeValue = DynamoDBClientTypes.AttributeValue.m(values) - - let decodedValue: TypedTTLDatabaseItem - = try DynamoDBDecoder().decode(attributeValue) - let key = decodedValue.compositePrimaryKey - - self.outputItems[key] = decodedValue - return nil - } catch { - return error + let errors = + output.responses?.flatMap { _, itemList -> [Error] in + return itemList.compactMap { values -> Error? in + do { + let attributeValue = DynamoDBClientTypes.AttributeValue.m(values) + + let decodedValue: TypedTTLDatabaseItem = + try DynamoDBDecoder().decode(attributeValue) + let key = decodedValue.compositePrimaryKey + + self.outputItems[key] = decodedValue + return nil + } catch { + return error + } } - } - } ?? [] + } ?? [] if !errors.isEmpty { throw DynamoDBTableError.multipleUnexpectedErrors(cause: errors) @@ -103,7 +112,9 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { // if there are retries remaining if self.retriesRemaining > 0 { // determine the required interval - let retryInterval = Int(self.retryConfiguration.getRetryInterval(retriesRemaining: self.retriesRemaining)) + let retryInterval = Int( + self.retryConfiguration.getRetryInterval(retriesRemaining: self.retriesRemaining) + ) let currentRetriesRemaining = self.retriesRemaining self.retriesRemaining -= 1 @@ -111,7 +122,8 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { let remainingKeysCount = self.input.requestItems?.count ?? 0 self.logger.warning( - "Request retried for remaining items: \(remainingKeysCount). Remaining retries: \(currentRetriesRemaining). Retrying in \(retryInterval) ms.") + "Request retried for remaining items: \(remainingKeysCount). Remaining retries: \(currentRetriesRemaining). Retrying in \(retryInterval) ms." + ) try await Task.sleep(nanoseconds: UInt64(retryInterval) * millisecondsToNanoSeconds) self.logger.trace("Reattempting request due to remaining retries: \(currentRetriesRemaining)") @@ -122,9 +134,12 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } } - func getItems( - forKeys keys: [CompositePrimaryKey]) async throws - -> [CompositePrimaryKey: TypedTTLDatabaseItem] + public func getItems( + forKeys keys: [CompositePrimaryKey] + ) async throws + -> [CompositePrimaryKey: TypedTTLDatabaseItem< + AttributesType, ItemType, TimeToLiveAttributesType + >] { let chunkedList = keys.chunked(by: maximumKeysPerGetItemBatch) @@ -135,7 +150,8 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { initialInput: input, dynamodb: self.dynamodb, retryConfiguration: self.tableConfiguration.retry, - logger: self.logger) + logger: self.logger + ) return try await retriable.batchGetItem() } diff --git a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+polymorphicGetItems.swift b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+polymorphicGetItems.swift index 2339df7..41e54ab 100644 --- a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+polymorphicGetItems.swift +++ b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+polymorphicGetItems.swift @@ -34,15 +34,18 @@ private let maximumKeysPerGetItemBatch = 100 private let millisecondsToNanoSeconds: UInt64 = 1_000_000 /// DynamoDBTable conformance polymorphicGetItems function -public extension GenericAWSDynamoDBCompositePrimaryKeyTable { +extension GenericAWSDynamoDBCompositePrimaryKeyTable { /** Helper type that manages the state of a polymorphicGetItems request. - + As suggested here - https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html - this helper type monitors the unprocessed items returned in the response from DynamoDB and uses an exponential backoff algorithm to retry those items using the same retry configuration as the underlying DynamoDB client. */ - private class PolymorphicGetItemsRetriable { + private class PolymorphicGetItemsRetriable< + ReturnedType: PolymorphicOperationReturnType & BatchCapableReturnType, + DynamoClient: DynamoDBClientProtocol + > { typealias OutputType = [CompositePrimaryKey: ReturnedType] let dynamodb: DynamoClient @@ -53,11 +56,12 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { var input: BatchGetItemInput var outputItems: OutputType = [:] - init(initialInput: BatchGetItemInput, - dynamodb: DynamoClient, - retryConfiguration: RetryConfiguration, - logger: Logging.Logger) - { + init( + initialInput: BatchGetItemInput, + dynamodb: DynamoClient, + retryConfiguration: RetryConfiguration, + logger: Logging.Logger + ) { self.dynamodb = dynamodb self.retryConfiguration = retryConfiguration self.retriesRemaining = retryConfiguration.numRetries @@ -69,22 +73,25 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { // submit the asynchronous request let output = try await self.dynamodb.batchGetItem(input: self.input) - let errors = output.responses?.flatMap { _, itemList -> [Error] in - return itemList.compactMap { values -> Error? in - do { - let attributeValue = DynamoDBClientTypes.AttributeValue.m(values) - - let decodedItem: ReturnTypeDecodable = try DynamoDBDecoder().decode(attributeValue) - let decodedValue = decodedItem.decodedValue - let key = decodedValue.getItemKey() - - self.outputItems[key] = decodedValue - return nil - } catch { - return error + let errors = + output.responses?.flatMap { _, itemList -> [Error] in + return itemList.compactMap { values -> Error? in + do { + let attributeValue = DynamoDBClientTypes.AttributeValue.m(values) + + let decodedItem: ReturnTypeDecodable = try DynamoDBDecoder().decode( + attributeValue + ) + let decodedValue = decodedItem.decodedValue + let key = decodedValue.getItemKey() + + self.outputItems[key] = decodedValue + return nil + } catch { + return error + } } - } - } ?? [] + } ?? [] if !errors.isEmpty { throw DynamoDBTableError.multipleUnexpectedErrors(cause: errors) @@ -103,7 +110,9 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { // if there are retries remaining if self.retriesRemaining > 0 { // determine the required interval - let retryInterval = Int(self.retryConfiguration.getRetryInterval(retriesRemaining: self.retriesRemaining)) + let retryInterval = Int( + self.retryConfiguration.getRetryInterval(retriesRemaining: self.retriesRemaining) + ) let currentRetriesRemaining = self.retriesRemaining self.retriesRemaining -= 1 @@ -111,7 +120,8 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { let remainingKeysCount = self.input.requestItems?.count ?? 0 self.logger.warning( - "Request retried for remaining items: \(remainingKeysCount). Remaining retries: \(currentRetriesRemaining). Retrying in \(retryInterval) ms.") + "Request retried for remaining items: \(remainingKeysCount). Remaining retries: \(currentRetriesRemaining). Retrying in \(retryInterval) ms." + ) try await Task.sleep(nanoseconds: UInt64(retryInterval) * millisecondsToNanoSeconds) self.logger.trace("Reattempting request due to remaining retries: \(currentRetriesRemaining)") @@ -122,20 +132,23 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } } - func polymorphicGetItems( - forKeys keys: [CompositePrimaryKey]) async throws + public func polymorphicGetItems( + forKeys keys: [CompositePrimaryKey] + ) async throws -> [CompositePrimaryKey: ReturnedType] { let chunkedList = keys.chunked(by: maximumKeysPerGetItemBatch) - let maps = try await chunkedList.concurrentMap { chunk -> [CompositePrimaryKey: ReturnedType] in + let maps = try await chunkedList.concurrentMap { + chunk -> [CompositePrimaryKey: ReturnedType] in let input = try self.getInputForBatchGetItem(forKeys: chunk) let retriable = PolymorphicGetItemsRetriable( initialInput: input, dynamodb: self.dynamodb, retryConfiguration: self.tableConfiguration.retry, - logger: self.logger) + logger: self.logger + ) return try await retriable.batchGetItem() } diff --git a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift index f9505fa..775965b 100644 --- a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift +++ b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift @@ -37,86 +37,112 @@ public enum AWSDynamoDBLimits { public static let maxStatementLength = 8192 } -private struct AWSDynamoDBPolymorphicWriteEntryTransform: PolymorphicWriteEntryTransform { +private struct AWSDynamoDBPolymorphicWriteEntryTransform: + PolymorphicWriteEntryTransform +{ typealias TableType = GenericAWSDynamoDBCompositePrimaryKeyTable let statement: String - init(_ entry: WriteEntry, table: TableType) throws { + init( + _ entry: WriteEntry, + table: TableType + ) throws { self.statement = try table.entryToStatement(entry) } } -private struct AWSDynamoDBPolymorphicTransactionConstraintTransform: PolymorphicTransactionConstraintTransform { +private struct AWSDynamoDBPolymorphicTransactionConstraintTransform: + PolymorphicTransactionConstraintTransform +{ typealias TableType = GenericAWSDynamoDBCompositePrimaryKeyTable let statement: String - init(_ entry: TransactionConstraintEntry, - table: TableType) throws - { + init( + _ entry: TransactionConstraintEntry< + some PrimaryKeyAttributes, some Codable & Sendable, some TimeToLiveAttributes + >, + table: TableType + ) throws { self.statement = try table.entryToStatement(entry) } } /// DynamoDBTable conformance updateItems function -public extension GenericAWSDynamoDBCompositePrimaryKeyTable { - func validateEntry(entry: WriteEntry) throws { +extension GenericAWSDynamoDBCompositePrimaryKeyTable { + public func validateEntry(entry: WriteEntry) throws { let statement: String = try entryToStatement(entry) if statement.count > AWSDynamoDBLimits.maxStatementLength { throw DynamoDBTableError.statementLengthExceeded( reason: "failed to satisfy constraint: Member must have length less than or equal " - + "to \(AWSDynamoDBLimits.maxStatementLength). Actual length \(statement.count)") + + "to \(AWSDynamoDBLimits.maxStatementLength). Actual length \(statement.count)" + ) } } internal func entryToStatement( - _ entry: WriteEntry) throws -> String - { - let statement: String = switch entry { - case let .update(new: new, existing: existing): - try getUpdateExpression(tableName: self.targetTableName, - newItem: new, - existingItem: existing) - case let .insert(new: new): - try getInsertExpression(tableName: self.targetTableName, - newItem: new) - case let .deleteAtKey(key: key): - try getDeleteExpression(tableName: self.targetTableName, - existingKey: key) - case let .deleteItem(existing: existing): - try getDeleteExpression(tableName: self.targetTableName, - existingItem: existing) - } + _ entry: WriteEntry + ) throws -> String { + let statement: String = + switch entry { + case let .update(new: new, existing: existing): + try getUpdateExpression( + tableName: self.targetTableName, + newItem: new, + existingItem: existing + ) + case let .insert(new: new): + try getInsertExpression( + tableName: self.targetTableName, + newItem: new + ) + case let .deleteAtKey(key: key): + try getDeleteExpression( + tableName: self.targetTableName, + existingKey: key + ) + case let .deleteItem(existing: existing): + try getDeleteExpression( + tableName: self.targetTableName, + existingItem: existing + ) + } return statement } internal func entryToStatement( - _ entry: TransactionConstraintEntry) throws -> String - { - let statement: String = switch entry { - case let .required(existing: existing): - getExistsExpression(tableName: self.targetTableName, - existingItem: existing) - } + _ entry: TransactionConstraintEntry + ) throws -> String { + let statement: String = + switch entry { + case let .required(existing: existing): + getExistsExpression( + tableName: self.targetTableName, + existingItem: existing + ) + } return statement } private func entryToBatchStatementRequest( - _ entry: WriteEntry) throws -> DynamoDBClientTypes.BatchStatementRequest - { + _ entry: WriteEntry + ) throws -> DynamoDBClientTypes.BatchStatementRequest { let statement: String = try entryToStatement(entry) - return DynamoDBClientTypes.BatchStatementRequest(consistentRead: self.tableConfiguration.consistentRead, statement: statement) + return DynamoDBClientTypes.BatchStatementRequest( + consistentRead: self.tableConfiguration.consistentRead, + statement: statement + ) } private func writeTransactionItems( _ entries: [WriteEntry], - constraints: [TransactionConstraintEntry]) async throws - { + constraints: [TransactionConstraintEntry] + ) async throws { // if there are no items, there is nothing to update guard entries.count > 0 else { return @@ -140,15 +166,18 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } private func getExecuteTransactionInput( - _ entries: [some PolymorphicWriteEntry], constraints: [some PolymorphicTransactionConstraintEntry]) throws -> ExecuteTransactionInput? - { + _ entries: [some PolymorphicWriteEntry], + constraints: [some PolymorphicTransactionConstraintEntry] + ) throws -> ExecuteTransactionInput? { // if there are no items, there is nothing to update guard entries.count > 0 else { return nil } - let context = StandardPolymorphicWriteEntryContext, - AWSDynamoDBPolymorphicTransactionConstraintTransform>(table: self) + let context = StandardPolymorphicWriteEntryContext< + AWSDynamoDBPolymorphicWriteEntryTransform, + AWSDynamoDBPolymorphicTransactionConstraintTransform + >(table: self) let entryStatements = try entries.map { entry -> DynamoDBClientTypes.ParameterizedStatement in let transform: AWSDynamoDBPolymorphicWriteEntryTransform = try entry.handle(context: context) let statement = transform.statement @@ -157,7 +186,9 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } let requiredItemsStatements = try constraints.map { entry -> DynamoDBClientTypes.ParameterizedStatement in - let transform: AWSDynamoDBPolymorphicTransactionConstraintTransform = try entry.handle(context: context) + let transform: AWSDynamoDBPolymorphicTransactionConstraintTransform = try entry.handle( + context: context + ) let statement = transform.statement return DynamoDBClientTypes.ParameterizedStatement(statement: statement) @@ -166,20 +197,28 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { return ExecuteTransactionInput(transactStatements: entryStatements + requiredItemsStatements) } - func transactWrite(_ entries: [WriteEntry]) async throws { - try await self.transactWrite(entries, constraints: [], - retriesRemaining: self.tableConfiguration.retry.numRetries) + public func transactWrite(_ entries: [WriteEntry]) async throws { + try await self.transactWrite( + entries, + constraints: [], + retriesRemaining: self.tableConfiguration.retry.numRetries + ) } - func transactWrite( + public func transactWrite( _ entries: [WriteEntry], - constraints: [TransactionConstraintEntry]) async throws - { - try await self.transactWrite(entries, constraints: constraints, - retriesRemaining: self.tableConfiguration.retry.numRetries) + constraints: [TransactionConstraintEntry] + ) async throws { + try await self.transactWrite( + entries, + constraints: constraints, + retriesRemaining: self.tableConfiguration.retry.numRetries + ) } - func polymorphicTransactWrite(_ entries: [WriteEntryType]) async throws { + public func polymorphicTransactWrite( + _ entries: [WriteEntryType] + ) async throws { let noConstraints: [EmptyPolymorphicTransactionConstraintEntry] = [] guard let transactionInput = try getExecuteTransactionInput(entries, constraints: noConstraints) else { @@ -188,35 +227,46 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } let inputKeys = entries.map(\.compositePrimaryKey) - try await self.polymorphicTransactWrite(transactionInput, inputKeys: inputKeys, - retriesRemaining: self.tableConfiguration.retry.numRetries) + try await self.polymorphicTransactWrite( + transactionInput, + inputKeys: inputKeys, + retriesRemaining: self.tableConfiguration.retry.numRetries + ) } - func polymorphicTransactWrite( - _ entries: [WriteEntryType], constraints: [TransactionConstraintEntryType]) async throws - where WriteEntryType.AttributesType == TransactionConstraintEntryType.AttributesType - { + public func polymorphicTransactWrite< + WriteEntryType: PolymorphicWriteEntry, + TransactionConstraintEntryType: PolymorphicTransactionConstraintEntry + >( + _ entries: [WriteEntryType], + constraints: [TransactionConstraintEntryType] + ) async throws + where WriteEntryType.AttributesType == TransactionConstraintEntryType.AttributesType { guard let transactionInput = try getExecuteTransactionInput(entries, constraints: constraints) else { // nothing to do return } let inputKeys = entries.map(\.compositePrimaryKey) + constraints.map(\.compositePrimaryKey) - try await self.polymorphicTransactWrite(transactionInput, inputKeys: inputKeys, - retriesRemaining: self.tableConfiguration.retry.numRetries) + try await self.polymorphicTransactWrite( + transactionInput, + inputKeys: inputKeys, + retriesRemaining: self.tableConfiguration.retry.numRetries + ) } private func transactWrite( _ entries: [WriteEntry], constraints: [TransactionConstraintEntry], - retriesRemaining: Int) async throws - { + retriesRemaining: Int + ) async throws { let entryCount = entries.count + constraints.count if entryCount > AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement { - throw DynamoDBTableError.itemCollectionSizeLimitExceeded(attemptedSize: entryCount, - maximumSize: AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement) + throw DynamoDBTableError.itemCollectionSizeLimitExceeded( + attemptedSize: entryCount, + maximumSize: AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement + ) } let result: Swift.Result @@ -232,12 +282,15 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { let keys = entries.map(\.compositePrimaryKey) + constraints.map(\.compositePrimaryKey) var isTransactionConflict = false - let reasons = try zip(cancellationReasons, keys).compactMap { cancellationReason, entryKey -> DynamoDBTableError? in - let key: CompositePrimaryKey? = if let item = cancellationReason.item { - try DynamoDBDecoder().decode(.m(item)) - } else { - nil - } + let reasons = try zip(cancellationReasons, keys).compactMap { + cancellationReason, + entryKey -> DynamoDBTableError? in + let key: CompositePrimaryKey? = + if let item = cancellationReason.item { + try DynamoDBDecoder().decode(.m(item)) + } else { + nil + } let partitionKey = key?.partitionKey ?? entryKey.partitionKey let sortKey = key?.sortKey ?? entryKey.sortKey @@ -247,15 +300,22 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { case "None": return nil case "ConditionalCheckFailed": - return DynamoDBTableError.conditionalCheckFailed(partitionKey: partitionKey, - sortKey: sortKey, - message: cancellationReason.message) + return DynamoDBTableError.conditionalCheckFailed( + partitionKey: partitionKey, + sortKey: sortKey, + message: cancellationReason.message + ) case "DuplicateItem": - return DynamoDBTableError.duplicateItem(partitionKey: partitionKey, sortKey: sortKey, - message: cancellationReason.message) + return DynamoDBTableError.duplicateItem( + partitionKey: partitionKey, + sortKey: sortKey, + message: cancellationReason.message + ) case "ItemCollectionSizeLimitExceeded": - return DynamoDBTableError.itemCollectionSizeLimitExceeded(attemptedSize: entryCount, - maximumSize: AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement) + return DynamoDBTableError.itemCollectionSizeLimitExceeded( + attemptedSize: entryCount, + maximumSize: AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement + ) case "TransactionConflict": isTransactionConflict = true @@ -265,22 +325,37 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { case "ThrottlingError": return DynamoDBTableError.throttling(message: cancellationReason.message) case "ValidationError": - return DynamoDBTableError.validation(partitionKey: partitionKey, sortKey: sortKey, - message: cancellationReason.message) + return DynamoDBTableError.validation( + partitionKey: partitionKey, + sortKey: sortKey, + message: cancellationReason.message + ) default: - return DynamoDBTableError.unknown(code: cancellationReason.code, partitionKey: partitionKey, - sortKey: sortKey, message: cancellationReason.message) + return DynamoDBTableError.unknown( + code: cancellationReason.code, + partitionKey: partitionKey, + sortKey: sortKey, + message: cancellationReason.message + ) } } if isTransactionConflict, retriesRemaining > 0 { - return try await retryTransactWrite(entries, constraints: constraints, retriesRemaining: retriesRemaining) + return try await retryTransactWrite( + entries, + constraints: constraints, + retriesRemaining: retriesRemaining + ) } result = .failure(DynamoDBTableError.transactionCanceled(reasons: reasons)) } catch let exception as TransactionConflictException { if retriesRemaining > 0 { - return try await retryTransactWrite(entries, constraints: constraints, retriesRemaining: retriesRemaining) + return try await retryTransactWrite( + entries, + constraints: constraints, + retriesRemaining: retriesRemaining + ) } let reason = DynamoDBTableError.transactionConflict(message: exception.message) @@ -302,25 +377,30 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { private func retryTransactWrite( _ entries: [WriteEntry], constraints: [TransactionConstraintEntry], - retriesRemaining: Int) async throws - { + retriesRemaining: Int + ) async throws { // determine the required interval let retryInterval = Int(self.tableConfiguration.retry.getRetryInterval(retriesRemaining: retriesRemaining)) logger.warning( - "Transaction retried due to conflict. Remaining retries: \(retriesRemaining). Retrying in \(retryInterval) ms.") + "Transaction retried due to conflict. Remaining retries: \(retriesRemaining). Retrying in \(retryInterval) ms." + ) try await Task.sleep(nanoseconds: UInt64(retryInterval) * millisecondsToNanoSeconds) logger.trace("Reattempting request due to remaining retries: \(retryInterval)") return try await self.transactWrite(entries, constraints: constraints, retriesRemaining: retriesRemaining - 1) } - private func polymorphicTransactWrite(_ transactionInput: ExecuteTransactionInput, - inputKeys: [CompositePrimaryKey], retriesRemaining: Int) async throws - { + private func polymorphicTransactWrite( + _ transactionInput: ExecuteTransactionInput, + inputKeys: [CompositePrimaryKey], + retriesRemaining: Int + ) async throws { if inputKeys.count > AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement { - throw DynamoDBTableError.itemCollectionSizeLimitExceeded(attemptedSize: inputKeys.count, - maximumSize: AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement) + throw DynamoDBTableError.itemCollectionSizeLimitExceeded( + attemptedSize: inputKeys.count, + maximumSize: AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement + ) } let result: Swift.Result @@ -334,12 +414,15 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } var isTransactionConflict = false - let reasons = try zip(cancellationReasons, inputKeys).compactMap { cancellationReason, entryKey -> DynamoDBTableError? in - let key: CompositePrimaryKey? = if let item = cancellationReason.item { - try DynamoDBDecoder().decode(.m(item)) - } else { - nil - } + let reasons = try zip(cancellationReasons, inputKeys).compactMap { + cancellationReason, + entryKey -> DynamoDBTableError? in + let key: CompositePrimaryKey? = + if let item = cancellationReason.item { + try DynamoDBDecoder().decode(.m(item)) + } else { + nil + } let partitionKey = key?.partitionKey ?? entryKey.partitionKey let sortKey = key?.sortKey ?? entryKey.sortKey @@ -349,15 +432,22 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { case "None": return nil case "ConditionalCheckFailed": - return DynamoDBTableError.conditionalCheckFailed(partitionKey: partitionKey, - sortKey: sortKey, - message: cancellationReason.message) + return DynamoDBTableError.conditionalCheckFailed( + partitionKey: partitionKey, + sortKey: sortKey, + message: cancellationReason.message + ) case "DuplicateItem": - return DynamoDBTableError.duplicateItem(partitionKey: partitionKey, sortKey: sortKey, - message: cancellationReason.message) + return DynamoDBTableError.duplicateItem( + partitionKey: partitionKey, + sortKey: sortKey, + message: cancellationReason.message + ) case "ItemCollectionSizeLimitExceeded": - return DynamoDBTableError.itemCollectionSizeLimitExceeded(attemptedSize: inputKeys.count, - maximumSize: AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement) + return DynamoDBTableError.itemCollectionSizeLimitExceeded( + attemptedSize: inputKeys.count, + maximumSize: AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement + ) case "TransactionConflict": isTransactionConflict = true @@ -367,22 +457,37 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { case "ThrottlingError": return DynamoDBTableError.throttling(message: cancellationReason.message) case "ValidationError": - return DynamoDBTableError.validation(partitionKey: partitionKey, sortKey: sortKey, - message: cancellationReason.message) + return DynamoDBTableError.validation( + partitionKey: partitionKey, + sortKey: sortKey, + message: cancellationReason.message + ) default: - return DynamoDBTableError.unknown(code: cancellationReason.code, partitionKey: partitionKey, - sortKey: sortKey, message: cancellationReason.message) + return DynamoDBTableError.unknown( + code: cancellationReason.code, + partitionKey: partitionKey, + sortKey: sortKey, + message: cancellationReason.message + ) } } if isTransactionConflict, retriesRemaining > 0 { - return try await retryPolymorphicTransactWrite(transactionInput, inputKeys: inputKeys, retriesRemaining: retriesRemaining) + return try await retryPolymorphicTransactWrite( + transactionInput, + inputKeys: inputKeys, + retriesRemaining: retriesRemaining + ) } result = .failure(DynamoDBTableError.transactionCanceled(reasons: reasons)) } catch let exception as TransactionConflictException { if retriesRemaining > 0 { - return try await retryPolymorphicTransactWrite(transactionInput, inputKeys: inputKeys, retriesRemaining: retriesRemaining) + return try await retryPolymorphicTransactWrite( + transactionInput, + inputKeys: inputKeys, + retriesRemaining: retriesRemaining + ) } let reason = DynamoDBTableError.transactionConflict(message: exception.message) @@ -403,32 +508,45 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { private func retryPolymorphicTransactWrite( _ transactionInput: ExecuteTransactionInput, - inputKeys: [CompositePrimaryKey], retriesRemaining: Int) async throws - { + inputKeys: [CompositePrimaryKey], + retriesRemaining: Int + ) async throws { // determine the required interval let retryInterval = Int(self.tableConfiguration.retry.getRetryInterval(retriesRemaining: retriesRemaining)) logger.warning( - "Transaction retried due to conflict. Remaining retries: \(retriesRemaining). Retrying in \(retryInterval) ms.") + "Transaction retried due to conflict. Remaining retries: \(retriesRemaining). Retrying in \(retryInterval) ms." + ) try await Task.sleep(nanoseconds: UInt64(retryInterval) * millisecondsToNanoSeconds) logger.trace("Reattempting request due to remaining retries: \(retryInterval)") - return try await self.polymorphicTransactWrite(transactionInput, inputKeys: inputKeys, retriesRemaining: retriesRemaining - 1) + return try await self.polymorphicTransactWrite( + transactionInput, + inputKeys: inputKeys, + retriesRemaining: retriesRemaining - 1 + ) } - private func writeChunkedItems(_ entries: [some PolymorphicWriteEntry]) async throws -> [DynamoDBClientTypes.BatchStatementResponse] { + private func writeChunkedItems( + _ entries: [some PolymorphicWriteEntry] + ) async throws -> [DynamoDBClientTypes.BatchStatementResponse] { // if there are no items, there is nothing to update guard entries.count > 0 else { return [] } - let context = StandardPolymorphicWriteEntryContext, - AWSDynamoDBPolymorphicTransactionConstraintTransform>(table: self) + let context = StandardPolymorphicWriteEntryContext< + AWSDynamoDBPolymorphicWriteEntryTransform, + AWSDynamoDBPolymorphicTransactionConstraintTransform + >(table: self) let statements = try entries.map { entry -> DynamoDBClientTypes.BatchStatementRequest in let transform: AWSDynamoDBPolymorphicWriteEntryTransform = try entry.handle(context: context) let statement = transform.statement - return DynamoDBClientTypes.BatchStatementRequest(consistentRead: self.tableConfiguration.consistentRead, statement: statement) + return DynamoDBClientTypes.BatchStatementRequest( + consistentRead: self.tableConfiguration.consistentRead, + statement: statement + ) } let executeInput = BatchExecuteStatementInput(statements: statements) @@ -437,7 +555,7 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { return response.responses ?? [] } - func polymorphicBulkWrite(_ entries: [some PolymorphicWriteEntry]) async throws { + public func polymorphicBulkWrite(_ entries: [some PolymorphicWriteEntry]) async throws { // BatchExecuteStatement has a maximum of 25 statements // This function handles pagination internally. let chunkedEntries = entries.chunked(by: AWSDynamoDBLimits.maximumUpdatesPerExecuteStatement) @@ -448,8 +566,11 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } let errors = zippedResponses.compactMap { response, item in - response.error?.asDynamoDBTableError(partitionKey: item.compositePrimaryKey.partitionKey, - sortKey: item.compositePrimaryKey.sortKey, entryCount: entries.count) + response.error?.asDynamoDBTableError( + partitionKey: item.compositePrimaryKey.partitionKey, + sortKey: item.compositePrimaryKey.sortKey, + entryCount: entries.count + ) } if !errors.isEmpty { @@ -457,30 +578,44 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } } - private func writeChunkedItems(_ entries: [WriteEntry]) async throws -> [DynamoDBClientTypes.BatchStatementResponse] { + private func writeChunkedItems( + _ entries: [WriteEntry] + ) async throws -> [DynamoDBClientTypes.BatchStatementResponse] { // if there are no items, there is nothing to update guard entries.count > 0 else { return [] } let statements = try entries.map { entry -> DynamoDBClientTypes.BatchStatementRequest in - let statement: String = switch entry { - case let .update(new: new, existing: existing): - try getUpdateExpression(tableName: self.targetTableName, - newItem: new, - existingItem: existing) - case let .insert(new: new): - try getInsertExpression(tableName: self.targetTableName, - newItem: new) - case let .deleteAtKey(key: key): - try getDeleteExpression(tableName: self.targetTableName, - existingKey: key) - case let .deleteItem(existing: existing): - try getDeleteExpression(tableName: self.targetTableName, - existingItem: existing) - } + let statement: String = + switch entry { + case let .update(new: new, existing: existing): + try getUpdateExpression( + tableName: self.targetTableName, + newItem: new, + existingItem: existing + ) + case let .insert(new: new): + try getInsertExpression( + tableName: self.targetTableName, + newItem: new + ) + case let .deleteAtKey(key: key): + try getDeleteExpression( + tableName: self.targetTableName, + existingKey: key + ) + case let .deleteItem(existing: existing): + try getDeleteExpression( + tableName: self.targetTableName, + existingItem: existing + ) + } - return DynamoDBClientTypes.BatchStatementRequest(consistentRead: self.tableConfiguration.consistentRead, statement: statement) + return DynamoDBClientTypes.BatchStatementRequest( + consistentRead: self.tableConfiguration.consistentRead, + statement: statement + ) } let executeInput = BatchExecuteStatementInput(statements: statements) @@ -489,7 +624,7 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { return response.responses ?? [] } - func bulkWrite(_ entries: [WriteEntry]) async throws { + public func bulkWrite(_ entries: [WriteEntry]) async throws { // BatchExecuteStatement has a maximum of 25 statements // This function handles pagination internally. let chunkedEntries = entries.chunked(by: AWSDynamoDBLimits.maximumUpdatesPerExecuteStatement) @@ -500,8 +635,11 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } let errors = zippedResponses.compactMap { response, item in - response.error?.asDynamoDBTableError(partitionKey: item.compositePrimaryKey.partitionKey, - sortKey: item.compositePrimaryKey.sortKey, entryCount: entries.count) + response.error?.asDynamoDBTableError( + partitionKey: item.compositePrimaryKey.partitionKey, + sortKey: item.compositePrimaryKey.sortKey, + entryCount: entries.count + ) } if !errors.isEmpty { @@ -509,32 +647,33 @@ public extension GenericAWSDynamoDBCompositePrimaryKeyTable { } } - func bulkWriteWithFallback( - _ entries: [WriteEntry]) async throws - { + public func bulkWriteWithFallback( + _ entries: [WriteEntry] + ) async throws { // fall back to single operation if the write entry exceeds the statement length limitation - let results: [Result, DynamoDBTableError>] = try await entries.concurrentMap { entry in - do { - try self.validateEntry(entry: entry) - } catch DynamoDBTableError.statementLengthExceeded { + let results: [Result, DynamoDBTableError>] = + try await entries.concurrentMap { entry in do { - switch entry { - case let .update(new: new, existing: existing): - try await self.updateItem(newItem: new, existingItem: existing) - case let .insert(new: new): - try await self.insertItem(new) - case let .deleteAtKey(key: key): - try await self.deleteItem(forKey: key) - case let .deleteItem(existing: existing): - try await self.deleteItem(existingItem: existing) + try self.validateEntry(entry: entry) + } catch DynamoDBTableError.statementLengthExceeded { + do { + switch entry { + case let .update(new: new, existing: existing): + try await self.updateItem(newItem: new, existingItem: existing) + case let .insert(new: new): + try await self.insertItem(new) + case let .deleteAtKey(key: key): + try await self.deleteItem(forKey: key) + case let .deleteItem(existing: existing): + try await self.deleteItem(existingItem: existing) + } + } catch let error as DynamoDBTableError { + return .failure(error) } - } catch let error as DynamoDBTableError { - return .failure(error) } - } - return .success(entry) - } + return .success(entry) + } var bulkWriteEntries: [WriteEntry] = [] var errors: [DynamoDBTableError] = [] diff --git a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable.swift b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable.swift index b33f578..dc7a6cd 100644 --- a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable.swift +++ b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable.swift @@ -24,8 +24,8 @@ // DynamoDBTables // -import AwsCommonRuntimeKit @preconcurrency import AWSDynamoDB +import AwsCommonRuntimeKit import ClientRuntime import Foundation import Logging @@ -46,9 +46,11 @@ public struct AWSDynamoDBTableConfiguration: Sendable { public let escapeSingleQuoteInPartiQL: Bool public let retry: RetryConfiguration - public init(consistentRead: Bool = true, escapeSingleQuoteInPartiQL: Bool = false, - retry: RetryConfiguration = .default) - { + public init( + consistentRead: Bool = true, + escapeSingleQuoteInPartiQL: Bool = false, + retry: RetryConfiguration = .default + ) { self.consistentRead = consistentRead self.escapeSingleQuoteInPartiQL = escapeSingleQuoteInPartiQL self.retry = retry @@ -78,27 +80,34 @@ public struct AWSDynamoDBTableConfiguration: Sendable { /// client: awsClient /// ) /// ``` -public typealias AWSDynamoDBCompositePrimaryKeyTable = GenericAWSDynamoDBCompositePrimaryKeyTable +public typealias AWSDynamoDBCompositePrimaryKeyTable = GenericAWSDynamoDBCompositePrimaryKeyTable< + AWSDynamoDB.DynamoDBClient +> -public struct GenericAWSDynamoDBCompositePrimaryKeyTable: DynamoDBCompositePrimaryKeyTable, Sendable { +public struct GenericAWSDynamoDBCompositePrimaryKeyTable: + DynamoDBCompositePrimaryKeyTable, Sendable +{ let dynamodb: Client let targetTableName: String public let tableConfiguration: AWSDynamoDBTableConfiguration public let tableMetrics: AWSDynamoDBTableMetrics let logger: Logging.Logger - public init(tableName: String, region: Swift.String, - awsCredentialIdentityResolver: (any SmithyIdentity.AWSCredentialIdentityResolver)? = nil, - httpClientConfiguration: ClientRuntime.HttpClientConfiguration? = nil, - tableConfiguration: AWSDynamoDBTableConfiguration = .init(), - tableMetrics: AWSDynamoDBTableMetrics = .init(), - logger: Logging.Logger? = nil) throws where Client == AWSDynamoDB.DynamoDBClient - { + public init( + tableName: String, + region: Swift.String, + awsCredentialIdentityResolver: (any SmithyIdentity.AWSCredentialIdentityResolver)? = nil, + httpClientConfiguration: ClientRuntime.HttpClientConfiguration? = nil, + tableConfiguration: AWSDynamoDBTableConfiguration = .init(), + tableMetrics: AWSDynamoDBTableMetrics = .init(), + logger: Logging.Logger? = nil + ) throws where Client == AWSDynamoDB.DynamoDBClient { self.logger = logger ?? Logging.Logger(label: "AWSDynamoDBCompositePrimaryKeyTable") let config = try DynamoDBClient.DynamoDBClientConfiguration( awsCredentialIdentityResolver: awsCredentialIdentityResolver, region: region, - httpClientConfiguration: httpClientConfiguration) + httpClientConfiguration: httpClientConfiguration + ) self.dynamodb = AWSDynamoDB.DynamoDBClient(config: config) self.targetTableName = tableName self.tableConfiguration = tableConfiguration @@ -107,12 +116,13 @@ public struct GenericAWSDynamoDBCompositePrimaryKeyTable( - _ item: TypedTTLDatabaseItem) throws + _ item: TypedTTLDatabaseItem + ) throws -> AWSDynamoDB.PutItemInput { let attributes = try getAttributes(forItem: item) - let expressionAttributeNames = ["#pk": AttributesType.partitionKeyAttributeName, "#sk": AttributesType.sortKeyAttributeName] + let expressionAttributeNames = [ + "#pk": AttributesType.partitionKeyAttributeName, "#sk": AttributesType.sortKeyAttributeName, + ] let conditionExpression = "attribute_not_exists (#pk) AND attribute_not_exists (#sk)" - return AWSDynamoDB.PutItemInput(conditionExpression: conditionExpression, - expressionAttributeNames: expressionAttributeNames, - item: attributes, - tableName: self.targetTableName) + return AWSDynamoDB.PutItemInput( + conditionExpression: conditionExpression, + expressionAttributeNames: expressionAttributeNames, + item: attributes, + tableName: self.targetTableName + ) } func getInputForUpdateItem( newItem: TypedTTLDatabaseItem, - existingItem: TypedTTLDatabaseItem) throws -> AWSDynamoDB.PutItemInput - { + existingItem: TypedTTLDatabaseItem + ) throws -> AWSDynamoDB.PutItemInput { let attributes = try getAttributes(forItem: newItem) let expressionAttributeNames = [ "#rowversion": RowStatus.CodingKeys.rowVersion.stringValue, - "#createdate": TypedTTLDatabaseItem.CodingKeys.createDate.stringValue, + "#createdate": TypedTTLDatabaseItem.CodingKeys + .createDate.stringValue, ] let expressionAttributeValues = [ ":versionnumber": DynamoDBClientTypes.AttributeValue.n(String(existingItem.rowStatus.rowVersion)), @@ -156,26 +172,32 @@ extension GenericAWSDynamoDBCompositePrimaryKeyTable { let conditionExpression = "#rowversion = :versionnumber AND #createdate = :creationdate" - return AWSDynamoDB.PutItemInput(conditionExpression: conditionExpression, - expressionAttributeNames: expressionAttributeNames, - expressionAttributeValues: expressionAttributeValues, - item: attributes, - tableName: self.targetTableName) + return AWSDynamoDB.PutItemInput( + conditionExpression: conditionExpression, + expressionAttributeNames: expressionAttributeNames, + expressionAttributeValues: expressionAttributeValues, + item: attributes, + tableName: self.targetTableName + ) } func getInputForGetItem(forKey key: CompositePrimaryKey) throws -> AWSDynamoDB.GetItemInput { let attributeValue = try DynamoDBEncoder().encode(key) if case let .m(keyAttributes) = attributeValue { - return AWSDynamoDB.GetItemInput(consistentRead: self.tableConfiguration.consistentRead, - key: keyAttributes, - tableName: self.targetTableName) + return AWSDynamoDB.GetItemInput( + consistentRead: self.tableConfiguration.consistentRead, + key: keyAttributes, + tableName: self.targetTableName + ) } else { throw DynamoDBTableError.unexpectedResponse(reason: "Expected a structure.") } } - func getInputForBatchGetItem(forKeys keys: [CompositePrimaryKey]) throws + func getInputForBatchGetItem( + forKeys keys: [CompositePrimaryKey] + ) throws -> AWSDynamoDB.BatchGetItemInput { let keys = try keys.map { key -> [String: DynamoDBClientTypes.AttributeValue] in @@ -188,8 +210,10 @@ extension GenericAWSDynamoDBCompositePrimaryKeyTable { } } - let keysAndAttributes = DynamoDBClientTypes.KeysAndAttributes(consistentRead: self.tableConfiguration.consistentRead, - keys: keys) + let keysAndAttributes = DynamoDBClientTypes.KeysAndAttributes( + consistentRead: self.tableConfiguration.consistentRead, + keys: keys + ) return AWSDynamoDB.BatchGetItemInput(requestItems: [self.targetTableName: keysAndAttributes]) } @@ -198,16 +222,18 @@ extension GenericAWSDynamoDBCompositePrimaryKeyTable { let attributeValue = try DynamoDBEncoder().encode(key) if case let .m(keyAttributes) = attributeValue { - return AWSDynamoDB.DeleteItemInput(key: keyAttributes, - tableName: self.targetTableName) + return AWSDynamoDB.DeleteItemInput( + key: keyAttributes, + tableName: self.targetTableName + ) } else { throw DynamoDBTableError.unexpectedResponse(reason: "Expected a structure.") } } func getInputForDeleteItem( - existingItem: TypedTTLDatabaseItem) throws -> AWSDynamoDB.DeleteItemInput - { + existingItem: TypedTTLDatabaseItem + ) throws -> AWSDynamoDB.DeleteItemInput { let attributeValue = try DynamoDBEncoder().encode(existingItem.compositePrimaryKey) guard case let .m(keyAttributes) = attributeValue else { @@ -216,7 +242,8 @@ extension GenericAWSDynamoDBCompositePrimaryKeyTable { let expressionAttributeNames = [ "#rowversion": RowStatus.CodingKeys.rowVersion.stringValue, - "#createdate": TypedTTLDatabaseItem.CodingKeys.createDate.stringValue, + "#createdate": TypedTTLDatabaseItem.CodingKeys + .createDate.stringValue, ] let expressionAttributeValues = [ ":versionnumber": DynamoDBClientTypes.AttributeValue.n(String(existingItem.rowStatus.rowVersion)), @@ -225,18 +252,20 @@ extension GenericAWSDynamoDBCompositePrimaryKeyTable { let conditionExpression = "#rowversion = :versionnumber AND #createdate = :creationdate" - return AWSDynamoDB.DeleteItemInput(conditionExpression: conditionExpression, - expressionAttributeNames: expressionAttributeNames, - expressionAttributeValues: expressionAttributeValues, - key: keyAttributes, - tableName: self.targetTableName) + return AWSDynamoDB.DeleteItemInput( + conditionExpression: conditionExpression, + expressionAttributeNames: expressionAttributeNames, + expressionAttributeValues: expressionAttributeValues, + key: keyAttributes, + tableName: self.targetTableName + ) } } extension Array { func chunked(by chunkSize: Int) -> [[Element]] { stride(from: 0, to: self.count, by: chunkSize).map { - Array(self[$0 ..< Swift.min($0 + chunkSize, self.count)]) + Array(self[$0..(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?) async throws +extension AWSDynamoDBCompositePrimaryKeysProjection { + public func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition? + ) async throws -> [CompositePrimaryKey] { - try await self.partialQuery(forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition, - exclusiveStartKey: nil) + try await self.partialQuery( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition, + exclusiveStartKey: nil + ) } // function to return a future with the results of a query call and all future paginated calls private func partialQuery( forPartitionKey partitionKey: String, sortKeyCondition: AttributeCondition?, - exclusiveStartKey: String?) async throws + exclusiveStartKey: String? + ) async throws -> [CompositePrimaryKey] { let paginatedItems: ([CompositePrimaryKey], String?) = - try await query(forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition, - limit: nil, - scanIndexForward: true, - exclusiveStartKey: exclusiveStartKey) + try await query( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition, + limit: nil, + scanIndexForward: true, + exclusiveStartKey: exclusiveStartKey + ) // if there are more items if let lastEvaluatedKey = paginatedItems.1 { @@ -59,7 +66,8 @@ public extension AWSDynamoDBCompositePrimaryKeysProjection { let partialResult: [CompositePrimaryKey] = try await self.partialQuery( forPartitionKey: partitionKey, sortKeyCondition: sortKeyCondition, - exclusiveStartKey: lastEvaluatedKey) + exclusiveStartKey: lastEvaluatedKey + ) // return the results from 'this' call and all later paginated calls return paginatedItems.0 + partialResult @@ -69,34 +77,46 @@ public extension AWSDynamoDBCompositePrimaryKeysProjection { } } - func query(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - exclusiveStartKey: String?) async throws + public func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + exclusiveStartKey: String? + ) async throws -> (keys: [CompositePrimaryKey], lastEvaluatedKey: String?) { - try await self.query(forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition, - limit: limit, - scanIndexForward: true, - exclusiveStartKey: exclusiveStartKey) + try await self.query( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition, + limit: limit, + scanIndexForward: true, + exclusiveStartKey: exclusiveStartKey + ) } - func query(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - scanIndexForward: Bool, - exclusiveStartKey: String?) async throws + public func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + scanIndexForward: Bool, + exclusiveStartKey: String? + ) async throws -> (keys: [CompositePrimaryKey], lastEvaluatedKey: String?) { - let queryInput = try AWSDynamoDB.QueryInput.forSortKeyCondition(partitionKey: partitionKey, targetTableName: targetTableName, - primaryKeyType: AttributesType.self, - sortKeyCondition: sortKeyCondition, limit: limit, - scanIndexForward: scanIndexForward, exclusiveStartKey: exclusiveStartKey, - consistentRead: self.tableConfiguration.consistentRead) - - let logMessage = "dynamodb.query with partitionKey: \(partitionKey), " + - "sortKeyCondition: \(sortKeyCondition.debugDescription), and table name \(targetTableName)." + let queryInput = try AWSDynamoDB.QueryInput.forSortKeyCondition( + partitionKey: partitionKey, + targetTableName: targetTableName, + primaryKeyType: AttributesType.self, + sortKeyCondition: sortKeyCondition, + limit: limit, + scanIndexForward: scanIndexForward, + exclusiveStartKey: exclusiveStartKey, + consistentRead: self.tableConfiguration.consistentRead + ) + + let logMessage = + "dynamodb.query with partitionKey: \(partitionKey), " + + "sortKeyCondition: \(sortKeyCondition.debugDescription), and table name \(targetTableName)." self.logger.trace("\(logMessage)") let queryOutput = try await self.dynamodb.query(input: queryInput) diff --git a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeysProjection.swift b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeysProjection.swift index b25f525..85b0860 100644 --- a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeysProjection.swift +++ b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeysProjection.swift @@ -24,8 +24,8 @@ // DynamoDBTables // -import AwsCommonRuntimeKit import AWSDynamoDB +import AwsCommonRuntimeKit import ClientRuntime import Foundation import Logging @@ -42,17 +42,20 @@ public struct AWSDynamoDBCompositePrimaryKeysProjection: DynamoDBCompositePrimar var exclusiveStartKey: String? } - public init(tableName: String, region: Swift.String, - awsCredentialIdentityResolver: (any SmithyIdentity.AWSCredentialIdentityResolver)? = nil, - httpClientConfiguration: ClientRuntime.HttpClientConfiguration? = nil, - tableConfiguration: AWSDynamoDBTableConfiguration = .init(), - logger: Logging.Logger? = nil) throws - { + public init( + tableName: String, + region: Swift.String, + awsCredentialIdentityResolver: (any SmithyIdentity.AWSCredentialIdentityResolver)? = nil, + httpClientConfiguration: ClientRuntime.HttpClientConfiguration? = nil, + tableConfiguration: AWSDynamoDBTableConfiguration = .init(), + logger: Logging.Logger? = nil + ) throws { self.logger = logger ?? Logging.Logger(label: "AWSDynamoDBCompositePrimaryKeysProjection") let config = try DynamoDBClient.DynamoDBClientConfiguration( awsCredentialIdentityResolver: awsCredentialIdentityResolver, region: region, - httpClientConfiguration: httpClientConfiguration) + httpClientConfiguration: httpClientConfiguration + ) self.dynamodb = AWSDynamoDB.DynamoDBClient(config: config) self.tableConfiguration = tableConfiguration self.targetTableName = tableName @@ -60,11 +63,12 @@ public struct AWSDynamoDBCompositePrimaryKeysProjection: DynamoDBCompositePrimar self.logger.trace("AWSDynamoDBCompositePrimaryKeysProjection created with region '\(region)'") } - public init(tableName: String, - client: AWSDynamoDB.DynamoDBClient, - tableConfiguration: AWSDynamoDBTableConfiguration = .init(), - logger: Logging.Logger? = nil) - { + public init( + tableName: String, + client: AWSDynamoDB.DynamoDBClient, + tableConfiguration: AWSDynamoDBTableConfiguration = .init(), + logger: Logging.Logger? = nil + ) { self.logger = logger ?? Logging.Logger(label: "AWSDynamoDBCompositePrimaryKeysProjection") self.dynamodb = client self.tableConfiguration = tableConfiguration diff --git a/Sources/DynamoDBTables/CompositePrimaryKey.swift b/Sources/DynamoDBTables/CompositePrimaryKey.swift index 779f814..5f4f534 100644 --- a/Sources/DynamoDBTables/CompositePrimaryKey.swift +++ b/Sources/DynamoDBTables/CompositePrimaryKey.swift @@ -32,8 +32,8 @@ public protocol PrimaryKeyAttributes { static var indexName: String? { get } } -public extension PrimaryKeyAttributes { - static var indexName: String? { +extension PrimaryKeyAttributes { + public static var indexName: String? { nil } } @@ -69,7 +69,9 @@ struct DynamoDBAttributesTypeCodingKey: CodingKey { } } -public struct CompositePrimaryKey: Sendable, Codable, CustomStringConvertible, Hashable { +public struct CompositePrimaryKey: Sendable, Codable, CustomStringConvertible, + Hashable +{ public var description: String { "CompositePrimaryKey(partitionKey: \(self.partitionKey), sortKey: \(self.sortKey))" } @@ -84,13 +86,25 @@ public struct CompositePrimaryKey: Sendabl public init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: DynamoDBAttributesTypeCodingKey.self) - self.partitionKey = try values.decode(String.self, forKey: DynamoDBAttributesTypeCodingKey(stringValue: AttributesType.partitionKeyAttributeName)!) - self.sortKey = try values.decode(String.self, forKey: DynamoDBAttributesTypeCodingKey(stringValue: AttributesType.sortKeyAttributeName)!) + self.partitionKey = try values.decode( + String.self, + forKey: DynamoDBAttributesTypeCodingKey(stringValue: AttributesType.partitionKeyAttributeName)! + ) + self.sortKey = try values.decode( + String.self, + forKey: DynamoDBAttributesTypeCodingKey(stringValue: AttributesType.sortKeyAttributeName)! + ) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: DynamoDBAttributesTypeCodingKey.self) - try container.encode(self.partitionKey, forKey: DynamoDBAttributesTypeCodingKey(stringValue: AttributesType.partitionKeyAttributeName)!) - try container.encode(self.sortKey, forKey: DynamoDBAttributesTypeCodingKey(stringValue: AttributesType.sortKeyAttributeName)!) + try container.encode( + self.partitionKey, + forKey: DynamoDBAttributesTypeCodingKey(stringValue: AttributesType.partitionKeyAttributeName)! + ) + try container.encode( + self.sortKey, + forKey: DynamoDBAttributesTypeCodingKey(stringValue: AttributesType.sortKeyAttributeName)! + ) } } diff --git a/Sources/DynamoDBTables/CustomRowTypeIdentifier.swift b/Sources/DynamoDBTables/CustomRowTypeIdentifier.swift index 31d92e9..eeb8d9e 100644 --- a/Sources/DynamoDBTables/CustomRowTypeIdentifier.swift +++ b/Sources/DynamoDBTables/CustomRowTypeIdentifier.swift @@ -31,15 +31,15 @@ public protocol CustomRowTypeIdentifier { } func getTypeRowIdentifier(type: Any.Type) -> String { - let typeRowIdentifier: String - // if this type has a custom row identity - = if let customAttributesTypeType = type as? CustomRowTypeIdentifier.Type, - let identifier = customAttributesTypeType.rowTypeIdentifier - { - identifier - } else { - String(describing: type) - } + let typeRowIdentifier: String// if this type has a custom row identity + = + if let customAttributesTypeType = type as? CustomRowTypeIdentifier.Type, + let identifier = customAttributesTypeType.rowTypeIdentifier + { + identifier + } else { + String(describing: type) + } return typeRowIdentifier } diff --git a/Sources/DynamoDBTables/DynamoDBClientProtocol.swift b/Sources/DynamoDBTables/DynamoDBClientProtocol.swift index d38fc11..9857b13 100644 --- a/Sources/DynamoDBTables/DynamoDBClientProtocol.swift +++ b/Sources/DynamoDBTables/DynamoDBClientProtocol.swift @@ -173,7 +173,9 @@ public protocol DynamoDBClientProtocol { /// let batchInput = BatchExecuteStatementInput(statements: statements) /// let response = try await client.batchExecuteStatement(input: batchInput) /// ``` - func batchExecuteStatement(input: AWSDynamoDB.BatchExecuteStatementInput) async throws -> AWSDynamoDB.BatchExecuteStatementOutput + func batchExecuteStatement( + input: AWSDynamoDB.BatchExecuteStatementInput + ) async throws -> AWSDynamoDB.BatchExecuteStatementOutput // MARK: - Advanced Operations @@ -214,7 +216,9 @@ public protocol DynamoDBClientProtocol { /// let transactionInput = ExecuteTransactionInput(transactStatements: statements) /// let response = try await client.executeTransaction(input: transactionInput) /// ``` - func executeTransaction(input: AWSDynamoDB.ExecuteTransactionInput) async throws -> AWSDynamoDB.ExecuteTransactionOutput + func executeTransaction( + input: AWSDynamoDB.ExecuteTransactionInput + ) async throws -> AWSDynamoDB.ExecuteTransactionOutput } // MARK: - AWS DynamoDB Client Conformance diff --git a/Sources/DynamoDBTables/DynamoDBClientTypes_AttributeValue+Codable.swift b/Sources/DynamoDBTables/DynamoDBClientTypes_AttributeValue+Codable.swift index e0f5592..e958038 100644 --- a/Sources/DynamoDBTables/DynamoDBClientTypes_AttributeValue+Codable.swift +++ b/Sources/DynamoDBTables/DynamoDBClientTypes_AttributeValue+Codable.swift @@ -140,7 +140,10 @@ extension DynamoDBClientTypes.AttributeValue: Swift.Codable { self = .bs(bs) return } - let mContainer = try values.decodeIfPresent([Swift.String: DynamoDBClientTypes.AttributeValue?].self, forKey: .m) + let mContainer = try values.decodeIfPresent( + [Swift.String: DynamoDBClientTypes.AttributeValue?].self, + forKey: .m + ) var mDecoded0: [Swift.String: DynamoDBClientTypes.AttributeValue]? = nil if let mContainer { mDecoded0 = [Swift.String: DynamoDBClientTypes.AttributeValue]() diff --git a/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyGSILogic.swift b/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyGSILogic.swift index d3e33fd..d405042 100644 --- a/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyGSILogic.swift +++ b/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyGSILogic.swift @@ -33,14 +33,13 @@ public struct NoOpPolymorphicWriteEntry: P fatalError("Unimplemented") } - public func handle(context _: Context) throws -> Context.WriteEntryTransformType where Context: PolymorphicWriteEntryContext { + public func handle(context _: Context) throws -> Context.WriteEntryTransformType + where Context: PolymorphicWriteEntryContext { fatalError("Unimplemented") } } -/** - A protocol that simulates the logic of a GSI reacting to events on the main table. - */ +/// A protocol that simulates the logic of a GSI reacting to events on the main table. public protocol DynamoDBCompositePrimaryKeyGSILogic { associatedtype GSIAttributesType: PrimaryKeyAttributes associatedtype WriteEntryType: PolymorphicWriteEntry = NoOpPolymorphicWriteEntry @@ -48,35 +47,47 @@ public protocol DynamoDBCompositePrimaryKeyGSILogic { /** * Called when an item is inserted on the main table. Can be used to transform the provided item to the item that would be made available on the GSI. */ - func onInsertItem(_ item: TypedTTLDatabaseItem, - gsiDataStore: InMemoryDynamoDBCompositePrimaryKeyTable) async throws + func onInsertItem( + _ item: TypedTTLDatabaseItem, + gsiDataStore: InMemoryDynamoDBCompositePrimaryKeyTable + ) async throws /** * Called when an item is clobbered on the main table. Can be used to transform the provided item to the item that would be made available on the GSI. */ - func onClobberItem(_ item: TypedTTLDatabaseItem, - gsiDataStore: InMemoryDynamoDBCompositePrimaryKeyTable) async throws + func onClobberItem( + _ item: TypedTTLDatabaseItem, + gsiDataStore: InMemoryDynamoDBCompositePrimaryKeyTable + ) async throws /** * Called when an item is updated on the main table. Can be used to transform the provided item to the item that would be made available on the GSI. */ - func onUpdateItem(newItem: TypedTTLDatabaseItem, - existingItem: TypedTTLDatabaseItem, - gsiDataStore: InMemoryDynamoDBCompositePrimaryKeyTable) async throws + TimeToLiveAttributesType + >( + newItem: TypedTTLDatabaseItem, + existingItem: TypedTTLDatabaseItem, + gsiDataStore: InMemoryDynamoDBCompositePrimaryKeyTable + ) async throws /** * Called when an item is delete on the main table. Can be used to also delete the corresponding item on the GSI. - + */ - func onDeleteItem(forKey key: CompositePrimaryKey, - gsiDataStore: InMemoryDynamoDBCompositePrimaryKeyTable) async throws + func onDeleteItem( + forKey key: CompositePrimaryKey, + gsiDataStore: InMemoryDynamoDBCompositePrimaryKeyTable + ) async throws /** * Called when an transact write in the main table. Can be used to also transact write the corresponding item on the GSI. - + */ - func onTransactWrite(_ entries: [WriteEntryType], - gsiDataStore: InMemoryDynamoDBCompositePrimaryKeyTable) async throws + func onTransactWrite( + _ entries: [WriteEntryType], + gsiDataStore: InMemoryDynamoDBCompositePrimaryKeyTable + ) async throws } diff --git a/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+clobberVersionedItemWithHistoricalRow.swift b/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+clobberVersionedItemWithHistoricalRow.swift index f91c2ae..bf82bf1 100644 --- a/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+clobberVersionedItemWithHistoricalRow.swift +++ b/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+clobberVersionedItemWithHistoricalRow.swift @@ -26,7 +26,7 @@ import Foundation -public extension DynamoDBCompositePrimaryKeyTable { +extension DynamoDBCompositePrimaryKeyTable { /** * This operation provide a mechanism for managing mutable database rows * and storing all previous versions of that row in a historical partition. @@ -34,7 +34,7 @@ public extension DynamoDBCompositePrimaryKeyTable { * with a payload that replicates the current version of the row. This * historical partition contains rows for each version, including the * current version under a sort key for that version. - + - Parameters: - partitionKey: the partition key to use for the primary (v0) item - historicalKey: the partition key to use for the historical items @@ -44,15 +44,23 @@ public extension DynamoDBCompositePrimaryKeyTable { version number. - completion: completion handler providing an error that was thrown or nil */ - func clobberVersionedItemWithHistoricalRow( + public func clobberVersionedItemWithHistoricalRow< + AttributesType: PrimaryKeyAttributes, + ItemType: Codable & Sendable, + TimeToLiveAttributesType: TimeToLiveAttributes + >( forPrimaryKey partitionKey: String, andHistoricalKey historicalKey: String, item: ItemType, primaryKeyType _: AttributesType.Type = StandardPrimaryKeyAttributes.self, timeToLiveAttributesType _: TimeToLiveAttributesType.Type = StandardTimeToLiveAttributes.self, - generateSortKey: @escaping (Int) -> String) async throws - { - func primaryItemProvider(_ existingItem: TypedTTLDatabaseItem, TimeToLiveAttributesType>?) + generateSortKey: @escaping (Int) -> String + ) async throws { + func primaryItemProvider( + _ existingItem: TypedTTLDatabaseItem< + AttributesType, RowWithItemVersion, TimeToLiveAttributesType + >? + ) -> TypedTTLDatabaseItem, TimeToLiveAttributesType> { if let existingItem { @@ -61,26 +69,36 @@ public extension DynamoDBCompositePrimaryKeyTable { // with the payload from the default item. let overWrittenItemRowValue = existingItem.rowValue.createUpdatedItem( withVersion: existingItem.rowValue.itemVersion + 1, - withValue: item) + withValue: item + ) return existingItem.createUpdatedItem(withValue: overWrittenItemRowValue) } // If there is no existing item to be overwritten, a new item should be constructed. let newItemRowValue = RowWithItemVersion.newItem(withValue: item) - let defaultKey = CompositePrimaryKey(partitionKey: partitionKey, sortKey: generateSortKey(0)) + let defaultKey = CompositePrimaryKey( + partitionKey: partitionKey, + sortKey: generateSortKey(0) + ) return TypedTTLDatabaseItem.newItem(withKey: defaultKey, andValue: newItemRowValue) } - func historicalItemProvider(_ primaryItem: TypedTTLDatabaseItem, TimeToLiveAttributesType>) + func historicalItemProvider( + _ primaryItem: TypedTTLDatabaseItem, TimeToLiveAttributesType> + ) -> TypedTTLDatabaseItem, TimeToLiveAttributesType> { let sortKey = generateSortKey(primaryItem.rowValue.itemVersion) - let key = CompositePrimaryKey(partitionKey: historicalKey, - sortKey: sortKey) + let key = CompositePrimaryKey( + partitionKey: historicalKey, + sortKey: sortKey + ) return TypedTTLDatabaseItem.newItem(withKey: key, andValue: primaryItem.rowValue) } - return try await clobberItemWithHistoricalRow(primaryItemProvider: primaryItemProvider, - historicalItemProvider: historicalItemProvider) + return try await clobberItemWithHistoricalRow( + primaryItemProvider: primaryItemProvider, + historicalItemProvider: historicalItemProvider + ) } } diff --git a/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyInTransaction.swift b/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyInTransaction.swift index a28a559..2bcc694 100644 --- a/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyInTransaction.swift +++ b/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyInTransaction.swift @@ -1,4 +1,3 @@ - //===----------------------------------------------------------------------===// // // This source file is part of the DynamoDBTables open source project @@ -28,19 +27,22 @@ import Foundation public enum ConditionalTransactWriteError: Error { - case transactionCanceled(partitionKey: String, sortKey: String, - reasons: [DynamoDBTableError]) + case transactionCanceled( + partitionKey: String, + sortKey: String, + reasons: [DynamoDBTableError] + ) case concurrencyError(keys: [CompositePrimaryKey], message: String?) } -public extension DynamoDBCompositePrimaryKeyTable { +extension DynamoDBCompositePrimaryKeyTable { /** Method to perform a transaction for a set of keys. The `writeEntryProvider` will be called once for each key specified in the input, either with the current item corresponding to that key or nil if the item currently doesn't exist. The `writeEntryProvider` should return the `WriteEntry` for this key in the transaction or nil if the key should not be part of the transaction. The transaction may fail in which case the process repeats until the retry limit has been reached. - + - Parameters: - keys: the item keys to use in the transaction - withRetries: the number of times to attempt to retry the update before failing. @@ -49,21 +51,29 @@ public extension DynamoDBCompositePrimaryKeyTable { - Returns: the list of `WriteEntry` used in the successful transaction */ @discardableResult - func transactWrite( + public func transactWrite( forKeys keys: [CompositePrimaryKey], withRetries retries: Int = 10, constraints: [TransactionConstraintEntry] = [], - writeEntryProvider: @Sendable @escaping (CompositePrimaryKey, TypedTTLDatabaseItem?) - async throws -> WriteEntry?) async throws + writeEntryProvider: @Sendable @escaping ( + CompositePrimaryKey, + TypedTTLDatabaseItem? + ) + async throws -> WriteEntry? + ) async throws -> [WriteEntry] { guard retries > 0 else { - throw ConditionalTransactWriteError.concurrencyError(keys: keys, - message: "Unable to complete conditional transact write in specified number of attempts") + throw ConditionalTransactWriteError.concurrencyError( + keys: keys, + message: "Unable to complete conditional transact write in specified number of attempts" + ) } - let existingItems: [CompositePrimaryKey: TypedTTLDatabaseItem] - = try await getItems(forKeys: keys) + let existingItems: + [CompositePrimaryKey: TypedTTLDatabaseItem< + AttributesType, ItemType, TimeToLiveAttributesType + >] = try await getItems(forKeys: keys) let entries = try await keys.asyncCompactMap { key in try await writeEntryProvider(key, existingItems[key]) @@ -76,9 +86,11 @@ public extension DynamoDBCompositePrimaryKeyTable { } catch DynamoDBTableError.transactionCanceled { // try again return try await self.transactWrite( - forKeys: keys, withRetries: retries - 1, + forKeys: keys, + withRetries: retries - 1, constraints: constraints, - writeEntryProvider: writeEntryProvider) + writeEntryProvider: writeEntryProvider + ) } } @@ -88,7 +100,7 @@ public extension DynamoDBCompositePrimaryKeyTable { item currently doesn't exist. The `writeEntryProvider` should return the `WriteEntry` for this key in the transaction or nil if the key should not be part of the transaction. The transaction may fail in which case the process repeats until the retry limit has been reached. - + - Parameters: - keys: the item keys to use in the transaction - withRetries: the number of times to attempt to retry the update before failing. @@ -96,19 +108,24 @@ public extension DynamoDBCompositePrimaryKeyTable { - Returns: the list of `WriteEntry` used in the successful transaction */ @discardableResult - func polymorphicTransactWrite( + public func polymorphicTransactWrite< + WriteEntryType: PolymorphicWriteEntry, + ReturnedType: PolymorphicOperationReturnType & BatchCapableReturnType + >( forKeys keys: [CompositePrimaryKey], withRetries retries: Int = 10, writeEntryProvider: @Sendable @escaping (CompositePrimaryKey, ReturnedType?) - async throws -> WriteEntryType?) async throws + async throws -> WriteEntryType? + ) async throws -> [WriteEntryType] where WriteEntryType.AttributesType == ReturnedType.AttributesType { let noConstraints: [EmptyPolymorphicTransactionConstraintEntry] = [] - return try await self.polymorphicTransactWrite(forKeys: keys, - withRetries: retries, - constraints: noConstraints, - writeEntryProvider: writeEntryProvider) + return try await self.polymorphicTransactWrite( + forKeys: keys, + withRetries: retries, + constraints: noConstraints, + writeEntryProvider: writeEntryProvider + ) } /** @@ -117,7 +134,7 @@ public extension DynamoDBCompositePrimaryKeyTable { item currently doesn't exist. The `writeEntryProvider` should return the `WriteEntry` for this key in the transaction or nil if the key should not be part of the transaction. The transaction may fail in which case the process repeats until the retry limit has been reached. - + - Parameters: - keys: the item keys to use in the transaction - withRetries: the number of times to attempt to retry the update before failing. @@ -126,24 +143,31 @@ public extension DynamoDBCompositePrimaryKeyTable { - Returns: the list of `WriteEntry` used in the successful transaction */ @discardableResult - func polymorphicTransactWrite( + ReturnedType: PolymorphicOperationReturnType & BatchCapableReturnType + >( forKeys keys: [CompositePrimaryKey], withRetries retries: Int = 10, constraints: [TransactionConstraintEntryType], writeEntryProvider: @Sendable @escaping (CompositePrimaryKey, ReturnedType?) - async throws -> WriteEntryType?) async throws - -> [WriteEntryType] where WriteEntryType.AttributesType == ReturnedType.AttributesType, + async throws -> WriteEntryType? + ) async throws + -> [WriteEntryType] + where + WriteEntryType.AttributesType == ReturnedType.AttributesType, WriteEntryType.AttributesType == TransactionConstraintEntryType.AttributesType { guard retries > 0 else { - throw ConditionalTransactWriteError.concurrencyError(keys: keys, - message: "Unable to complete conditional transact write in specified number of attempts") + throw ConditionalTransactWriteError.concurrencyError( + keys: keys, + message: "Unable to complete conditional transact write in specified number of attempts" + ) } - let existingItems: [CompositePrimaryKey: ReturnedType] - = try await polymorphicGetItems(forKeys: keys) + let existingItems: [CompositePrimaryKey: ReturnedType] = + try await polymorphicGetItems(forKeys: keys) let entries = try await keys.asyncCompactMap { key in try await writeEntryProvider(key, existingItems[key]) @@ -156,9 +180,11 @@ public extension DynamoDBCompositePrimaryKeyTable { } catch DynamoDBTableError.transactionCanceled { // try again return try await self.polymorphicTransactWrite( - forKeys: keys, withRetries: retries - 1, + forKeys: keys, + withRetries: retries - 1, constraints: constraints, - writeEntryProvider: writeEntryProvider) + writeEntryProvider: writeEntryProvider + ) } } } diff --git a/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyUpdateItem.swift b/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyUpdateItem.swift index 1753f07..d607ce3 100644 --- a/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyUpdateItem.swift +++ b/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyUpdateItem.swift @@ -28,7 +28,7 @@ import AWSDynamoDB import Foundation import Logging -public extension DynamoDBCompositePrimaryKeyTable { +extension DynamoDBCompositePrimaryKeyTable { /** Method to conditionally update an item at the specified key for a number of retries. This method is useful for database rows that may be updated simultaneously by different clients @@ -37,27 +37,33 @@ public extension DynamoDBCompositePrimaryKeyTable { generate an updated payload or fail with an error if an updated payload is not valid. If an updated payload is returned, this method will attempt to update the row. This update may fail due to concurrency, in which case the process will repeat until the retry limit has been reached. - + - Parameters: _: the key of the item to update withRetries: the number of times to attempt to retry the update before failing. updatedPayloadProvider: the provider that will return updated payloads. */ - func conditionallyUpdateItem( + public func conditionallyUpdateItem< + AttributesType, + ItemType: Codable & Sendable, + TimeToLiveAttributesType: TimeToLiveAttributes + >( forKey key: CompositePrimaryKey, withRetries retries: Int = 10, timeToLiveAttributesType _: TimeToLiveAttributesType.Type = StandardTimeToLiveAttributes.self, - updatedPayloadProvider: @escaping (ItemType) async throws -> ItemType) async throws - { - let updatedItemProvider: (TypedTTLDatabaseItem) async throws - -> TypedTTLDatabaseItem = { existingItem in - let updatedPayload = try await updatedPayloadProvider(existingItem.rowValue) - return existingItem.createUpdatedItem(withValue: updatedPayload) - } + updatedPayloadProvider: @escaping (ItemType) async throws -> ItemType + ) async throws { + let updatedItemProvider: + (TypedTTLDatabaseItem) async throws + -> TypedTTLDatabaseItem = { existingItem in + let updatedPayload = try await updatedPayloadProvider(existingItem.rowValue) + return existingItem.createUpdatedItem(withValue: updatedPayload) + } try await self.conditionallyUpdateItemInternal( forKey: key, withRetries: retries, - updatedItemProvider: updatedItemProvider) + updatedItemProvider: updatedItemProvider + ) } /** @@ -68,42 +74,58 @@ public extension DynamoDBCompositePrimaryKeyTable { generate an updated row or fail with an error if an updated row is not valid. If an updated row is returned, this method will attempt to update the row. This update may fail due to concurrency, in which case the process will repeat until the retry limit has been reached. - + - Parameters: _: the key of the item to update withRetries: the number of times to attempt to retry the update before failing. updatedItemProvider: the provider that will return updated items. */ - func conditionallyUpdateItem( + public func conditionallyUpdateItem< + AttributesType, + ItemType: Codable, + TimeToLiveAttributesType: TimeToLiveAttributes + >( forKey key: CompositePrimaryKey, withRetries retries: Int = 10, - updatedItemProvider: @escaping (TypedTTLDatabaseItem) async throws - -> TypedTTLDatabaseItem) async throws - { + updatedItemProvider: @escaping (TypedTTLDatabaseItem) + async throws + -> TypedTTLDatabaseItem + ) async throws { try await self.conditionallyUpdateItemInternal( forKey: key, withRetries: retries, - updatedItemProvider: updatedItemProvider) + updatedItemProvider: updatedItemProvider + ) } - private func conditionallyUpdateItemInternal( + private func conditionallyUpdateItemInternal< + AttributesType, + ItemType: Codable, + TimeToLiveAttributesType: TimeToLiveAttributes + >( forKey key: CompositePrimaryKey, withRetries retries: Int = 10, - updatedItemProvider: @escaping (TypedTTLDatabaseItem) async throws - -> TypedTTLDatabaseItem) async throws - { + updatedItemProvider: @escaping (TypedTTLDatabaseItem) + async throws + -> TypedTTLDatabaseItem + ) async throws { guard retries > 0 else { - throw DynamoDBTableError.concurrencyError(partitionKey: key.partitionKey, - sortKey: key.sortKey, - message: "Unable to complete request to update versioned item in specified number of attempts") + throw DynamoDBTableError.concurrencyError( + partitionKey: key.partitionKey, + sortKey: key.sortKey, + message: "Unable to complete request to update versioned item in specified number of attempts" + ) } - let databaseItemOptional: TypedTTLDatabaseItem? = try await getItem(forKey: key) + let databaseItemOptional: TypedTTLDatabaseItem? = + try await getItem(forKey: key) guard let databaseItem = databaseItemOptional else { - throw DynamoDBTableError.conditionalCheckFailed(partitionKey: key.partitionKey, - sortKey: key.sortKey, - message: "Item not present in database.") + throw DynamoDBTableError.conditionalCheckFailed( + partitionKey: key.partitionKey, + sortKey: key.sortKey, + message: "Item not present in database." + ) } let updatedDatabaseItem = try await updatedItemProvider(databaseItem) @@ -112,9 +134,11 @@ public extension DynamoDBCompositePrimaryKeyTable { try await self.updateItem(newItem: updatedDatabaseItem, existingItem: databaseItem) } catch DynamoDBTableError.conditionalCheckFailed { // try again - return try await self.conditionallyUpdateItem(forKey: key, - withRetries: retries - 1, - updatedItemProvider: updatedItemProvider) + return try await self.conditionallyUpdateItem( + forKey: key, + withRetries: retries - 1, + updatedItemProvider: updatedItemProvider + ) } } } diff --git a/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable.swift b/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable.swift index 0fc37f6..2ebfa35 100644 --- a/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable.swift +++ b/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable.swift @@ -28,9 +28,7 @@ import AWSDynamoDB import ClientRuntime import Foundation -/** - Enumeration of the errors that can be thrown by a DynamoDBTable. - */ +/// Enumeration of the errors that can be thrown by a DynamoDBTable. public enum DynamoDBTableError: Error { case accessDenied(message: String?) case internalServerError(message: String?) @@ -61,17 +59,15 @@ public enum DynamoDBTableError: Error { public typealias DynamoDBTableErrorResult = Result -public extension Swift.Error { - func asUnrecognizedDynamoDBTableError() -> DynamoDBTableError { +extension Swift.Error { + public func asUnrecognizedDynamoDBTableError() -> DynamoDBTableError { let errorType = String(describing: type(of: self)) let errorDescription = String(describing: self) return .unrecognizedError(errorType, errorDescription) } } -/** - Enumeration of the types of conditions that can be specified for an attribute. - */ +/// Enumeration of the types of conditions that can be specified for an attribute. public enum AttributeCondition: Sendable { case equals(String) case lessThan(String) @@ -82,16 +78,22 @@ public enum AttributeCondition: Sendable { case beginsWith(String) } -public enum WriteEntry: Sendable { - case update(new: TypedTTLDatabaseItem, - existing: TypedTTLDatabaseItem) +public enum WriteEntry< + AttributesType: PrimaryKeyAttributes, + ItemType: Codable & Sendable, + TimeToLiveAttributesType: TimeToLiveAttributes +>: Sendable { + case update( + new: TypedTTLDatabaseItem, + existing: TypedTTLDatabaseItem + ) case insert(new: TypedTTLDatabaseItem) case deleteAtKey(key: CompositePrimaryKey) case deleteItem(existing: TypedTTLDatabaseItem) public var compositePrimaryKey: CompositePrimaryKey { switch self { - case .update(new: let new, existing: _): + case .update(let new, existing: _): new.compositePrimaryKey case let .insert(new: new): new.compositePrimaryKey @@ -103,7 +105,9 @@ public enum WriteEntry = WriteEntry +public typealias StandardWriteEntry = WriteEntry< + StandardPrimaryKeyAttributes, ItemType, StandardTimeToLiveAttributes +> public protocol DynamoDBCompositePrimaryKeyTable { /** @@ -124,7 +128,8 @@ public protocol DynamoDBCompositePrimaryKeyTable { */ func updateItem( newItem: TypedTTLDatabaseItem, - existingItem: TypedTTLDatabaseItem) async throws + existingItem: TypedTTLDatabaseItem + ) async throws /** * Provides the ability to bulk write database rows in a transaction. @@ -134,7 +139,8 @@ public protocol DynamoDBCompositePrimaryKeyTable { func transactWrite(_ entries: [WriteEntry]) async throws func polymorphicTransactWrite( - _ entries: [some PolymorphicWriteEntry]) async throws + _ entries: [some PolymorphicWriteEntry] + ) async throws /** * Provides the ability to bulk write database rows in a transaction. @@ -145,12 +151,17 @@ public protocol DynamoDBCompositePrimaryKeyTable { */ func transactWrite( _ entries: [WriteEntry], - constraints: [TransactionConstraintEntry]) async throws - - func polymorphicTransactWrite( - _ entries: [WriteEntryType], constraints: [TransactionConstraintEntryType]) async throws - where WriteEntryType.AttributesType == TransactionConstraintEntryType.AttributesType + constraints: [TransactionConstraintEntry] + ) async throws + + func polymorphicTransactWrite< + WriteEntryType: PolymorphicWriteEntry, + TransactionConstraintEntryType: PolymorphicTransactionConstraintEntry + >( + _ entries: [WriteEntryType], + constraints: [TransactionConstraintEntryType] + ) async throws + where WriteEntryType.AttributesType == TransactionConstraintEntryType.AttributesType /** * Provides the ability to bulk write database rows @@ -164,14 +175,17 @@ public protocol DynamoDBCompositePrimaryKeyTable { /** * Retrieves an item from the database table. Returns nil if the item doesn't exist. */ - func getItem(forKey key: CompositePrimaryKey) async throws + func getItem( + forKey key: CompositePrimaryKey + ) async throws -> TypedTTLDatabaseItem? /** * Retrieves items from the database table as a dictionary mapped to the provided key. Missing entries from the provided map indicate that item doesn't exist. */ func polymorphicGetItems( - forKeys keys: [CompositePrimaryKey]) async throws + forKeys keys: [CompositePrimaryKey] + ) async throws -> [CompositePrimaryKey: ReturnedType] /** @@ -206,8 +220,10 @@ public protocol DynamoDBCompositePrimaryKeyTable { function will potentially make multiple calls to DynamoDB to retrieve all results for the query. */ - func polymorphicQuery(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?) async throws + func polymorphicQuery( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition? + ) async throws -> [ReturnedType] /** @@ -215,10 +231,12 @@ public protocol DynamoDBCompositePrimaryKeyTable { partition doesn't exist, this operation will return an empty list as a response. This function will return paginated results based on the limit and exclusiveStartKey provided. */ - func polymorphicQuery(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - exclusiveStartKey: String?) async throws + func polymorphicQuery( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + exclusiveStartKey: String? + ) async throws -> (items: [ReturnedType], lastEvaluatedKey: String?) /** @@ -226,11 +244,13 @@ public protocol DynamoDBCompositePrimaryKeyTable { partition doesn't exist, this operation will return an empty list as a response. This function will return paginated results based on the limit and exclusiveStartKey provided. */ - func polymorphicQuery(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - scanIndexForward: Bool, - exclusiveStartKey: String?) async throws + func polymorphicQuery( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + scanIndexForward: Bool, + exclusiveStartKey: String? + ) async throws -> (items: [ReturnedType], lastEvaluatedKey: String?) /** @@ -243,7 +263,8 @@ public protocol DynamoDBCompositePrimaryKeyTable { func polymorphicExecute( partitionKeys: [String], attributesFilter: [String]?, - additionalWhereClause: String?) async throws -> [ReturnedType] + additionalWhereClause: String? + ) async throws -> [ReturnedType] /** * Uses the ExecuteStatement API to to perform batch reads or writes on data stored in DynamoDB, using PartiQL. @@ -255,7 +276,9 @@ public protocol DynamoDBCompositePrimaryKeyTable { func polymorphicExecute( partitionKeys: [String], attributesFilter: [String]?, - additionalWhereClause: String?, nextToken: String?) async throws + additionalWhereClause: String?, + nextToken: String? + ) async throws -> (items: [ReturnedType], lastEvaluatedKey: String?) // MARK: Monomorphic batch and queries @@ -264,8 +287,11 @@ public protocol DynamoDBCompositePrimaryKeyTable { * Retrieves items from the database table as a dictionary mapped to the provided key. Missing entries from the provided map indicate that item doesn't exist. */ func getItems( - forKeys keys: [CompositePrimaryKey]) async throws - -> [CompositePrimaryKey: TypedTTLDatabaseItem] + forKeys keys: [CompositePrimaryKey] + ) async throws + -> [CompositePrimaryKey: TypedTTLDatabaseItem< + AttributesType, ItemType, TimeToLiveAttributesType + >] /** * Queries a partition in the database table and optionally a sort key condition. If the @@ -273,8 +299,10 @@ public protocol DynamoDBCompositePrimaryKeyTable { function will potentially make multiple calls to DynamoDB to retrieve all results for the query. */ - func query(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?) async throws + func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition? + ) async throws -> [TypedTTLDatabaseItem] /** @@ -287,8 +315,11 @@ public protocol DynamoDBCompositePrimaryKeyTable { sortKeyCondition: AttributeCondition?, limit: Int?, scanIndexForward: Bool, - exclusiveStartKey: String?) async throws - -> (items: [TypedTTLDatabaseItem], lastEvaluatedKey: String?) + exclusiveStartKey: String? + ) async throws + -> ( + items: [TypedTTLDatabaseItem], lastEvaluatedKey: String? + ) /** * Uses the ExecuteStatement API to to perform batch reads or writes on data stored in DynamoDB, using PartiQL. @@ -300,7 +331,8 @@ public protocol DynamoDBCompositePrimaryKeyTable { func execute( partitionKeys: [String], attributesFilter: [String]?, - additionalWhereClause: String?) async throws -> [TypedTTLDatabaseItem] + additionalWhereClause: String? + ) async throws -> [TypedTTLDatabaseItem] /** * Uses the ExecuteStatement API to to perform batch reads or writes on data stored in DynamoDB, using PartiQL. @@ -312,6 +344,10 @@ public protocol DynamoDBCompositePrimaryKeyTable { func execute( partitionKeys: [String], attributesFilter: [String]?, - additionalWhereClause: String?, nextToken: String?) async throws - -> (items: [TypedTTLDatabaseItem], lastEvaluatedKey: String?) + additionalWhereClause: String?, + nextToken: String? + ) async throws + -> ( + items: [TypedTTLDatabaseItem], lastEvaluatedKey: String? + ) } diff --git a/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTableHistoricalItemExtensions.swift b/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTableHistoricalItemExtensions.swift index 94cdc17..e364391 100644 --- a/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTableHistoricalItemExtensions.swift +++ b/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTableHistoricalItemExtensions.swift @@ -29,24 +29,24 @@ import AWSDynamoDB import Foundation import Logging -public extension DynamoDBCompositePrimaryKeyTable { +extension DynamoDBCompositePrimaryKeyTable { /** * Historical items exist across multiple rows. This method provides an interface to record all * rows in a single call. */ - func insertItemWithHistoricalRow( + public func insertItemWithHistoricalRow( primaryItem: TypedTTLDatabaseItem, - historicalItem: TypedTTLDatabaseItem) async throws - { + historicalItem: TypedTTLDatabaseItem + ) async throws { try await insertItem(primaryItem) try await insertItem(historicalItem) } - func updateItemWithHistoricalRow( + public func updateItemWithHistoricalRow( primaryItem: TypedTTLDatabaseItem, existingItem: TypedTTLDatabaseItem, - historicalItem: TypedTTLDatabaseItem) async throws - { + historicalItem: TypedTTLDatabaseItem + ) async throws { try await updateItem(newItem: primaryItem, existingItem: existingItem) try await insertItem(historicalItem) } @@ -63,44 +63,57 @@ public extension DynamoDBCompositePrimaryKeyTable { * Clobbering a historical item requires knowledge of existing rows to accurately record * historical data. */ - func clobberItemWithHistoricalRow( + public func clobberItemWithHistoricalRow( primaryItemProvider: @escaping (TypedTTLDatabaseItem?) -> TypedTTLDatabaseItem, historicalItemProvider: @escaping (TypedTTLDatabaseItem) -> TypedTTLDatabaseItem, - withRetries retries: Int = 10) async throws - { + withRetries retries: Int = 10 + ) async throws { let primaryItem = primaryItemProvider(nil) guard retries > 0 else { - throw DynamoDBTableError.concurrencyError(partitionKey: primaryItem.compositePrimaryKey.partitionKey, - sortKey: primaryItem.compositePrimaryKey.sortKey, - message: "Unable to complete request to clobber versioned item in specified number of attempts") + throw DynamoDBTableError.concurrencyError( + partitionKey: primaryItem.compositePrimaryKey.partitionKey, + sortKey: primaryItem.compositePrimaryKey.sortKey, + message: "Unable to complete request to clobber versioned item in specified number of attempts" + ) } let existingItemOptional: TypedTTLDatabaseItem? = try await getItem(forKey: primaryItem.compositePrimaryKey) if let existingItem = existingItemOptional { - let newItem: TypedTTLDatabaseItem = primaryItemProvider(existingItem) + let newItem: TypedTTLDatabaseItem = primaryItemProvider( + existingItem + ) do { - try await self.updateItemWithHistoricalRow(primaryItem: newItem, existingItem: existingItem, - historicalItem: historicalItemProvider(newItem)) + try await self.updateItemWithHistoricalRow( + primaryItem: newItem, + existingItem: existingItem, + historicalItem: historicalItemProvider(newItem) + ) } catch { - try await self.clobberItemWithHistoricalRow(primaryItemProvider: primaryItemProvider, - historicalItemProvider: historicalItemProvider, - withRetries: retries - 1) + try await self.clobberItemWithHistoricalRow( + primaryItemProvider: primaryItemProvider, + historicalItemProvider: historicalItemProvider, + withRetries: retries - 1 + ) return } } else { do { - try await self.insertItemWithHistoricalRow(primaryItem: primaryItem, - historicalItem: historicalItemProvider(primaryItem)) + try await self.insertItemWithHistoricalRow( + primaryItem: primaryItem, + historicalItem: historicalItemProvider(primaryItem) + ) } catch { - try await self.clobberItemWithHistoricalRow(primaryItemProvider: primaryItemProvider, - historicalItemProvider: historicalItemProvider, - withRetries: retries - 1) + try await self.clobberItemWithHistoricalRow( + primaryItemProvider: primaryItemProvider, + historicalItemProvider: historicalItemProvider, + withRetries: retries - 1 + ) return } } @@ -114,64 +127,76 @@ public extension DynamoDBCompositePrimaryKeyTable { row is unable to be updated. The `historicalItemProvider` is called to provide the historical item based on the primary item that was inserted into the database table. - + - Parameters: - compositePrimaryKey: The composite key for the version to update. - primaryItemProvider: Function to provide the updated item or throw if the current item can't be updated. - historicalItemProvider: Function to provide the historical item for the primary item. */ - func conditionallyUpdateItemWithHistoricalRow( + public func conditionallyUpdateItemWithHistoricalRow( compositePrimaryKey: CompositePrimaryKey, - primaryItemProvider: @escaping (TypedTTLDatabaseItem) async throws -> + primaryItemProvider: @escaping (TypedTTLDatabaseItem) + async throws -> TypedTTLDatabaseItem, historicalItemProvider: @escaping (TypedTTLDatabaseItem) -> TypedTTLDatabaseItem, - withRetries retries: Int = 10) async throws -> TypedTTLDatabaseItem - { + withRetries retries: Int = 10 + ) async throws -> TypedTTLDatabaseItem { try await self.conditionallyUpdateItemWithHistoricalRowInternal( compositePrimaryKey: compositePrimaryKey, primaryItemProvider: primaryItemProvider, historicalItemProvider: historicalItemProvider, - withRetries: retries) + withRetries: retries + ) } private func conditionallyUpdateItemWithHistoricalRowInternal( compositePrimaryKey: CompositePrimaryKey, - primaryItemProvider: @escaping (TypedTTLDatabaseItem) async throws -> + primaryItemProvider: @escaping (TypedTTLDatabaseItem) + async throws -> TypedTTLDatabaseItem, historicalItemProvider: @escaping (TypedTTLDatabaseItem) -> TypedTTLDatabaseItem, - withRetries retries: Int = 10) async throws -> TypedTTLDatabaseItem - { + withRetries retries: Int = 10 + ) async throws -> TypedTTLDatabaseItem { guard retries > 0 else { - throw DynamoDBTableError.concurrencyError(partitionKey: compositePrimaryKey.partitionKey, - sortKey: compositePrimaryKey.sortKey, - message: "Unable to complete request to update versioned item in specified number of attempts") + throw DynamoDBTableError.concurrencyError( + partitionKey: compositePrimaryKey.partitionKey, + sortKey: compositePrimaryKey.sortKey, + message: "Unable to complete request to update versioned item in specified number of attempts" + ) } let existingItemOptional: TypedTTLDatabaseItem? = try await getItem(forKey: compositePrimaryKey) guard let existingItem = existingItemOptional else { - throw DynamoDBTableError.conditionalCheckFailed(partitionKey: compositePrimaryKey.partitionKey, - sortKey: compositePrimaryKey.sortKey, - message: "Item not present in database.") + throw DynamoDBTableError.conditionalCheckFailed( + partitionKey: compositePrimaryKey.partitionKey, + sortKey: compositePrimaryKey.sortKey, + message: "Item not present in database." + ) } let updatedItem = try await primaryItemProvider(existingItem) let historicalItem = historicalItemProvider(updatedItem) do { - try await self.updateItemWithHistoricalRow(primaryItem: updatedItem, - existingItem: existingItem, - historicalItem: historicalItem) + try await self.updateItemWithHistoricalRow( + primaryItem: updatedItem, + existingItem: existingItem, + historicalItem: historicalItem + ) return updatedItem } catch DynamoDBTableError.conditionalCheckFailed { // try again - return try await self.conditionallyUpdateItemWithHistoricalRowInternal(compositePrimaryKey: compositePrimaryKey, - primaryItemProvider: primaryItemProvider, - historicalItemProvider: historicalItemProvider, withRetries: retries - 1) + return try await self.conditionallyUpdateItemWithHistoricalRowInternal( + compositePrimaryKey: compositePrimaryKey, + primaryItemProvider: primaryItemProvider, + historicalItemProvider: historicalItemProvider, + withRetries: retries - 1 + ) } } } diff --git a/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeysProjection.swift b/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeysProjection.swift index dcf4818..ae31fa5 100644 --- a/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeysProjection.swift +++ b/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeysProjection.swift @@ -27,10 +27,8 @@ import AWSDynamoDB import Foundation -/** - Protocol presenting a Keys Only projection of a DynamoDB table such as a Keys Only GSI projection. - Provides the ability to query the projection to get the list of keys without attempting to decode the row into a particular data type. - */ +/// Protocol presenting a Keys Only projection of a DynamoDB table such as a Keys Only GSI projection. +/// Provides the ability to query the projection to get the list of keys without attempting to decode the row into a particular data type. public protocol DynamoDBCompositePrimaryKeysProjection { /** * Queries a partition in the database table and optionally a sort key condition. If the @@ -38,8 +36,10 @@ public protocol DynamoDBCompositePrimaryKeysProjection { function will potentially make multiple calls to DynamoDB to retrieve all results for the query. */ - func query(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?) async throws + func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition? + ) async throws -> [CompositePrimaryKey] /** @@ -47,16 +47,20 @@ public protocol DynamoDBCompositePrimaryKeysProjection { partition doesn't exist, this operation will return an empty list as a response. This function will return paginated results based on the limit and exclusiveStartKey provided. */ - func query(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - exclusiveStartKey: String?) async throws + func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + exclusiveStartKey: String? + ) async throws -> (keys: [CompositePrimaryKey], lastEvaluatedKey: String?) - func query(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - scanIndexForward: Bool, - exclusiveStartKey: String?) async throws + func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + scanIndexForward: Bool, + exclusiveStartKey: String? + ) async throws -> (keys: [CompositePrimaryKey], lastEvaluatedKey: String?) } diff --git a/Sources/DynamoDBTables/DynamoDBDecoder.swift b/Sources/DynamoDBTables/DynamoDBDecoder.swift index af8fe46..b083ba3 100644 --- a/Sources/DynamoDBTables/DynamoDBDecoder.swift +++ b/Sources/DynamoDBTables/DynamoDBDecoder.swift @@ -34,13 +34,18 @@ public class DynamoDBDecoder { self.attributeNameTransform = attributeNameTransform } - public func decode(_ value: DynamoDBClientTypes.AttributeValue, userInfo: [CodingUserInfoKey: Any] = [:]) throws + public func decode( + _ value: DynamoDBClientTypes.AttributeValue, + userInfo: [CodingUserInfoKey: Any] = [:] + ) throws -> T { - let container = InternalSingleValueDecodingContainer(attributeValue: value, - codingPath: [], - userInfo: userInfo, - attributeNameTransform: attributeNameTransform) + let container = InternalSingleValueDecodingContainer( + attributeValue: value, + codingPath: [], + userInfo: userInfo, + attributeNameTransform: attributeNameTransform + ) return try T(from: container) } diff --git a/Sources/DynamoDBTables/DynamoDBEncoder.swift b/Sources/DynamoDBTables/DynamoDBEncoder.swift index 44fcb10..1a904fc 100644 --- a/Sources/DynamoDBTables/DynamoDBEncoder.swift +++ b/Sources/DynamoDBTables/DynamoDBEncoder.swift @@ -34,11 +34,16 @@ public class DynamoDBEncoder { self.attributeNameTransform = attributeNameTransform } - public func encode(_ value: some Swift.Encodable, userInfo: [CodingUserInfoKey: Any] = [:]) throws -> DynamoDBClientTypes.AttributeValue { - let container = InternalSingleValueEncodingContainer(userInfo: userInfo, - codingPath: [], - attributeNameTransform: attributeNameTransform, - defaultValue: nil) + public func encode( + _ value: some Swift.Encodable, + userInfo: [CodingUserInfoKey: Any] = [:] + ) throws -> DynamoDBClientTypes.AttributeValue { + let container = InternalSingleValueEncodingContainer( + userInfo: userInfo, + codingPath: [], + attributeNameTransform: attributeNameTransform, + defaultValue: nil + ) try value.encode(to: container) return container.attributeValue diff --git a/Sources/DynamoDBTables/InMemoryDataRepresentations.swift b/Sources/DynamoDBTables/InMemoryDataRepresentations.swift index 6ae2d07..1057b0d 100644 --- a/Sources/DynamoDBTables/InMemoryDataRepresentations.swift +++ b/Sources/DynamoDBTables/InMemoryDataRepresentations.swift @@ -27,10 +27,8 @@ import Foundation without storing the potentially non-Sendable row types. */ -/** - Representation of a `DatabaseItem` when stored in `InMemoryDynamoDBCompositePrimaryKeyTableStore`. - This type stores the serialised `DynamoDBClientTypes.AttributeValue` map - */ +/// Representation of a `DatabaseItem` when stored in `InMemoryDynamoDBCompositePrimaryKeyTableStore`. +/// This type stores the serialised `DynamoDBClientTypes.AttributeValue` map public struct InMemoryDatabaseItem: Sendable { public var item: [Swift.String: DynamoDBClientTypes.AttributeValue] var metadata: DatabaseItemMetadata @@ -133,8 +131,10 @@ extension TypedTTLDatabaseItem { func inMemoryFormWithKey() throws -> InMemoryDatabaseItemWithKey { - try InMemoryDatabaseItemWithKey(compositePrimaryKey: self.compositePrimaryKey, - inMemoryDatabaseItem: self.inMemoryForm()) + try InMemoryDatabaseItemWithKey( + compositePrimaryKey: self.compositePrimaryKey, + inMemoryDatabaseItem: self.inMemoryForm() + ) } } @@ -146,7 +146,7 @@ enum InMemoryWriteEntry: Sendable { var compositePrimaryKey: CompositePrimaryKey { switch self { - case .update(new: let new, existing: _): + case .update(let new, existing: _): new.compositePrimaryKey case let .insert(new: new): new.compositePrimaryKey diff --git a/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+execute.swift b/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+execute.swift index c8932d3..bea4b66 100644 --- a/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+execute.swift +++ b/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+execute.swift @@ -19,13 +19,16 @@ import Foundation // MARK: - Execute implementations -public extension InMemoryDynamoDBCompositePrimaryKeyTable { - func polymorphicExecute( +extension InMemoryDynamoDBCompositePrimaryKeyTable { + public func polymorphicExecute( partitionKeys: [String], attributesFilter _: [String]?, - additionalWhereClause: String?) async throws -> [ReturnedType] - { - let items = await self.getExecuteItems(partitionKeys: partitionKeys, additionalWhereClause: additionalWhereClause) + additionalWhereClause: String? + ) async throws -> [ReturnedType] { + let items = await self.getExecuteItems( + partitionKeys: partitionKeys, + additionalWhereClause: additionalWhereClause + ) let returnedItems: [ReturnedType] = try items.map { item in try self.convertToQueryableType(input: item) @@ -34,13 +37,18 @@ public extension InMemoryDynamoDBCompositePrimaryKeyTable { return returnedItems } - func polymorphicExecute( + public func polymorphicExecute( partitionKeys: [String], attributesFilter _: [String]?, - additionalWhereClause: String?, nextToken _: String?) async throws + additionalWhereClause: String?, + nextToken _: String? + ) async throws -> (items: [ReturnedType], lastEvaluatedKey: String?) { - let items = await self.getExecuteItems(partitionKeys: partitionKeys, additionalWhereClause: additionalWhereClause) + let items = await self.getExecuteItems( + partitionKeys: partitionKeys, + additionalWhereClause: additionalWhereClause + ) let returnedItems: [ReturnedType] = try items.map { item in try self.convertToQueryableType(input: item) @@ -49,19 +57,30 @@ public extension InMemoryDynamoDBCompositePrimaryKeyTable { return (returnedItems, nil) } - func execute( + public func execute( partitionKeys: [String], attributesFilter _: [String]?, - additionalWhereClause: String?) async throws -> [TypedTTLDatabaseItem] - { - let items = await self.getExecuteItems(partitionKeys: partitionKeys, additionalWhereClause: additionalWhereClause) - - let returnedItems: [TypedTTLDatabaseItem] = try items.map { item in - guard let typedItem: TypedTTLDatabaseItem = try item.getItem() else { + additionalWhereClause: String? + ) async throws -> [TypedTTLDatabaseItem] { + let items = await self.getExecuteItems( + partitionKeys: partitionKeys, + additionalWhereClause: additionalWhereClause + ) + + let returnedItems: [TypedTTLDatabaseItem] = try items.map { + item in + guard + let typedItem: TypedTTLDatabaseItem = + try item.getItem() + else { let foundType = type(of: item) - let description = "Expected to decode \(TypedTTLDatabaseItem.self). Instead found \(foundType)." + let description = + "Expected to decode \(TypedTTLDatabaseItem.self). Instead found \(foundType)." let context = DecodingError.Context(codingPath: [], debugDescription: description) - let error = DecodingError.typeMismatch(TypedTTLDatabaseItem.self, context) + let error = DecodingError.typeMismatch( + TypedTTLDatabaseItem.self, + context + ) throw error } @@ -72,20 +91,35 @@ public extension InMemoryDynamoDBCompositePrimaryKeyTable { return returnedItems } - func execute( + public func execute( partitionKeys: [String], attributesFilter _: [String]?, - additionalWhereClause: String?, nextToken _: String?) async throws - -> (items: [TypedTTLDatabaseItem], lastEvaluatedKey: String?) + additionalWhereClause: String?, + nextToken _: String? + ) async throws + -> ( + items: [TypedTTLDatabaseItem], lastEvaluatedKey: String? + ) { - let items = await self.getExecuteItems(partitionKeys: partitionKeys, additionalWhereClause: additionalWhereClause) - - let returnedItems: [TypedTTLDatabaseItem] = try items.map { item in - guard let typedItem: TypedTTLDatabaseItem = try item.getItem() else { + let items = await self.getExecuteItems( + partitionKeys: partitionKeys, + additionalWhereClause: additionalWhereClause + ) + + let returnedItems: [TypedTTLDatabaseItem] = try items.map { + item in + guard + let typedItem: TypedTTLDatabaseItem = + try item.getItem() + else { let foundType = type(of: item) - let description = "Expected to decode \(TypedTTLDatabaseItem.self). Instead found \(foundType)." + let description = + "Expected to decode \(TypedTTLDatabaseItem.self). Instead found \(foundType)." let context = DecodingError.Context(codingPath: [], debugDescription: description) - let error = DecodingError.typeMismatch(TypedTTLDatabaseItem.self, context) + let error = DecodingError.typeMismatch( + TypedTTLDatabaseItem.self, + context + ) throw error } @@ -96,9 +130,10 @@ public extension InMemoryDynamoDBCompositePrimaryKeyTable { return (returnedItems, nil) } - internal func getExecuteItems(partitionKeys: [String], - additionalWhereClause: String?) async -> [InMemoryDatabaseItem] - { + internal func getExecuteItems( + partitionKeys: [String], + additionalWhereClause: String? + ) async -> [InMemoryDatabaseItem] { let store = await self.store var items: [InMemoryDatabaseItem] = [] @@ -118,7 +153,9 @@ public extension InMemoryDynamoDBCompositePrimaryKeyTable { items.append(databaseItem) } } else { - fatalError("An executeItemFilter must be provided when an excute call includes an additionalWhereClause") + fatalError( + "An executeItemFilter must be provided when an excute call includes an additionalWhereClause" + ) } } else { // otherwise just add the item diff --git a/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift b/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift index 792a169..28b3914 100644 --- a/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift +++ b/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift @@ -19,14 +19,20 @@ import Foundation // MARK: - Query implementations -public extension InMemoryDynamoDBCompositePrimaryKeyTable { - func getItems( - forKeys keys: [CompositePrimaryKey]) async throws - -> [CompositePrimaryKey: TypedTTLDatabaseItem] +extension InMemoryDynamoDBCompositePrimaryKeyTable { + public func getItems( + forKeys keys: [CompositePrimaryKey] + ) async throws + -> [CompositePrimaryKey: TypedTTLDatabaseItem< + AttributesType, ItemType, TimeToLiveAttributesType + >] { let items = await self.getInMemoryDatabaseItems(forKeys: keys) - var map: [CompositePrimaryKey: TypedTTLDatabaseItem] = [:] + var map: + [CompositePrimaryKey: TypedTTLDatabaseItem< + AttributesType, ItemType, TimeToLiveAttributesType + >] = [:] try items.forEach { key, value in map[key] = try DynamoDBDecoder().decode(DynamoDBClientTypes.AttributeValue.m(value.item)) @@ -35,7 +41,9 @@ public extension InMemoryDynamoDBCompositePrimaryKeyTable { return map } - private func getInMemoryDatabaseItems(forKeys keys: [CompositePrimaryKey]) async + private func getInMemoryDatabaseItems( + forKeys keys: [CompositePrimaryKey] + ) async -> [CompositePrimaryKey: InMemoryDatabaseItem] { var map: [CompositePrimaryKey: InMemoryDatabaseItem] = [:] @@ -55,8 +63,9 @@ public extension InMemoryDynamoDBCompositePrimaryKeyTable { return map } - func polymorphicGetItems( - forKeys keys: [CompositePrimaryKey]) async throws + public func polymorphicGetItems( + forKeys keys: [CompositePrimaryKey] + ) async throws -> [CompositePrimaryKey: ReturnedType] { let store = await self.store @@ -72,8 +81,10 @@ public extension InMemoryDynamoDBCompositePrimaryKeyTable { return resultMap } - func polymorphicQuery(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?) async throws + public func polymorphicQuery( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition? + ) async throws -> [ReturnedType] { var items: [ReturnedType] = [] @@ -131,35 +142,44 @@ public extension InMemoryDynamoDBCompositePrimaryKeyTable { return items } - func polymorphicQuery(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - exclusiveStartKey: String?) async throws + public func polymorphicQuery( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + exclusiveStartKey: String? + ) async throws -> (items: [ReturnedType], lastEvaluatedKey: String?) { - try await self.polymorphicQuery(forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition, - limit: limit, - scanIndexForward: true, - exclusiveStartKey: exclusiveStartKey) + try await self.polymorphicQuery( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition, + limit: limit, + scanIndexForward: true, + exclusiveStartKey: exclusiveStartKey + ) } - func polymorphicQuery(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - scanIndexForward: Bool, - exclusiveStartKey: String?) async throws + public func polymorphicQuery( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + scanIndexForward: Bool, + exclusiveStartKey: String? + ) async throws -> (items: [ReturnedType], lastEvaluatedKey: String?) { // get all the results - let rawItems: [ReturnedType] = try await polymorphicQuery(forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition) - - let items: [ReturnedType] = if !scanIndexForward { - rawItems.reversed() - } else { - rawItems - } + let rawItems: [ReturnedType] = try await polymorphicQuery( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition + ) + + let items: [ReturnedType] = + if !scanIndexForward { + rawItems.reversed() + } else { + rawItems + } let startIndex: Int // if there is an exclusiveStartKey @@ -183,11 +203,13 @@ public extension InMemoryDynamoDBCompositePrimaryKeyTable { lastEvaluatedKey = nil } - return (Array(items[startIndex ..< endIndex]), lastEvaluatedKey) + return (Array(items[startIndex..(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?) async throws + public func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition? + ) async throws -> [TypedTTLDatabaseItem] { var items: [TypedTTLDatabaseItem] = [] @@ -238,14 +260,20 @@ public extension InMemoryDynamoDBCompositePrimaryKeyTable { } } - if let typedValue: TypedTTLDatabaseItem = try value.getItem() { + if let typedValue: TypedTTLDatabaseItem = + try value.getItem() + { items.append(typedValue) } else { - let description = "Expected type \(TypedTTLDatabaseItem.self), " + let description = + "Expected type \(TypedTTLDatabaseItem.self), " + " was \(type(of: value))." let context = DecodingError.Context(codingPath: [], debugDescription: description) - throw DecodingError.typeMismatch(TypedTTLDatabaseItem.self, context) + throw DecodingError.typeMismatch( + TypedTTLDatabaseItem.self, + context + ) } } } @@ -253,22 +281,29 @@ public extension InMemoryDynamoDBCompositePrimaryKeyTable { return items } - func query(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - scanIndexForward: Bool, - exclusiveStartKey: String?) async throws - -> (items: [TypedTTLDatabaseItem], lastEvaluatedKey: String?) + public func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + scanIndexForward: Bool, + exclusiveStartKey: String? + ) async throws + -> ( + items: [TypedTTLDatabaseItem], lastEvaluatedKey: String? + ) { // get all the results - let rawItems: [TypedTTLDatabaseItem] = try await query(forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition) - - let items: [TypedTTLDatabaseItem] = if !scanIndexForward { - rawItems.reversed() - } else { - rawItems - } + let rawItems: [TypedTTLDatabaseItem] = try await query( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition + ) + + let items: [TypedTTLDatabaseItem] = + if !scanIndexForward { + rawItems.reversed() + } else { + rawItems + } let startIndex: Int // if there is an exclusiveStartKey @@ -292,6 +327,6 @@ public extension InMemoryDynamoDBCompositePrimaryKeyTable { lastEvaluatedKey = nil } - return (Array(items[startIndex ..< endIndex]), lastEvaluatedKey) + return (Array(items[startIndex..]) async throws - { +extension InMemoryDynamoDBCompositePrimaryKeyTable { + public func transactWrite( + _ entries: [WriteEntry] + ) async throws { try await self.transactWrite(entries, constraints: []) } - func transactWrite( + public func transactWrite( _ entries: [WriteEntry], - constraints: [TransactionConstraintEntry]) async throws - { + constraints: [TransactionConstraintEntry] + ) async throws { // if there is a transaction delegate and it wants to inject errors let inputKeys = entries.map(\.compositePrimaryKey) + constraints.map(\.compositePrimaryKey) - if let errors = try await transactionDelegate?.injectErrors(inputKeys: inputKeys, table: self), !errors.isEmpty { + if let errors = try await transactionDelegate?.injectErrors(inputKeys: inputKeys, table: self), !errors.isEmpty + { throw DynamoDBTableError.transactionCanceled(reasons: errors) } @@ -44,54 +45,74 @@ public extension InMemoryDynamoDBCompositePrimaryKeyTable { } } - func polymorphicTransactWrite(_ entries: [WriteEntryType]) async throws { + public func polymorphicTransactWrite( + _ entries: [WriteEntryType] + ) async throws { let noConstraints: [EmptyPolymorphicTransactionConstraintEntry] = [] return try await self.polymorphicTransactWrite(entries, constraints: noConstraints) } - func polymorphicTransactWrite( - _ entries: [WriteEntryType], constraints: [TransactionConstraintEntryType]) async throws - where WriteEntryType.AttributesType == TransactionConstraintEntryType.AttributesType - { + public func polymorphicTransactWrite< + WriteEntryType: PolymorphicWriteEntry, + TransactionConstraintEntryType: PolymorphicTransactionConstraintEntry + >( + _ entries: [WriteEntryType], + constraints: [TransactionConstraintEntryType] + ) async throws + where WriteEntryType.AttributesType == TransactionConstraintEntryType.AttributesType { // if there is a transaction delegate and it wants to inject errors let inputKeys = entries.map(\.compositePrimaryKey) + constraints.map(\.compositePrimaryKey) - if let errors = try await transactionDelegate?.injectErrors(inputKeys: inputKeys, table: self), !errors.isEmpty { + if let errors = try await transactionDelegate?.injectErrors(inputKeys: inputKeys, table: self), !errors.isEmpty + { throw DynamoDBTableError.transactionCanceled(reasons: errors) } - let context = StandardPolymorphicWriteEntryContext(table: self) + let context = StandardPolymorphicWriteEntryContext< + InMemoryPolymorphicWriteEntryTransform, + InMemoryPolymorphicTransactionConstraintTransform + >(table: self) let entryTransformResults = entries.asInMemoryTransforms(context: context) let contraintTransformResults = constraints.asInMemoryTransforms(context: context) try await self.storeWrapper.execute { store in - try self.polymorphicBulkWrite(entryTransformResults, constraintTransformResults: contraintTransformResults, - store: &store, context: context, isTransaction: true) + try self.polymorphicBulkWrite( + entryTransformResults, + constraintTransformResults: contraintTransformResults, + store: &store, + context: context, + isTransaction: true + ) } } - func polymorphicBulkWrite(_ entries: [some PolymorphicWriteEntry]) async throws { - let context = StandardPolymorphicWriteEntryContext(table: self) + public func polymorphicBulkWrite(_ entries: [some PolymorphicWriteEntry]) async throws { + let context = StandardPolymorphicWriteEntryContext< + InMemoryPolymorphicWriteEntryTransform, + InMemoryPolymorphicTransactionConstraintTransform + >(table: self) let entryTransformResults = entries.asInMemoryTransforms(context: context) try await self.storeWrapper.execute { store in - try self.polymorphicBulkWrite(entryTransformResults, constraintTransformResults: [], - store: &store, context: context, isTransaction: false) + try self.polymorphicBulkWrite( + entryTransformResults, + constraintTransformResults: [], + store: &store, + context: context, + isTransaction: false + ) } } - func bulkWrite(_ entries: [WriteEntry]) async throws { + public func bulkWrite(_ entries: [WriteEntry]) async throws { let inMemoryEntries = try entries.map { try $0.inMemoryForm() } try await self.storeWrapper.execute { store in try self.bulkWrite(inMemoryEntries, constraints: [], store: &store, isTransaction: false) } } - func bulkWriteWithFallback(_ entries: [WriteEntry]) async throws { + public func bulkWriteWithFallback(_ entries: [WriteEntry]) async throws { try await self.bulkWrite(entries) } } diff --git a/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift b/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift index 7965a97..338cbad 100644 --- a/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift +++ b/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift @@ -30,9 +30,11 @@ struct InMemoryPolymorphicWriteEntryTransform: PolymorphicWriteEntryTransform, S let inMemoryNewItem = try new.inMemoryForm() let existingItemMetadata = existing.asMetadataWithKey() self.operation = { store in - try table.updateItem(newItem: inMemoryNewItem, - existingItemMetadata: existingItemMetadata, - store: &store) + try table.updateItem( + newItem: inMemoryNewItem, + existingItemMetadata: existingItemMetadata, + store: &store + ) } case let .insert(new: new): let inMemoryNewItem = try new.inMemoryFormWithKey() @@ -53,8 +55,12 @@ struct InMemoryPolymorphicWriteEntryTransform: PolymorphicWriteEntryTransform, S } extension Array where Element: PolymorphicWriteEntry { - func asInMemoryTransforms(context: StandardPolymorphicWriteEntryContext) + func asInMemoryTransforms( + context: StandardPolymorphicWriteEntryContext< + InMemoryPolymorphicWriteEntryTransform, + InMemoryPolymorphicTransactionConstraintTransform + > + ) -> [Swift.Result] { self.map { entry in @@ -77,9 +83,12 @@ struct InMemoryPolymorphicTransactionConstraintTransform: PolymorphicTransaction let sortKey: String let rowVersion: Int - init(_ entry: TransactionConstraintEntry, - table _: TableType) throws - { + init( + _ entry: TransactionConstraintEntry< + some PrimaryKeyAttributes, some Codable & Sendable, some TimeToLiveAttributes + >, + table _: TableType + ) throws { switch entry { case let .required(existing: existing): self.partitionKey = existing.compositePrimaryKey.partitionKey @@ -90,8 +99,12 @@ struct InMemoryPolymorphicTransactionConstraintTransform: PolymorphicTransaction } extension Array where Element: PolymorphicTransactionConstraintEntry { - func asInMemoryTransforms(context: StandardPolymorphicWriteEntryContext) + func asInMemoryTransforms( + context: StandardPolymorphicWriteEntryContext< + InMemoryPolymorphicWriteEntryTransform, + InMemoryPolymorphicTransactionConstraintTransform + > + ) -> [Swift.Result] { self.map { entry in @@ -114,9 +127,10 @@ private let itemAlreadyExistsMessage = "Row already exists." extension InMemoryDynamoDBCompositePrimaryKeyTable { // Can be used directly by `InMemoryPolymorphicTransactionConstraintTransform` or through the `InMemoryPolymorphicWriteEntryTransform` - func insertItem(_ item: InMemoryDatabaseItemWithKey, - store: inout [String: [String: InMemoryDatabaseItem]]) throws - { + func insertItem( + _ item: InMemoryDatabaseItemWithKey, + store: inout [String: [String: InMemoryDatabaseItem]] + ) throws { let key = item.compositePrimaryKey let partition = store[key.partitionKey] @@ -127,9 +141,11 @@ extension InMemoryDynamoDBCompositePrimaryKeyTable { // if the row already exists if partition[item.compositePrimaryKey.sortKey] != nil { - throw DynamoDBTableError.conditionalCheckFailed(partitionKey: key.partitionKey, - sortKey: key.sortKey, - message: "Row already exists.") + throw DynamoDBTableError.conditionalCheckFailed( + partitionKey: key.partitionKey, + sortKey: key.sortKey, + message: "Row already exists." + ) } updatedPartition[key.sortKey] = item.inMemoryDatabaseItem @@ -140,10 +156,11 @@ extension InMemoryDynamoDBCompositePrimaryKeyTable { store[key.partitionKey] = updatedPartition } - func updateItem(newItem: InMemoryDatabaseItem, - existingItemMetadata: DatabaseItemMetadataWithKey, - store: inout [String: [String: InMemoryDatabaseItem]]) throws - { + func updateItem( + newItem: InMemoryDatabaseItem, + existingItemMetadata: DatabaseItemMetadataWithKey, + store: inout [String: [String: InMemoryDatabaseItem]] + ) throws { let key = existingItemMetadata.compositePrimaryKey let partition = store[key.partitionKey] @@ -154,38 +171,46 @@ extension InMemoryDynamoDBCompositePrimaryKeyTable { // if the row already exists if let actuallyExistingItem = partition[key.sortKey] { - if existingItemMetadata.rowStatus.rowVersion != actuallyExistingItem.rowStatus.rowVersion || - existingItemMetadata.createDate.iso8601 != actuallyExistingItem.createDate.iso8601 + if existingItemMetadata.rowStatus.rowVersion != actuallyExistingItem.rowStatus.rowVersion + || existingItemMetadata.createDate.iso8601 != actuallyExistingItem.createDate.iso8601 { - throw DynamoDBTableError.conditionalCheckFailed(partitionKey: key.partitionKey, - sortKey: key.sortKey, - message: "Trying to overwrite incorrect version.") + throw DynamoDBTableError.conditionalCheckFailed( + partitionKey: key.partitionKey, + sortKey: key.sortKey, + message: "Trying to overwrite incorrect version." + ) } } else { - throw DynamoDBTableError.conditionalCheckFailed(partitionKey: key.partitionKey, - sortKey: key.sortKey, - message: "Existing item does not exist.") + throw DynamoDBTableError.conditionalCheckFailed( + partitionKey: key.partitionKey, + sortKey: key.sortKey, + message: "Existing item does not exist." + ) } updatedPartition[key.sortKey] = newItem } else { - throw DynamoDBTableError.conditionalCheckFailed(partitionKey: key.partitionKey, - sortKey: key.sortKey, - message: "Existing item does not exist.") + throw DynamoDBTableError.conditionalCheckFailed( + partitionKey: key.partitionKey, + sortKey: key.sortKey, + message: "Existing item does not exist." + ) } store[key.partitionKey] = updatedPartition } - func deleteItem(forKey key: CompositePrimaryKey, - store: inout [String: [String: InMemoryDatabaseItem]]) throws - { + func deleteItem( + forKey key: CompositePrimaryKey, + store: inout [String: [String: InMemoryDatabaseItem]] + ) throws { store[key.partitionKey]?[key.sortKey] = nil } - func deleteItem(itemMetadata: DatabaseItemMetadataWithKey, - store: inout [String: [String: InMemoryDatabaseItem]]) throws - { + func deleteItem( + itemMetadata: DatabaseItemMetadataWithKey, + store: inout [String: [String: InMemoryDatabaseItem]] + ) throws { let partition = store[itemMetadata.compositePrimaryKey.partitionKey] // if there is already a partition @@ -195,38 +220,47 @@ extension InMemoryDynamoDBCompositePrimaryKeyTable { // if the row already exists if let actuallyExistingItem = partition[itemMetadata.compositePrimaryKey.sortKey] { - if itemMetadata.rowStatus.rowVersion != actuallyExistingItem.rowStatus.rowVersion || - itemMetadata.createDate.iso8601 != actuallyExistingItem.createDate.iso8601 + if itemMetadata.rowStatus.rowVersion != actuallyExistingItem.rowStatus.rowVersion + || itemMetadata.createDate.iso8601 != actuallyExistingItem.createDate.iso8601 { - throw DynamoDBTableError.conditionalCheckFailed(partitionKey: itemMetadata.compositePrimaryKey.partitionKey, - sortKey: itemMetadata.compositePrimaryKey.sortKey, - message: "Trying to delete incorrect version.") + throw DynamoDBTableError.conditionalCheckFailed( + partitionKey: itemMetadata.compositePrimaryKey.partitionKey, + sortKey: itemMetadata.compositePrimaryKey.sortKey, + message: "Trying to delete incorrect version." + ) } } else { - throw DynamoDBTableError.conditionalCheckFailed(partitionKey: itemMetadata.compositePrimaryKey.partitionKey, - sortKey: itemMetadata.compositePrimaryKey.sortKey, - message: "Existing item does not exist.") + throw DynamoDBTableError.conditionalCheckFailed( + partitionKey: itemMetadata.compositePrimaryKey.partitionKey, + sortKey: itemMetadata.compositePrimaryKey.sortKey, + message: "Existing item does not exist." + ) } updatedPartition[itemMetadata.compositePrimaryKey.sortKey] = nil } else { - throw DynamoDBTableError.conditionalCheckFailed(partitionKey: itemMetadata.compositePrimaryKey.partitionKey, - sortKey: itemMetadata.compositePrimaryKey.sortKey, - message: "Existing item does not exist.") + throw DynamoDBTableError.conditionalCheckFailed( + partitionKey: itemMetadata.compositePrimaryKey.partitionKey, + sortKey: itemMetadata.compositePrimaryKey.sortKey, + message: "Existing item does not exist." + ) } store[itemMetadata.compositePrimaryKey.partitionKey] = updatedPartition } - func bulkWrite(_ entries: [InMemoryWriteEntry], - constraints: [InMemoryTransactionConstraintEntry], - store: inout [String: [String: InMemoryDatabaseItem]], - isTransaction: Bool) throws - { + func bulkWrite( + _ entries: [InMemoryWriteEntry], + constraints: [InMemoryTransactionConstraintEntry], + store: inout [String: [String: InMemoryDatabaseItem]], + isTransaction: Bool + ) throws { let entryCount = entries.count + constraints.count if isTransaction, entryCount > AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement { - throw DynamoDBTableError.itemCollectionSizeLimitExceeded(attemptedSize: entryCount, - maximumSize: AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement) + throw DynamoDBTableError.itemCollectionSizeLimitExceeded( + attemptedSize: entryCount, + maximumSize: AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement + ) } let savedStore = store @@ -247,30 +281,42 @@ extension InMemoryDynamoDBCompositePrimaryKeyTable { func polymorphicBulkWrite( _ entryTransformResults: [Swift.Result], - constraintTransformResults: [Swift.Result], + constraintTransformResults: [Swift.Result< + InMemoryPolymorphicTransactionConstraintTransform, DynamoDBTableError + >], store: inout [String: [String: InMemoryDatabaseItem]], - context: StandardPolymorphicWriteEntryContext, - isTransaction: Bool) throws - { + context: StandardPolymorphicWriteEntryContext< + InMemoryPolymorphicWriteEntryTransform, + InMemoryPolymorphicTransactionConstraintTransform + >, + isTransaction: Bool + ) throws { let entryCount = entryTransformResults.count + constraintTransformResults.count if isTransaction, entryCount > AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement { - throw DynamoDBTableError.itemCollectionSizeLimitExceeded(attemptedSize: entryCount, - maximumSize: AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement) + throw DynamoDBTableError.itemCollectionSizeLimitExceeded( + attemptedSize: entryCount, + maximumSize: AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement + ) } let savedStore = store - if let error = self.handleConstraints(transformResults: constraintTransformResults, isTransaction: isTransaction, - store: &store, context: context) - { + if let error = self.handleConstraints( + transformResults: constraintTransformResults, + isTransaction: isTransaction, + store: &store, + context: context + ) { throw error } - if let error = self.handlePolymorphicEntries(entryTransformResults: entryTransformResults, isTransaction: isTransaction, - store: &store, context: context) - { + if let error = self.handlePolymorphicEntries( + entryTransformResults: entryTransformResults, + isTransaction: isTransaction, + store: &store, + context: context + ) { if isTransaction { // restore the state prior to the transaction store = savedStore @@ -282,24 +328,29 @@ extension InMemoryDynamoDBCompositePrimaryKeyTable { func handleConstraints( constraints: [InMemoryTransactionConstraintEntry], - store: inout [String: [String: InMemoryDatabaseItem]], isTransaction _: Bool) + store: inout [String: [String: InMemoryDatabaseItem]], + isTransaction _: Bool + ) -> DynamoDBTableError? { let errors = constraints.compactMap { entry -> DynamoDBTableError? in - let existingItem: InMemoryDatabaseItemWithKey = switch entry { - case let .required(existing: existing): - existing - } + let existingItem: InMemoryDatabaseItemWithKey = + switch entry { + case let .required(existing: existing): + existing + } let compositePrimaryKey = existingItem.compositePrimaryKey guard let partition = store[compositePrimaryKey.partitionKey], - let item = partition[compositePrimaryKey.sortKey], - item.rowStatus.rowVersion == existingItem.rowStatus.rowVersion + let item = partition[compositePrimaryKey.sortKey], + item.rowStatus.rowVersion == existingItem.rowStatus.rowVersion else { - return DynamoDBTableError.conditionalCheckFailed(partitionKey: compositePrimaryKey.partitionKey, - sortKey: compositePrimaryKey.sortKey, - message: "Item doesn't exist or doesn't have correct version") + return DynamoDBTableError.conditionalCheckFailed( + partitionKey: compositePrimaryKey.partitionKey, + sortKey: compositePrimaryKey.sortKey, + message: "Item doesn't exist or doesn't have correct version" + ) } return nil @@ -313,10 +364,14 @@ extension InMemoryDynamoDBCompositePrimaryKeyTable { } func handleConstraints( - transformResults: [Swift.Result], isTransaction _: Bool, + transformResults: [Swift.Result], + isTransaction _: Bool, store: inout [String: [String: InMemoryDatabaseItem]], - context _: StandardPolymorphicWriteEntryContext) + context _: StandardPolymorphicWriteEntryContext< + InMemoryPolymorphicWriteEntryTransform, + InMemoryPolymorphicTransactionConstraintTransform + > + ) -> DynamoDBTableError? { let errors = transformResults.compactMap { transformResult -> DynamoDBTableError? in @@ -329,12 +384,14 @@ extension InMemoryDynamoDBCompositePrimaryKeyTable { } guard let partition = store[transform.partitionKey], - let item = partition[transform.sortKey], - item.rowStatus.rowVersion == transform.rowVersion + let item = partition[transform.sortKey], + item.rowStatus.rowVersion == transform.rowVersion else { - return DynamoDBTableError.conditionalCheckFailed(partitionKey: transform.partitionKey, - sortKey: transform.sortKey, - message: "Item doesn't exist or doesn't have correct version") + return DynamoDBTableError.conditionalCheckFailed( + partitionKey: transform.partitionKey, + sortKey: transform.sortKey, + message: "Item doesn't exist or doesn't have correct version" + ) } return nil @@ -349,15 +406,20 @@ extension InMemoryDynamoDBCompositePrimaryKeyTable { func handleEntries( entries: [InMemoryWriteEntry], - store: inout [String: [String: InMemoryDatabaseItem]], isTransaction: Bool) + store: inout [String: [String: InMemoryDatabaseItem]], + isTransaction: Bool + ) -> DynamoDBTableError? { let writeErrors = entries.compactMap { entry -> DynamoDBTableError? in do { switch entry { case let .update(new: new, existing: existing): - try self.updateItem(newItem: new.inMemoryDatabaseItem, existingItemMetadata: existing.asMetadataWithKey(), - store: &store) + try self.updateItem( + newItem: new.inMemoryDatabaseItem, + existingItemMetadata: existing.asMetadataWithKey(), + store: &store + ) case let .insert(new: new): try self.insertItem(new, store: &store) case let .deleteAtKey(key: key): @@ -371,8 +433,11 @@ extension InMemoryDynamoDBCompositePrimaryKeyTable { if message == itemAlreadyExistsMessage { return .duplicateItem(partitionKey: partitionKey, sortKey: sortKey, message: message) } else { - return .conditionalCheckFailed(partitionKey: partitionKey, - sortKey: sortKey, message: message) + return .conditionalCheckFailed( + partitionKey: partitionKey, + sortKey: sortKey, + message: message + ) } } return typedError @@ -397,10 +462,14 @@ extension InMemoryDynamoDBCompositePrimaryKeyTable { } func handlePolymorphicEntries( - entryTransformResults: [Swift.Result], isTransaction: Bool, + entryTransformResults: [Swift.Result], + isTransaction: Bool, store: inout [String: [String: InMemoryDatabaseItem]], - context _: StandardPolymorphicWriteEntryContext) + context _: StandardPolymorphicWriteEntryContext< + InMemoryPolymorphicWriteEntryTransform, + InMemoryPolymorphicTransactionConstraintTransform + > + ) -> DynamoDBTableError? { let writeErrors = entryTransformResults.compactMap { entryTransformResult -> DynamoDBTableError? in @@ -420,8 +489,11 @@ extension InMemoryDynamoDBCompositePrimaryKeyTable { if message == itemAlreadyExistsMessage { return .duplicateItem(partitionKey: partitionKey, sortKey: sortKey, message: message) } else { - return .conditionalCheckFailed(partitionKey: partitionKey, - sortKey: sortKey, message: message) + return .conditionalCheckFailed( + partitionKey: partitionKey, + sortKey: sortKey, + message: message + ) } } return typedError @@ -445,7 +517,9 @@ extension InMemoryDynamoDBCompositePrimaryKeyTable { return nil } - func convertToQueryableType(input: InMemoryDatabaseItem) throws -> ReturnedType { + func convertToQueryableType( + input: InMemoryDatabaseItem + ) throws -> ReturnedType { let attributeValue = DynamoDBClientTypes.AttributeValue.m(input.item) let decodedItem: ReturnTypeDecodable = try DynamoDBDecoder().decode(attributeValue) diff --git a/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift b/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift index 993fc08..afe3213 100644 --- a/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift +++ b/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift @@ -28,8 +28,8 @@ @preconcurrency import AWSDynamoDB import Foundation -public extension TypedTTLDatabaseItem { - var rowTypeIdentifier: String { +extension TypedTTLDatabaseItem { + public var rowTypeIdentifier: String { getTypeRowIdentifier(type: RowType.self) } } @@ -42,7 +42,9 @@ public protocol InMemoryTransactionDelegate: Sendable { Inject errors into a `transactWrite` or `polymorphicTransactWrite` call. */ func injectErrors( - inputKeys: [CompositePrimaryKey?], table: InMemoryDynamoDBCompositePrimaryKeyTable) async throws -> [DynamoDBTableError] + inputKeys: [CompositePrimaryKey?], + table: InMemoryDynamoDBCompositePrimaryKeyTable + ) async throws -> [DynamoDBTableError] } public struct InMemoryDynamoDBCompositePrimaryKeyTable: DynamoDBCompositePrimaryKeyTable, Sendable { @@ -50,9 +52,10 @@ public struct InMemoryDynamoDBCompositePrimaryKeyTable: DynamoDBCompositePrimary public let executeItemFilter: ExecuteItemFilterType? let storeWrapper: InMemoryDynamoDBCompositePrimaryKeyTableStore - public init(executeItemFilter: ExecuteItemFilterType? = nil, - transactionDelegate: InMemoryTransactionDelegate? = nil) - { + public init( + executeItemFilter: ExecuteItemFilterType? = nil, + transactionDelegate: InMemoryTransactionDelegate? = nil + ) { self.storeWrapper = InMemoryDynamoDBCompositePrimaryKeyTableStore() self.transactionDelegate = transactionDelegate self.executeItemFilter = executeItemFilter @@ -69,7 +72,8 @@ public struct InMemoryDynamoDBCompositePrimaryKeyTable: DynamoDBCompositePrimary if entryString.count > AWSDynamoDBLimits.maxStatementLength { throw DynamoDBTableError.statementLengthExceeded( reason: "failed to satisfy constraint: Member must have length less than or equal to " - + "\(AWSDynamoDBLimits.maxStatementLength). Actual length \(entryString.count)") + + "\(AWSDynamoDBLimits.maxStatementLength). Actual length \(entryString.count)" + ) } } @@ -103,16 +107,22 @@ public struct InMemoryDynamoDBCompositePrimaryKeyTable: DynamoDBCompositePrimary public func updateItem( newItem: TypedTTLDatabaseItem, - existingItem: TypedTTLDatabaseItem) async throws - { + existingItem: TypedTTLDatabaseItem + ) async throws { let inMemoryDatabaseItem = try newItem.inMemoryForm() let existingItemMetadata = existingItem.asMetadataWithKey() try await self.storeWrapper.execute { store in - try self.updateItem(newItem: inMemoryDatabaseItem, existingItemMetadata: existingItemMetadata, store: &store) + try self.updateItem( + newItem: inMemoryDatabaseItem, + existingItemMetadata: existingItemMetadata, + store: &store + ) } } - public func getItem(forKey key: CompositePrimaryKey) async throws + public func getItem( + forKey key: CompositePrimaryKey + ) async throws -> TypedTTLDatabaseItem? { if let partition = await self.store[key.partitionKey] { diff --git a/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift b/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift index 4bb45a0..b90045c 100644 --- a/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift +++ b/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift @@ -34,17 +34,20 @@ public enum GSIError: Error { case unknownIndex(name: String) } -public struct InMemoryDynamoDBCompositePrimaryKeyTableWithIndex: DynamoDBCompositePrimaryKeyTable, Sendable { +public struct InMemoryDynamoDBCompositePrimaryKeyTableWithIndex< + GSILogic: DynamoDBCompositePrimaryKeyGSILogic & Sendable +>: DynamoDBCompositePrimaryKeyTable, Sendable { public let primaryTable: InMemoryDynamoDBCompositePrimaryKeyTable public let gsiDataStore: InMemoryDynamoDBCompositePrimaryKeyTable private let gsiName: String private let gsiLogic: GSILogic - public init(gsiName: String, - gsiLogic: GSILogic, - executeItemFilter: ExecuteItemFilterType? = nil) - { + public init( + gsiName: String, + gsiLogic: GSILogic, + executeItemFilter: ExecuteItemFilterType? = nil + ) { self.gsiName = gsiName self.gsiLogic = gsiLogic self.primaryTable = InMemoryDynamoDBCompositePrimaryKeyTable(executeItemFilter: executeItemFilter) @@ -55,7 +58,9 @@ public struct InMemoryDynamoDBCompositePrimaryKeyTableWithIndex maxStatementLength { throw DynamoDBTableError.statementLengthExceeded( - reason: "failed to satisfy constraint: Member must have length less than or equal to \(maxStatementLength). Actual length \(entryString.count)") + reason: + "failed to satisfy constraint: Member must have length less than or equal to \(maxStatementLength). Actual length \(entryString.count)" + ) } } @@ -71,10 +76,14 @@ public struct InMemoryDynamoDBCompositePrimaryKeyTableWithIndex( newItem: TypedTTLDatabaseItem, - existingItem: TypedTTLDatabaseItem) async throws - { + existingItem: TypedTTLDatabaseItem + ) async throws { try await self.primaryTable.updateItem(newItem: newItem, existingItem: existingItem) - try await self.gsiLogic.onUpdateItem(newItem: newItem, existingItem: existingItem, gsiDataStore: self.gsiDataStore) + try await self.gsiLogic.onUpdateItem( + newItem: newItem, + existingItem: existingItem, + gsiDataStore: self.gsiDataStore + ) } public func transactWrite(_ entries: [WriteEntry]) async throws { @@ -83,8 +92,8 @@ public struct InMemoryDynamoDBCompositePrimaryKeyTableWithIndex( _ entries: [WriteEntry], - constraints: [TransactionConstraintEntry]) async throws - { + constraints: [TransactionConstraintEntry] + ) async throws { try await self.primaryTable.transactWrite(entries, constraints: constraints) } @@ -92,11 +101,14 @@ public struct InMemoryDynamoDBCompositePrimaryKeyTableWithIndex( - _ entries: [WriteEntryType], constraints: [TransactionConstraintEntryType]) async throws - where WriteEntryType.AttributesType == TransactionConstraintEntryType.AttributesType - { + public func polymorphicTransactWrite< + WriteEntryType: PolymorphicWriteEntry, + TransactionConstraintEntryType: PolymorphicTransactionConstraintEntry + >( + _ entries: [WriteEntryType], + constraints: [TransactionConstraintEntryType] + ) async throws + where WriteEntryType.AttributesType == TransactionConstraintEntryType.AttributesType { try await self.primaryTable.polymorphicTransactWrite(entries, constraints: constraints) } @@ -120,19 +132,22 @@ public struct InMemoryDynamoDBCompositePrimaryKeyTableWithIndex]) async throws - { + _ entries: [WriteEntry] + ) async throws { try await self.bulkWrite(entries) } - public func getItem(forKey key: CompositePrimaryKey) async throws + public func getItem( + forKey key: CompositePrimaryKey + ) async throws -> TypedTTLDatabaseItem? { try await self.primaryTable.getItem(forKey: key) } public func polymorphicGetItems( - forKeys keys: [CompositePrimaryKey]) async throws + forKeys keys: [CompositePrimaryKey] + ) async throws -> [CompositePrimaryKey: ReturnedType] { try await self.primaryTable.polymorphicGetItems(forKeys: keys) @@ -160,12 +175,17 @@ public struct InMemoryDynamoDBCompositePrimaryKeyTableWithIndex(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?) async throws + public func polymorphicQuery( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition? + ) async throws -> [ReturnedType] { // if this is querying an index @@ -176,19 +196,25 @@ public struct InMemoryDynamoDBCompositePrimaryKeyTableWithIndex(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - exclusiveStartKey: String?) async throws + public func polymorphicQuery( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + exclusiveStartKey: String? + ) async throws -> (items: [ReturnedType], lastEvaluatedKey: String?) { // if this is querying an index @@ -199,20 +225,30 @@ public struct InMemoryDynamoDBCompositePrimaryKeyTableWithIndex(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - scanIndexForward: Bool, - exclusiveStartKey: String?) async throws + public func polymorphicQuery( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + scanIndexForward: Bool, + exclusiveStartKey: String? + ) async throws -> (items: [ReturnedType], lastEvaluatedKey: String?) { // if this is querying an index @@ -223,20 +259,30 @@ public struct InMemoryDynamoDBCompositePrimaryKeyTableWithIndex( partitionKeys: [String], attributesFilter: [String]?, - additionalWhereClause: String?) async throws -> [ReturnedType] - { + additionalWhereClause: String? + ) async throws -> [ReturnedType] { // if this is executing on index if let indexName = ReturnedType.AttributesType.indexName { // fail if it isn't the index we know about @@ -245,19 +291,27 @@ public struct InMemoryDynamoDBCompositePrimaryKeyTableWithIndex( partitionKeys: [String], attributesFilter: [String]?, - additionalWhereClause: String?, nextToken: String?) async throws + additionalWhereClause: String?, + nextToken: String? + ) async throws -> (items: [ReturnedType], lastEvaluatedKey: String?) { // if this is executing on index @@ -268,24 +322,37 @@ public struct InMemoryDynamoDBCompositePrimaryKeyTableWithIndex( - forKeys keys: [CompositePrimaryKey]) async throws - -> [CompositePrimaryKey: TypedTTLDatabaseItem] + forKeys keys: [CompositePrimaryKey] + ) async throws + -> [CompositePrimaryKey: TypedTTLDatabaseItem< + AttributesType, ItemType, TimeToLiveAttributesType + >] { try await self.primaryTable.getItems(forKeys: keys) } - public func query(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?) async throws + public func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition? + ) async throws -> [TypedTTLDatabaseItem] { // if this is querying an index @@ -296,21 +363,29 @@ public struct InMemoryDynamoDBCompositePrimaryKeyTableWithIndex(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - scanIndexForward: Bool, - exclusiveStartKey: String?) async throws - -> (items: [TypedTTLDatabaseItem], lastEvaluatedKey: String?) + public func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + scanIndexForward: Bool, + exclusiveStartKey: String? + ) async throws + -> ( + items: [TypedTTLDatabaseItem], lastEvaluatedKey: String? + ) { // if this is querying an index if let indexName = AttributesType.indexName { @@ -320,20 +395,30 @@ public struct InMemoryDynamoDBCompositePrimaryKeyTableWithIndex( partitionKeys: [String], attributesFilter: [String]?, - additionalWhereClause: String?) async throws -> [TypedTTLDatabaseItem] - { + additionalWhereClause: String? + ) async throws -> [TypedTTLDatabaseItem] { // if this is executing on index if let indexName = AttributesType.indexName { // fail if it isn't the index we know about @@ -342,20 +427,30 @@ public struct InMemoryDynamoDBCompositePrimaryKeyTableWithIndex( partitionKeys: [String], attributesFilter: [String]?, - additionalWhereClause: String?, nextToken: String?) async throws - -> (items: [TypedTTLDatabaseItem], lastEvaluatedKey: String?) + additionalWhereClause: String?, + nextToken: String? + ) async throws + -> ( + items: [TypedTTLDatabaseItem], lastEvaluatedKey: String? + ) { // if this is executing on index if let indexName = AttributesType.indexName { @@ -365,12 +460,20 @@ public struct InMemoryDynamoDBCompositePrimaryKeyTableWithIndex(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?) async throws + public func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition? + ) async throws -> [CompositePrimaryKey] { try await self.keysWrapper.query(forPartitionKey: partitionKey, sortKeyCondition: sortKeyCondition) } - public func query(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - exclusiveStartKey: String?) async throws + public func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + exclusiveStartKey: String? + ) async throws -> (keys: [CompositePrimaryKey], lastEvaluatedKey: String?) { - try await self.keysWrapper.query(forPartitionKey: partitionKey, sortKeyCondition: sortKeyCondition, - limit: limit, exclusiveStartKey: exclusiveStartKey) + try await self.keysWrapper.query( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition, + limit: limit, + exclusiveStartKey: exclusiveStartKey + ) } - public func query(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - scanIndexForward: Bool, - exclusiveStartKey: String?) async throws + public func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + scanIndexForward: Bool, + exclusiveStartKey: String? + ) async throws -> (keys: [CompositePrimaryKey], lastEvaluatedKey: String?) { - try await self.keysWrapper.query(forPartitionKey: partitionKey, sortKeyCondition: sortKeyCondition, - limit: limit, scanIndexForward: scanIndexForward, - exclusiveStartKey: exclusiveStartKey) + try await self.keysWrapper.query( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition, + limit: limit, + scanIndexForward: scanIndexForward, + exclusiveStartKey: exclusiveStartKey + ) } } diff --git a/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeysProjectionStore.swift b/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeysProjectionStore.swift index c16cd29..cff471d 100644 --- a/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeysProjectionStore.swift +++ b/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeysProjectionStore.swift @@ -37,14 +37,18 @@ actor InMemoryDynamoDBCompositePrimaryKeysProjectionStore { self.keys = keys.map { .init(partitionKey: $0.partitionKey, sortKey: $0.sortKey) } } - func query(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?) async throws + func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition? + ) async throws -> [CompositePrimaryKey] { var items: [CompositePrimaryKey] = [] - let sortedKeys: [CompositePrimaryKey] = self.keys.compactMap { .init(partitionKey: $0.partitionKey, sortKey: $0.sortKey) } - .sorted(by: { left, right -> Bool in left.sortKey < right.sortKey }) + let sortedKeys: [CompositePrimaryKey] = self.keys.compactMap { + .init(partitionKey: $0.partitionKey, sortKey: $0.sortKey) + } + .sorted(by: { left, right -> Bool in left.sortKey < right.sortKey }) sortKeyIteration: for key in sortedKeys { if key.partitionKey != partitionKey { @@ -100,34 +104,43 @@ actor InMemoryDynamoDBCompositePrimaryKeysProjectionStore { return items } - func query(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - exclusiveStartKey: String?) async throws + func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + exclusiveStartKey: String? + ) async throws -> (keys: [CompositePrimaryKey], lastEvaluatedKey: String?) { - try await self.query(forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition, - limit: limit, - scanIndexForward: true, - exclusiveStartKey: exclusiveStartKey) + try await self.query( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition, + limit: limit, + scanIndexForward: true, + exclusiveStartKey: exclusiveStartKey + ) } - func query(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - scanIndexForward: Bool, - exclusiveStartKey: String?) async throws + func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + scanIndexForward: Bool, + exclusiveStartKey: String? + ) async throws -> (keys: [CompositePrimaryKey], lastEvaluatedKey: String?) { // get all the results - let rawItems: [CompositePrimaryKey] = try await query(forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition) - let items: [CompositePrimaryKey] = if !scanIndexForward { - rawItems.reversed() - } else { - rawItems - } + let rawItems: [CompositePrimaryKey] = try await query( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition + ) + let items: [CompositePrimaryKey] = + if !scanIndexForward { + rawItems.reversed() + } else { + rawItems + } let startIndex: Int // if there is an exclusiveStartKey @@ -151,6 +164,6 @@ actor InMemoryDynamoDBCompositePrimaryKeysProjectionStore { lastEvaluatedKey = nil } - return (Array(items[startIndex ..< endIndex]), lastEvaluatedKey) + return (Array(items[startIndex..: KeyedDecodingContainerProto try self.createNestedContainer(for: key).decode(type) } - func nestedContainer(keyedBy type: NestedKey.Type, forKey key: K) throws + func nestedContainer( + keyedBy type: NestedKey.Type, + forKey key: K + ) throws -> KeyedDecodingContainer where NestedKey: CodingKey { try self.createNestedContainer(for: key).container(keyedBy: type) @@ -161,17 +164,21 @@ struct InternalKeyedDecodingContainer: KeyedDecodingContainerProto throw DecodingError.keyNotFound(key, context) } - return InternalSingleValueDecodingContainer(attributeValue: value, codingPath: self.decodingContainer.codingPath + [key], - userInfo: self.decodingContainer.userInfo, - attributeNameTransform: self.decodingContainer.attributeNameTransform) + return InternalSingleValueDecodingContainer( + attributeValue: value, + codingPath: self.decodingContainer.codingPath + [key], + userInfo: self.decodingContainer.userInfo, + attributeNameTransform: self.decodingContainer.attributeNameTransform + ) } private func getAttributeName(key: CodingKey) -> String { - let attributeName: String = if let attributeNameTransform = decodingContainer.attributeNameTransform { - attributeNameTransform(key.stringValue) - } else { - key.stringValue - } + let attributeName: String = + if let attributeNameTransform = decodingContainer.attributeNameTransform { + attributeNameTransform(key.stringValue) + } else { + key.stringValue + } return attributeName } diff --git a/Sources/DynamoDBTables/InternalKeyedEncodingContainer.swift b/Sources/DynamoDBTables/InternalKeyedEncodingContainer.swift index ad5c4d2..93ef9ef 100644 --- a/Sources/DynamoDBTables/InternalKeyedEncodingContainer.swift +++ b/Sources/DynamoDBTables/InternalKeyedEncodingContainer.swift @@ -50,51 +50,87 @@ struct InternalKeyedEncodingContainer: KeyedEncodingContainerProto } func encode(_ value: Int, forKey key: Key) throws { - self.enclosingContainer.addToKeyedContainer(key: key, value: DynamoDBClientTypes.AttributeValue.n(String(value))) + self.enclosingContainer.addToKeyedContainer( + key: key, + value: DynamoDBClientTypes.AttributeValue.n(String(value)) + ) } func encode(_ value: Int8, forKey key: Key) throws { - self.enclosingContainer.addToKeyedContainer(key: key, value: DynamoDBClientTypes.AttributeValue.n(String(value))) + self.enclosingContainer.addToKeyedContainer( + key: key, + value: DynamoDBClientTypes.AttributeValue.n(String(value)) + ) } func encode(_ value: Int16, forKey key: Key) throws { - self.enclosingContainer.addToKeyedContainer(key: key, value: DynamoDBClientTypes.AttributeValue.n(String(value))) + self.enclosingContainer.addToKeyedContainer( + key: key, + value: DynamoDBClientTypes.AttributeValue.n(String(value)) + ) } func encode(_ value: Int32, forKey key: Key) throws { - self.enclosingContainer.addToKeyedContainer(key: key, value: DynamoDBClientTypes.AttributeValue.n(String(value))) + self.enclosingContainer.addToKeyedContainer( + key: key, + value: DynamoDBClientTypes.AttributeValue.n(String(value)) + ) } func encode(_ value: Int64, forKey key: Key) throws { - self.enclosingContainer.addToKeyedContainer(key: key, value: DynamoDBClientTypes.AttributeValue.n(String(value))) + self.enclosingContainer.addToKeyedContainer( + key: key, + value: DynamoDBClientTypes.AttributeValue.n(String(value)) + ) } func encode(_ value: UInt, forKey key: Key) throws { - self.enclosingContainer.addToKeyedContainer(key: key, value: DynamoDBClientTypes.AttributeValue.n(String(value))) + self.enclosingContainer.addToKeyedContainer( + key: key, + value: DynamoDBClientTypes.AttributeValue.n(String(value)) + ) } func encode(_ value: UInt8, forKey key: Key) throws { - self.enclosingContainer.addToKeyedContainer(key: key, value: DynamoDBClientTypes.AttributeValue.n(String(value))) + self.enclosingContainer.addToKeyedContainer( + key: key, + value: DynamoDBClientTypes.AttributeValue.n(String(value)) + ) } func encode(_ value: UInt16, forKey key: Key) throws { - self.enclosingContainer.addToKeyedContainer(key: key, value: DynamoDBClientTypes.AttributeValue.n(String(value))) + self.enclosingContainer.addToKeyedContainer( + key: key, + value: DynamoDBClientTypes.AttributeValue.n(String(value)) + ) } func encode(_ value: UInt32, forKey key: Key) throws { - self.enclosingContainer.addToKeyedContainer(key: key, value: DynamoDBClientTypes.AttributeValue.n(String(value))) + self.enclosingContainer.addToKeyedContainer( + key: key, + value: DynamoDBClientTypes.AttributeValue.n(String(value)) + ) } func encode(_ value: UInt64, forKey key: Key) throws { - self.enclosingContainer.addToKeyedContainer(key: key, value: DynamoDBClientTypes.AttributeValue.n(String(value))) + self.enclosingContainer.addToKeyedContainer( + key: key, + value: DynamoDBClientTypes.AttributeValue.n(String(value)) + ) } func encode(_ value: Float, forKey key: Key) throws { - self.enclosingContainer.addToKeyedContainer(key: key, value: DynamoDBClientTypes.AttributeValue.n(String(value))) + self.enclosingContainer.addToKeyedContainer( + key: key, + value: DynamoDBClientTypes.AttributeValue.n(String(value)) + ) } func encode(_ value: Double, forKey key: Key) throws { - self.enclosingContainer.addToKeyedContainer(key: key, value: DynamoDBClientTypes.AttributeValue.n(String(value))) + self.enclosingContainer.addToKeyedContainer( + key: key, + value: DynamoDBClientTypes.AttributeValue.n(String(value)) + ) } func encode(_ value: String, forKey key: Key) throws { @@ -107,9 +143,10 @@ struct InternalKeyedEncodingContainer: KeyedEncodingContainerProto try nestedContainer.encode(value) } - func nestedContainer(keyedBy _: NestedKey.Type, - forKey key: Key) -> KeyedEncodingContainer - { + func nestedContainer( + keyedBy _: NestedKey.Type, + forKey key: Key + ) -> KeyedEncodingContainer { let nestedContainer = self.createNestedContainer(for: key, defaultValue: .keyedContainer([:])) let nestedKeyContainer = InternalKeyedEncodingContainer(enclosingContainer: nestedContainer) @@ -130,14 +167,18 @@ struct InternalKeyedEncodingContainer: KeyedEncodingContainerProto // MARK: - - private func createNestedContainer(for key: some CodingKey, - defaultValue: ContainerValueType? = nil) + private func createNestedContainer( + for key: some CodingKey, + defaultValue: ContainerValueType? = nil + ) -> InternalSingleValueEncodingContainer { - let nestedContainer = InternalSingleValueEncodingContainer(userInfo: enclosingContainer.userInfo, - codingPath: self.enclosingContainer.codingPath + [key], - attributeNameTransform: self.enclosingContainer.attributeNameTransform, - defaultValue: defaultValue) + let nestedContainer = InternalSingleValueEncodingContainer( + userInfo: enclosingContainer.userInfo, + codingPath: self.enclosingContainer.codingPath + [key], + attributeNameTransform: self.enclosingContainer.attributeNameTransform, + defaultValue: defaultValue + ) self.enclosingContainer.addToKeyedContainer(key: key, value: nestedContainer) return nestedContainer diff --git a/Sources/DynamoDBTables/InternalSingleValueDecodingContainer.swift b/Sources/DynamoDBTables/InternalSingleValueDecodingContainer.swift index 0cbd24b..6dc843b 100644 --- a/Sources/DynamoDBTables/InternalSingleValueDecodingContainer.swift +++ b/Sources/DynamoDBTables/InternalSingleValueDecodingContainer.swift @@ -33,11 +33,12 @@ struct InternalSingleValueDecodingContainer { let attributeValue: DynamoDBClientTypes.AttributeValue let attributeNameTransform: ((String) -> String)? - init(attributeValue: DynamoDBClientTypes.AttributeValue, - codingPath: [CodingKey], - userInfo: [CodingUserInfoKey: Any], - attributeNameTransform: ((String) -> String)?) - { + init( + attributeValue: DynamoDBClientTypes.AttributeValue, + codingPath: [CodingKey], + userInfo: [CodingUserInfoKey: Any], + attributeNameTransform: ((String) -> String)? + ) { self.attributeValue = attributeValue self.codingPath = codingPath self.userInfo = userInfo @@ -64,7 +65,7 @@ extension InternalSingleValueDecodingContainer: SingleValueDecodingContainer { func decode(_: Int.Type) throws -> Int { guard case let .n(valueAsString) = attributeValue, - let value = Int(valueAsString) + let value = Int(valueAsString) else { throw self.getTypeMismatchError(expectation: Int.self) } @@ -74,7 +75,7 @@ extension InternalSingleValueDecodingContainer: SingleValueDecodingContainer { func decode(_: Int8.Type) throws -> Int8 { guard case let .n(valueAsString) = attributeValue, - let value = Int8(valueAsString) + let value = Int8(valueAsString) else { throw self.getTypeMismatchError(expectation: Int8.self) } @@ -84,7 +85,7 @@ extension InternalSingleValueDecodingContainer: SingleValueDecodingContainer { func decode(_: Int16.Type) throws -> Int16 { guard case let .n(valueAsString) = attributeValue, - let value = Int16(valueAsString) + let value = Int16(valueAsString) else { throw self.getTypeMismatchError(expectation: Int16.self) } @@ -94,7 +95,7 @@ extension InternalSingleValueDecodingContainer: SingleValueDecodingContainer { func decode(_: Int32.Type) throws -> Int32 { guard case let .n(valueAsString) = attributeValue, - let value = Int32(valueAsString) + let value = Int32(valueAsString) else { throw self.getTypeMismatchError(expectation: Int32.self) } @@ -104,7 +105,7 @@ extension InternalSingleValueDecodingContainer: SingleValueDecodingContainer { func decode(_: Int64.Type) throws -> Int64 { guard case let .n(valueAsString) = attributeValue, - let value = Int64(valueAsString) + let value = Int64(valueAsString) else { throw self.getTypeMismatchError(expectation: Int64.self) } @@ -114,7 +115,7 @@ extension InternalSingleValueDecodingContainer: SingleValueDecodingContainer { func decode(_: UInt.Type) throws -> UInt { guard case let .n(valueAsString) = attributeValue, - let value = UInt(valueAsString) + let value = UInt(valueAsString) else { throw self.getTypeMismatchError(expectation: UInt.self) } @@ -124,7 +125,7 @@ extension InternalSingleValueDecodingContainer: SingleValueDecodingContainer { func decode(_: UInt8.Type) throws -> UInt8 { guard case let .n(valueAsString) = attributeValue, - let value = UInt8(valueAsString) + let value = UInt8(valueAsString) else { throw self.getTypeMismatchError(expectation: UInt8.self) } @@ -134,7 +135,7 @@ extension InternalSingleValueDecodingContainer: SingleValueDecodingContainer { func decode(_: UInt16.Type) throws -> UInt16 { guard case let .n(valueAsString) = attributeValue, - let value = UInt16(valueAsString) + let value = UInt16(valueAsString) else { throw self.getTypeMismatchError(expectation: UInt16.self) } @@ -144,7 +145,7 @@ extension InternalSingleValueDecodingContainer: SingleValueDecodingContainer { func decode(_: UInt32.Type) throws -> UInt32 { guard case let .n(valueAsString) = attributeValue, - let value = UInt32(valueAsString) + let value = UInt32(valueAsString) else { throw self.getTypeMismatchError(expectation: UInt32.self) } @@ -154,7 +155,7 @@ extension InternalSingleValueDecodingContainer: SingleValueDecodingContainer { func decode(_: UInt64.Type) throws -> UInt64 { guard case let .n(valueAsString) = attributeValue, - let value = UInt64(valueAsString) + let value = UInt64(valueAsString) else { throw self.getTypeMismatchError(expectation: UInt64.self) } @@ -164,7 +165,7 @@ extension InternalSingleValueDecodingContainer: SingleValueDecodingContainer { func decode(_: Float.Type) throws -> Float { guard case let .n(valueAsString) = attributeValue, - let value = Float(valueAsString) + let value = Float(valueAsString) else { throw self.getTypeMismatchError(expectation: Float.self) } @@ -174,7 +175,7 @@ extension InternalSingleValueDecodingContainer: SingleValueDecodingContainer { func decode(_: Double.Type) throws -> Double { guard case let .n(valueAsString) = attributeValue, - let value = Double(valueAsString) + let value = Double(valueAsString) else { throw self.getTypeMismatchError(expectation: Double.self) } diff --git a/Sources/DynamoDBTables/InternalSingleValueEncodingContainer.swift b/Sources/DynamoDBTables/InternalSingleValueEncodingContainer.swift index 76b7221..e84766b 100644 --- a/Sources/DynamoDBTables/InternalSingleValueEncodingContainer.swift +++ b/Sources/DynamoDBTables/InternalSingleValueEncodingContainer.swift @@ -35,11 +35,12 @@ class InternalSingleValueEncodingContainer: SingleValueEncodingContainer { let codingPath: [CodingKey] let userInfo: [CodingUserInfoKey: Any] - init(userInfo: [CodingUserInfoKey: Any], - codingPath: [CodingKey], - attributeNameTransform: ((String) -> String)?, - defaultValue: ContainerValueType?) - { + init( + userInfo: [CodingUserInfoKey: Any], + codingPath: [CodingKey], + attributeNameTransform: ((String) -> String)?, + defaultValue: ContainerValueType? + ) { self.containerValue = defaultValue self.userInfo = userInfo self.codingPath = codingPath @@ -148,11 +149,12 @@ class InternalSingleValueEncodingContainer: SingleValueEncodingContainer { } private func getAttributeName(key: CodingKey) -> String { - let attributeName: String = if let attributeNameTransform { - attributeNameTransform(key.stringValue) - } else { - key.stringValue - } + let attributeName: String = + if let attributeNameTransform { + attributeNameTransform(key.stringValue) + } else { + key.stringValue + } return attributeName } diff --git a/Sources/DynamoDBTables/InternalUnkeyedDecodingContainer.swift b/Sources/DynamoDBTables/InternalUnkeyedDecodingContainer.swift index d125aab..a9a4d1f 100644 --- a/Sources/DynamoDBTables/InternalUnkeyedDecodingContainer.swift +++ b/Sources/DynamoDBTables/InternalUnkeyedDecodingContainer.swift @@ -121,7 +121,9 @@ struct InternalUnkeyedDecodingContainer: UnkeyedDecodingContainer { return self.currentIndex >= values.count } - mutating func nestedContainer(keyedBy type: NestedKey.Type) throws + mutating func nestedContainer( + keyedBy type: NestedKey.Type + ) throws -> KeyedDecodingContainer where NestedKey: CodingKey { try self.createNestedContainer().container(keyedBy: type) @@ -155,11 +157,13 @@ struct InternalUnkeyedDecodingContainer: UnkeyedDecodingContainer { let value = values[index] - return InternalSingleValueDecodingContainer(attributeValue: value, - codingPath: self.decodingContainer.codingPath - + [InternalDynamoDBCodingKey(index: index)], - userInfo: self.decodingContainer.userInfo, - attributeNameTransform: self.decodingContainer.attributeNameTransform) + return InternalSingleValueDecodingContainer( + attributeValue: value, + codingPath: self.decodingContainer.codingPath + + [InternalDynamoDBCodingKey(index: index)], + userInfo: self.decodingContainer.userInfo, + attributeNameTransform: self.decodingContainer.attributeNameTransform + ) } } @@ -180,6 +184,6 @@ extension Date { extension String { var dateFromISO8601: Date? { - createISO8601DateFormatter().date(from: self) // "Mar 22, 2017, 10:22 AM" + createISO8601DateFormatter().date(from: self) // "Mar 22, 2017, 10:22 AM" } } diff --git a/Sources/DynamoDBTables/InternalUnkeyedEncodingContainer.swift b/Sources/DynamoDBTables/InternalUnkeyedEncodingContainer.swift index b31c29b..c7aba45 100644 --- a/Sources/DynamoDBTables/InternalUnkeyedEncodingContainer.swift +++ b/Sources/DynamoDBTables/InternalUnkeyedEncodingContainer.swift @@ -125,15 +125,19 @@ struct InternalUnkeyedEncodingContainer: UnkeyedEncodingContainer { // MARK: - - private func createNestedContainer(defaultValue: ContainerValueType? = nil) + private func createNestedContainer( + defaultValue: ContainerValueType? = nil + ) -> InternalSingleValueEncodingContainer { let index = self.enclosingContainer.unkeyedContainerCount - let nestedContainer = InternalSingleValueEncodingContainer(userInfo: enclosingContainer.userInfo, - codingPath: self.enclosingContainer.codingPath + [InternalDynamoDBCodingKey(index: index)], - attributeNameTransform: self.enclosingContainer.attributeNameTransform, - defaultValue: defaultValue) + let nestedContainer = InternalSingleValueEncodingContainer( + userInfo: enclosingContainer.userInfo, + codingPath: self.enclosingContainer.codingPath + [InternalDynamoDBCodingKey(index: index)], + attributeNameTransform: self.enclosingContainer.attributeNameTransform, + defaultValue: defaultValue + ) self.enclosingContainer.addToUnkeyedContainer(value: nestedContainer) return nestedContainer diff --git a/Sources/DynamoDBTables/Macros.swift b/Sources/DynamoDBTables/Macros.swift index 75af8bb..9e56e15 100644 --- a/Sources/DynamoDBTables/Macros.swift +++ b/Sources/DynamoDBTables/Macros.swift @@ -18,16 +18,30 @@ public macro PolymorphicWriteEntry() = #externalMacro( module: "DynamoDBTablesMacros", - type: "PolymorphicWriteEntryMacro") + type: "PolymorphicWriteEntryMacro" + ) -@attached(extension, conformances: PolymorphicTransactionConstraintEntry, names: named(handle(context:)), named(compositePrimaryKey)) +@attached( + extension, + conformances: PolymorphicTransactionConstraintEntry, + names: named(handle(context:)), + named(compositePrimaryKey) +) public macro PolymorphicTransactionConstraintEntry() = #externalMacro( module: "DynamoDBTablesMacros", - type: "PolymorphicTransactionConstraintEntryMacro") + type: "PolymorphicTransactionConstraintEntryMacro" + ) -@attached(extension, conformances: PolymorphicOperationReturnType, names: named(AttributesType), named(TimeToLiveAttributesType), named(types)) +@attached( + extension, + conformances: PolymorphicOperationReturnType, + names: named(AttributesType), + named(TimeToLiveAttributesType), + named(types) +) public macro PolymorphicOperationReturnType(databaseItemType: String = "StandardTypedDatabaseItem") = #externalMacro( module: "DynamoDBTablesMacros", - type: "PolymorphicOperationReturnTypeMacro") + type: "PolymorphicOperationReturnTypeMacro" + ) diff --git a/Sources/DynamoDBTables/PolymorphicOperationReturnType.swift b/Sources/DynamoDBTables/PolymorphicOperationReturnType.swift index 8caced4..b78f474 100644 --- a/Sources/DynamoDBTables/PolymorphicOperationReturnType.swift +++ b/Sources/DynamoDBTables/PolymorphicOperationReturnType.swift @@ -37,16 +37,22 @@ public protocol PolymorphicOperationReturnType: Sendable { associatedtype AttributesType: PrimaryKeyAttributes associatedtype TimeToLiveAttributesType: TimeToLiveAttributes - static var types: [(Codable.Type, PolymorphicOperationReturnOption)] { get } + static var types: [(Codable.Type, PolymorphicOperationReturnOption)] + { get } } -public struct PolymorphicOperationReturnOption: Sendable { +public struct PolymorphicOperationReturnOption< + AttributesType: PrimaryKeyAttributes, + ReturnType, + TimeToLiveAttributesType: TimeToLiveAttributes +>: Sendable { private let decodingPayloadHandler: @Sendable (Decoder) throws -> ReturnType private let typeConvertingPayloadHander: @Sendable (Any) throws -> ReturnType public init( - _ payloadHandler: @escaping @Sendable (TypedTTLDatabaseItem) -> ReturnType) - { + _ payloadHandler: @escaping @Sendable (TypedTTLDatabaseItem) + -> ReturnType + ) { @Sendable func newDecodingPayloadHandler(decoder: Decoder) throws -> ReturnType { let typedTTLDatabaseItem: TypedTTLDatabaseItem = @@ -57,10 +63,17 @@ public struct PolymorphicOperationReturnOption ReturnType { - guard let typedTTLDatabaseItem = input as? TypedTTLDatabaseItem else { - let description = "Expected to use item type \(TypedTTLDatabaseItem.self)." + guard + let typedTTLDatabaseItem = input + as? TypedTTLDatabaseItem + else { + let description = + "Expected to use item type \(TypedTTLDatabaseItem.self)." let context = DecodingError.Context(codingPath: [], debugDescription: description) - throw DecodingError.typeMismatch(TypedTTLDatabaseItem.self, context) + throw DecodingError.typeMismatch( + TypedTTLDatabaseItem.self, + context + ) } return payloadHandler(typedTTLDatabaseItem) @@ -90,7 +103,10 @@ struct ReturnTypeDecodable: Decodabl let values = try decoder.container(keyedBy: CodingKeys.self) let storedRowTypeName = try values.decode(String.self, forKey: .rowType) - var queryableTypeProviders: [String: PolymorphicOperationReturnOption] = [:] + var queryableTypeProviders: + [String: PolymorphicOperationReturnOption< + ReturnType.AttributesType, ReturnType, ReturnType.TimeToLiveAttributesType + >] = [:] for (type, provider) in ReturnType.types { queryableTypeProviders[getTypeRowIdentifier(type: type)] = provider } diff --git a/Sources/DynamoDBTables/PolymorphicWriteEntry.swift b/Sources/DynamoDBTables/PolymorphicWriteEntry.swift index 41693f6..ce02c46 100644 --- a/Sources/DynamoDBTables/PolymorphicWriteEntry.swift +++ b/Sources/DynamoDBTables/PolymorphicWriteEntry.swift @@ -32,7 +32,9 @@ public protocol PolymorphicWriteEntryTransform { associatedtype TableType init( - _ entry: WriteEntry, table: TableType) throws + _ entry: WriteEntry, + table: TableType + ) throws } // Conforming types are provided by the Table implementation to convert a `WriteEntry` into @@ -41,7 +43,9 @@ public protocol PolymorphicTransactionConstraintTransform { associatedtype TableType init( - _ entry: TransactionConstraintEntry, table: TableType) throws + _ entry: TransactionConstraintEntry, + table: TableType + ) throws } // Conforming types are provided by the application to express the different possible write entries @@ -57,7 +61,11 @@ public protocol PolymorphicWriteEntry: Sendable { public typealias StandardTransactionConstraintEntry = TransactionConstraintEntry -public enum TransactionConstraintEntry: Sendable { +public enum TransactionConstraintEntry< + AttributesType: PrimaryKeyAttributes, + ItemType: Codable & Sendable, + TimeToLiveAttributesType: TimeToLiveAttributes +>: Sendable { case required(existing: TypedTTLDatabaseItem) public var compositePrimaryKey: CompositePrimaryKey { @@ -73,13 +81,19 @@ public enum TransactionConstraintEntry(context: Context) throws -> Context.WriteTransactionConstraintType + func handle( + context: Context + ) throws -> Context.WriteTransactionConstraintType var compositePrimaryKey: CompositePrimaryKey { get } } -public struct EmptyPolymorphicTransactionConstraintEntry: PolymorphicTransactionConstraintEntry { - public func handle(context _: Context) throws -> Context.WriteTransactionConstraintType { +public struct EmptyPolymorphicTransactionConstraintEntry: + PolymorphicTransactionConstraintEntry +{ + public func handle( + context _: Context + ) throws -> Context.WriteTransactionConstraintType { fatalError("There are no items to transform") } @@ -94,18 +108,21 @@ public protocol PolymorphicWriteEntryContext { associatedtype WriteTransactionConstraintType: PolymorphicTransactionConstraintTransform func transform( - _ entry: WriteEntry) throws + _ entry: WriteEntry + ) throws -> WriteEntryTransformType func transform( - _ entry: TransactionConstraintEntry) throws + _ entry: TransactionConstraintEntry + ) throws -> WriteTransactionConstraintType } -public struct StandardPolymorphicWriteEntryContext: PolymorphicWriteEntryContext - where WriteEntryTransformType.TableType == WriteTransactionConstraintType.TableType -{ +public struct StandardPolymorphicWriteEntryContext< + WriteEntryTransformType: PolymorphicWriteEntryTransform, + WriteTransactionConstraintType: PolymorphicTransactionConstraintTransform +>: PolymorphicWriteEntryContext +where WriteEntryTransformType.TableType == WriteTransactionConstraintType.TableType { public typealias TableType = WriteEntryTransformType.TableType private let table: TableType @@ -114,13 +131,17 @@ public struct StandardPolymorphicWriteEntryContext) throws + public func transform( + _ entry: WriteEntry + ) throws -> WriteEntryTransformType { try .init(entry, table: self.table) } - public func transform(_ entry: TransactionConstraintEntry) throws + public func transform( + _ entry: TransactionConstraintEntry + ) throws -> WriteTransactionConstraintType { try .init(entry, table: self.table) diff --git a/Sources/DynamoDBTables/QueryInput+forSortKeyCondition.swift b/Sources/DynamoDBTables/QueryInput+forSortKeyCondition.swift index 053a2f0..e0f0b7d 100644 --- a/Sources/DynamoDBTables/QueryInput+forSortKeyCondition.swift +++ b/Sources/DynamoDBTables/QueryInput+forSortKeyCondition.swift @@ -28,14 +28,16 @@ import AWSDynamoDB import Foundation extension QueryInput { - static func forSortKeyCondition(partitionKey: String, - targetTableName: String, - primaryKeyType: AttributesType.Type, - sortKeyCondition: AttributeCondition?, - limit: Int?, - scanIndexForward: Bool, - exclusiveStartKey: String?, - consistentRead: Bool?) throws + static func forSortKeyCondition( + partitionKey: String, + targetTableName: String, + primaryKeyType: AttributesType.Type, + sortKeyCondition: AttributeCondition?, + limit: Int?, + scanIndexForward: Bool, + exclusiveStartKey: String?, + consistentRead: Bool? + ) throws -> AWSDynamoDB.QueryInput where AttributesType: PrimaryKeyAttributes { let expressionAttributeValues: [String: DynamoDBClientTypes.AttributeValue] @@ -43,7 +45,7 @@ extension QueryInput { let keyConditionExpression: String if let currentSortKeyCondition = sortKeyCondition { var withSortConditionAttributeValues: [String: DynamoDBClientTypes.AttributeValue] = [ - ":pk": DynamoDBClientTypes.AttributeValue.s(partitionKey), + ":pk": DynamoDBClientTypes.AttributeValue.s(partitionKey) ] let sortKeyExpression: String @@ -74,8 +76,10 @@ extension QueryInput { keyConditionExpression = "#pk= :pk AND \(sortKeyExpression)" - expressionAttributeNames = ["#pk": AttributesType.partitionKeyAttributeName, - "#sk": AttributesType.sortKeyAttributeName] + expressionAttributeNames = [ + "#pk": AttributesType.partitionKeyAttributeName, + "#sk": AttributesType.sortKeyAttributeName, + ] expressionAttributeValues = withSortConditionAttributeValues } else { keyConditionExpression = "#pk= :pk" @@ -84,21 +88,26 @@ extension QueryInput { expressionAttributeValues = [":pk": DynamoDBClientTypes.AttributeValue.s(partitionKey)] } - let inputExclusiveStartKey: [String: DynamoDBClientTypes.AttributeValue]? = if let exclusiveStartKey = exclusiveStartKey?.data(using: .utf8) { - try JSONDecoder().decode([String: DynamoDBClientTypes.AttributeValue].self, - from: exclusiveStartKey) - } else { - nil - } + let inputExclusiveStartKey: [String: DynamoDBClientTypes.AttributeValue]? = + if let exclusiveStartKey = exclusiveStartKey?.data(using: .utf8) { + try JSONDecoder().decode( + [String: DynamoDBClientTypes.AttributeValue].self, + from: exclusiveStartKey + ) + } else { + nil + } - return AWSDynamoDB.QueryInput(consistentRead: consistentRead, - exclusiveStartKey: inputExclusiveStartKey, - expressionAttributeNames: expressionAttributeNames, - expressionAttributeValues: expressionAttributeValues, - indexName: primaryKeyType.indexName, - keyConditionExpression: keyConditionExpression, - limit: limit, - scanIndexForward: scanIndexForward, - tableName: targetTableName) + return AWSDynamoDB.QueryInput( + consistentRead: consistentRead, + exclusiveStartKey: inputExclusiveStartKey, + expressionAttributeNames: expressionAttributeNames, + expressionAttributeValues: expressionAttributeValues, + indexName: primaryKeyType.indexName, + keyConditionExpression: keyConditionExpression, + limit: limit, + scanIndexForward: scanIndexForward, + tableName: targetTableName + ) } } diff --git a/Sources/DynamoDBTables/RetryConfiguration.swift b/Sources/DynamoDBTables/RetryConfiguration.swift index 11dc5a8..a1d54f0 100644 --- a/Sources/DynamoDBTables/RetryConfiguration.swift +++ b/Sources/DynamoDBTables/RetryConfiguration.swift @@ -29,9 +29,7 @@ import Foundation /// Type alias for a retry interval. public typealias RetryInterval = UInt32 -/** - Retry configuration for the requests made by a table.. - */ +/// Retry configuration for the requests made by a table.. public struct RetryConfiguration: Sendable { // Number of retries to be attempted public let numRetries: Int @@ -46,7 +44,7 @@ public struct RetryConfiguration: Sendable { /** Initializer. - + - Parameters: - numRetries: number of retries to be attempted. - baseRetryInterval: first interval of retry in millis. @@ -54,9 +52,13 @@ public struct RetryConfiguration: Sendable { - exponentialBackoff: exponential backoff for each retry - jitter: ramdomized backoff */ - public init(numRetries: Int, baseRetryInterval: RetryInterval, maxRetryInterval: RetryInterval, - exponentialBackoff: Double, jitter: Bool = true) - { + public init( + numRetries: Int, + baseRetryInterval: RetryInterval, + maxRetryInterval: RetryInterval, + exponentialBackoff: Double, + jitter: Bool = true + ) { self.numRetries = numRetries self.baseRetryInterval = baseRetryInterval self.maxRetryInterval = maxRetryInterval @@ -65,12 +67,14 @@ public struct RetryConfiguration: Sendable { } public func getRetryInterval(retriesRemaining: Int) -> RetryInterval { - let msInterval = RetryInterval(Double(baseRetryInterval) * pow(self.exponentialBackoff, Double(self.numRetries - retriesRemaining))) + let msInterval = RetryInterval( + Double(baseRetryInterval) * pow(self.exponentialBackoff, Double(self.numRetries - retriesRemaining)) + ) let boundedMsInterval = min(maxRetryInterval, msInterval) if self.jitter { if boundedMsInterval > 0 { - return RetryInterval.random(in: 0 ..< boundedMsInterval) + return RetryInterval.random(in: 0.. RowWithIndexCodi RowWithIndexCodingKey(stringValue: stringValue)! } -public struct RowWithIndex: Codable, CustomRowTypeIdentifier, Sendable { +public struct RowWithIndex: Codable, CustomRowTypeIdentifier, + Sendable +{ public static var rowTypeIdentifier: String? { let rowTypeIdentity = getTypeRowIdentifier(type: RowType.self) let indexIdentity = Identity.identity @@ -62,21 +64,27 @@ public struct RowWithIndex public let indexValue: String public let rowValue: RowType - public static func newItem(withIndex indexValue: String, - andValue rowValue: RowType) -> RowWithIndex - { - RowWithIndex(indexValue: indexValue, - rowValue: rowValue) + public static func newItem( + withIndex indexValue: String, + andValue rowValue: RowType + ) -> RowWithIndex { + RowWithIndex( + indexValue: indexValue, + rowValue: rowValue + ) } public func createUpdatedItem(withValue newRowValue: RowType) -> RowWithIndex { - RowWithIndex(indexValue: self.indexValue, - rowValue: newRowValue) + RowWithIndex( + indexValue: self.indexValue, + rowValue: newRowValue + ) } - init(indexValue: String, - rowValue: RowType) - { + init( + indexValue: String, + rowValue: RowType + ) { self.indexValue = indexValue self.rowValue = rowValue } diff --git a/Sources/DynamoDBTables/RowWithItemVersion.swift b/Sources/DynamoDBTables/RowWithItemVersion.swift index 5e178d0..4a4dc0a 100644 --- a/Sources/DynamoDBTables/RowWithItemVersion.swift +++ b/Sources/DynamoDBTables/RowWithItemVersion.swift @@ -40,23 +40,30 @@ public struct RowWithItemVersion: Codable, Sendable public let itemVersion: Int public let rowValue: RowType - public static func newItem(withVersion itemVersion: Int = 1, - withValue rowValue: RowType) -> RowWithItemVersion - { - RowWithItemVersion(itemVersion: itemVersion, - rowValue: rowValue) + public static func newItem( + withVersion itemVersion: Int = 1, + withValue rowValue: RowType + ) -> RowWithItemVersion { + RowWithItemVersion( + itemVersion: itemVersion, + rowValue: rowValue + ) } - public func createUpdatedItem(withVersion itemVersion: Int? = nil, - withValue newRowValue: RowType) -> RowWithItemVersion - { - RowWithItemVersion(itemVersion: itemVersion != nil ? itemVersion! : self.itemVersion + 1, - rowValue: newRowValue) + public func createUpdatedItem( + withVersion itemVersion: Int? = nil, + withValue newRowValue: RowType + ) -> RowWithItemVersion { + RowWithItemVersion( + itemVersion: itemVersion != nil ? itemVersion! : self.itemVersion + 1, + rowValue: newRowValue + ) } - init(itemVersion: Int, - rowValue: RowType) - { + init( + itemVersion: Int, + rowValue: RowType + ) { self.itemVersion = itemVersion self.rowValue = rowValue } diff --git a/Sources/DynamoDBTables/RowWithItemVersionProtocol.swift b/Sources/DynamoDBTables/RowWithItemVersionProtocol.swift index c01ec97..69920bf 100644 --- a/Sources/DynamoDBTables/RowWithItemVersionProtocol.swift +++ b/Sources/DynamoDBTables/RowWithItemVersionProtocol.swift @@ -26,10 +26,8 @@ import Foundation -/** - Protocol for a item payload wrapper that declares an item version. - Primarily required to allow the constrained extension below. - */ +/// Protocol for a item payload wrapper that declares an item version. +/// Primarily required to allow the constrained extension below. public protocol RowWithItemVersionProtocol { associatedtype RowType: Codable @@ -40,17 +38,19 @@ public protocol RowWithItemVersionProtocol { /// Function that accepts a version and an updated row version and returns /// an instance of the implementing type - func createUpdatedItem(withVersion itemVersion: Int?, - withValue newRowValue: RowType) -> Self + func createUpdatedItem( + withVersion itemVersion: Int?, + withValue newRowValue: RowType + ) -> Self /// Function that accepts an updated row version and returns /// an instance of the implementing type func createUpdatedItem(withValue newRowValue: RowType) -> Self } -public extension RowWithItemVersionProtocol { +extension RowWithItemVersionProtocol { /// Default implementation that delegates to createUpdatedItem(withVersion:withValue:) - func createUpdatedItem(withValue newRowValue: RowType) -> Self { + public func createUpdatedItem(withValue newRowValue: RowType) -> Self { self.createUpdatedItem(withVersion: nil, withValue: newRowValue) } } diff --git a/Sources/DynamoDBTables/Sequence+concurrency.swift b/Sources/DynamoDBTables/Sequence+concurrency.swift index 7e8c744..29fced8 100644 --- a/Sources/DynamoDBTables/Sequence+concurrency.swift +++ b/Sources/DynamoDBTables/Sequence+concurrency.swift @@ -36,8 +36,8 @@ extension Sequence { /// - parameter operation: The closure to run for each element. /// - throws: Rethrows any error thrown by the passed closure. func asyncForEach( - _ operation: @Sendable (Element) async throws -> Void) async rethrows - { + _ operation: @Sendable (Element) async throws -> Void + ) async rethrows { for element in self { try await operation(element) } @@ -62,8 +62,8 @@ extension Sequence where Element: Sendable { /// the transformed values will match the original sequence. func concurrentMap( withPriority priority: TaskPriority? = nil, - _ transform: @Sendable @escaping (Element) async -> T) async -> [T] - { + _ transform: @Sendable @escaping (Element) async -> T + ) async -> [T] { await withTaskGroup(of: (offset: Int, value: T).self) { group in var taskCount = 0 for (idx, element) in enumerated() { @@ -100,8 +100,8 @@ extension Sequence where Element: Sendable { /// - throws: Rethrows any error thrown by the passed closure. func concurrentMap( withPriority priority: TaskPriority? = nil, - _ transform: @Sendable @escaping (Element) async throws -> T) async throws -> [T] - { + _ transform: @Sendable @escaping (Element) async throws -> T + ) async throws -> [T] { try await withThrowingTaskGroup(of: (offset: Int, value: T).self) { group in var taskCount = 0 for (idx, element) in enumerated() { @@ -139,8 +139,8 @@ extension Sequence where Element: Sendable { /// except for the values that were transformed into `nil`. /// - throws: Rethrows any error thrown by the passed closure. func asyncCompactMap( - _ transform: @Sendable (Element) async throws -> T?) async rethrows -> [T] - { + _ transform: @Sendable (Element) async throws -> T? + ) async rethrows -> [T] { var values = [T]() for element in self { @@ -171,8 +171,8 @@ extension Sequence where Element: Sendable { /// except for the values that were transformed into `nil`. func concurrentCompactMap( withPriority priority: TaskPriority? = nil, - _ transform: @Sendable @escaping (Element) async -> T?) async -> [T] - { + _ transform: @Sendable @escaping (Element) async -> T? + ) async -> [T] { await withTaskGroup(of: (offset: Int, value: T?).self) { group in var taskCount = 0 for (idx, element) in enumerated() { @@ -211,8 +211,8 @@ extension Sequence where Element: Sendable { /// - throws: Rethrows any error thrown by the passed closure. func concurrentCompactMap( withPriority priority: TaskPriority? = nil, - _ transform: @Sendable @escaping (Element) async throws -> T?) async throws -> [T] - { + _ transform: @Sendable @escaping (Element) async throws -> T? + ) async throws -> [T] { try await withThrowingTaskGroup(of: (offset: Int, value: T?).self) { group in var taskCount = 0 for (idx, element) in enumerated() { @@ -253,8 +253,8 @@ extension Sequence where Element: Sendable { /// within the returned array. func concurrentFlatMap( withPriority priority: TaskPriority? = nil, - _ transform: @Sendable @escaping (Element) async -> T) async -> [T.Element] - { + _ transform: @Sendable @escaping (Element) async -> T + ) async -> [T.Element] { await withTaskGroup(of: (offset: Int, value: T).self) { group in var taskCount = 0 for (idx, element) in enumerated() { @@ -294,8 +294,8 @@ extension Sequence where Element: Sendable { /// - throws: Rethrows any error thrown by the passed closure. func concurrentFlatMap( withPriority priority: TaskPriority? = nil, - _ transform: @Sendable @escaping (Element) async throws -> T) async throws -> [T.Element] - { + _ transform: @Sendable @escaping (Element) async throws -> T + ) async throws -> [T.Element] { try await withThrowingTaskGroup(of: (offset: Int, value: T).self) { group in var taskCount = 0 for (idx, element) in enumerated() { @@ -315,8 +315,8 @@ extension Sequence where Element: Sendable { } } -extension Collection{ - func asNonOptionalCollection() -> [T] where Element == Optional { +extension Collection { + func asNonOptionalCollection() -> [T] where Element == T? { return self.map { optional in guard let finalValue = optional else { fatalError("Mapped task did not complete as expected") diff --git a/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift b/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift index 1771ac5..5b0f8d9 100644 --- a/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift +++ b/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift @@ -34,25 +34,28 @@ private let maxStatementLength = 8192 to a database by incrementing a row's version every time it is added for a specified number of requests. */ -public actor SimulateConcurrencyDynamoDBCompositePrimaryKeyTable: DynamoDBCompositePrimaryKeyTable, Sendable { +public actor SimulateConcurrencyDynamoDBCompositePrimaryKeyTable: + DynamoDBCompositePrimaryKeyTable, Sendable +{ let wrappedDynamoDBTable: Wrapped let simulateConcurrencyModifications: Int var previousConcurrencyModifications: Int let simulateOnInsertItem: Bool let simulateOnUpdateItem: Bool - /** - Initializer. - - - Parameters: - - wrappedDynamoDBTable: The underlying DynamoDBTable used by this implementation. - - simulateConcurrencyModifications: the number of get requests to simulate concurrency for. - - simulateOnInsertItem: if this instance should simulate concurrency on insertItem. - - simulateOnUpdateItem: if this instance should simulate concurrency on updateItem. - */ - public init(wrappedDynamoDBTable: Wrapped, simulateConcurrencyModifications: Int, - simulateOnInsertItem: Bool = true, simulateOnUpdateItem: Bool = true) - { + /// Initializer. + /// + /// - Parameters: + /// - wrappedDynamoDBTable: The underlying DynamoDBTable used by this implementation. + /// - simulateConcurrencyModifications: the number of get requests to simulate concurrency for. + /// - simulateOnInsertItem: if this instance should simulate concurrency on insertItem. + /// - simulateOnUpdateItem: if this instance should simulate concurrency on updateItem. + public init( + wrappedDynamoDBTable: Wrapped, + simulateConcurrencyModifications: Int, + simulateOnInsertItem: Bool = true, + simulateOnUpdateItem: Bool = true + ) { self.wrappedDynamoDBTable = wrappedDynamoDBTable self.simulateConcurrencyModifications = simulateConcurrencyModifications self.previousConcurrencyModifications = 0 @@ -64,7 +67,9 @@ public actor SimulateConcurrencyDynamoDBCompositePrimaryKeyTable maxStatementLength { throw DynamoDBTableError.statementLengthExceeded( - reason: "failed to satisfy constraint: Member must have length less than or equal to \(maxStatementLength). Actual length \(entryString.count)") + reason: + "failed to satisfy constraint: Member must have length less than or equal to \(maxStatementLength). Actual length \(entryString.count)" + ) } } @@ -90,12 +95,14 @@ public actor SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( newItem: TypedTTLDatabaseItem, - existingItem: TypedTTLDatabaseItem) async throws - { + existingItem: TypedTTLDatabaseItem + ) async throws { // if there are still modifications to be made and there is an existing row if self.simulateOnUpdateItem, self.previousConcurrencyModifications < self.simulateConcurrencyModifications { - try await self.wrappedDynamoDBTable.updateItem(newItem: existingItem.createUpdatedItem(withValue: existingItem.rowValue), - existingItem: existingItem) + try await self.wrappedDynamoDBTable.updateItem( + newItem: existingItem.createUpdatedItem(withValue: existingItem.rowValue), + existingItem: existingItem + ) self.previousConcurrencyModifications += 1 @@ -113,8 +120,8 @@ public actor SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( _ entries: [WriteEntry], - constraints: [TransactionConstraintEntry]) async throws - { + constraints: [TransactionConstraintEntry] + ) async throws { try await self.wrappedDynamoDBTable.transactWrite(entries, constraints: constraints) } @@ -122,11 +129,14 @@ public actor SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( - _ entries: [WriteEntryType], constraints: [TransactionConstraintEntryType]) async throws - where WriteEntryType.AttributesType == TransactionConstraintEntryType.AttributesType - { + public func polymorphicTransactWrite< + WriteEntryType: PolymorphicWriteEntry, + TransactionConstraintEntryType: PolymorphicTransactionConstraintEntry + >( + _ entries: [WriteEntryType], + constraints: [TransactionConstraintEntryType] + ) async throws + where WriteEntryType.AttributesType == TransactionConstraintEntryType.AttributesType { try await self.wrappedDynamoDBTable.polymorphicTransactWrite(entries, constraints: constraints) } @@ -153,7 +163,9 @@ public actor SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(forKey key: CompositePrimaryKey) async throws + public func getItem( + forKey key: CompositePrimaryKey + ) async throws -> TypedTTLDatabaseItem? { // simply delegate to the wrapped implementation @@ -161,7 +173,8 @@ public actor SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( - forKeys keys: [CompositePrimaryKey]) async throws + forKeys keys: [CompositePrimaryKey] + ) async throws -> [CompositePrimaryKey: ReturnedType] { // simply delegate to the wrapped implementation @@ -185,71 +198,93 @@ public actor SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?) async throws + public func polymorphicQuery( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition? + ) async throws -> [ReturnedType] { // simply delegate to the wrapped implementation - try await self.wrappedDynamoDBTable.polymorphicQuery(forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition) + try await self.wrappedDynamoDBTable.polymorphicQuery( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition + ) } - public func polymorphicQuery(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - exclusiveStartKey: String?) async throws + public func polymorphicQuery( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + exclusiveStartKey: String? + ) async throws -> (items: [ReturnedType], lastEvaluatedKey: String?) { // simply delegate to the wrapped implementation - try await self.wrappedDynamoDBTable.polymorphicQuery(forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition, - limit: limit, - exclusiveStartKey: exclusiveStartKey) + try await self.wrappedDynamoDBTable.polymorphicQuery( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition, + limit: limit, + exclusiveStartKey: exclusiveStartKey + ) } - public func polymorphicQuery(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - scanIndexForward: Bool, - exclusiveStartKey: String?) async throws + public func polymorphicQuery( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + scanIndexForward: Bool, + exclusiveStartKey: String? + ) async throws -> (items: [ReturnedType], lastEvaluatedKey: String?) { // simply delegate to the wrapped implementation - try await self.wrappedDynamoDBTable.polymorphicQuery(forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition, - limit: limit, - scanIndexForward: scanIndexForward, - exclusiveStartKey: exclusiveStartKey) + try await self.wrappedDynamoDBTable.polymorphicQuery( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition, + limit: limit, + scanIndexForward: scanIndexForward, + exclusiveStartKey: exclusiveStartKey + ) } public func polymorphicExecute( partitionKeys: [String], attributesFilter: [String]?, - additionalWhereClause: String?) async throws + additionalWhereClause: String? + ) async throws -> [ReturnedType] { // simply delegate to the wrapped implementation - try await self.wrappedDynamoDBTable.polymorphicExecute(partitionKeys: partitionKeys, - attributesFilter: attributesFilter, - additionalWhereClause: additionalWhereClause) + try await self.wrappedDynamoDBTable.polymorphicExecute( + partitionKeys: partitionKeys, + attributesFilter: attributesFilter, + additionalWhereClause: additionalWhereClause + ) } public func polymorphicExecute( partitionKeys: [String], attributesFilter: [String]?, - additionalWhereClause: String?, nextToken: String?) async throws + additionalWhereClause: String?, + nextToken: String? + ) async throws -> (items: [ReturnedType], lastEvaluatedKey: String?) { // simply delegate to the wrapped implementation - try await self.wrappedDynamoDBTable.polymorphicExecute(partitionKeys: partitionKeys, - attributesFilter: attributesFilter, - additionalWhereClause: additionalWhereClause, - nextToken: nextToken) + try await self.wrappedDynamoDBTable.polymorphicExecute( + partitionKeys: partitionKeys, + attributesFilter: attributesFilter, + additionalWhereClause: additionalWhereClause, + nextToken: nextToken + ) } public func getItems( - forKeys keys: [CompositePrimaryKey]) async throws - -> [CompositePrimaryKey: TypedTTLDatabaseItem] + forKeys keys: [CompositePrimaryKey] + ) async throws + -> [CompositePrimaryKey: TypedTTLDatabaseItem< + AttributesType, ItemType, TimeToLiveAttributesType + >] { // simply delegate to the wrapped implementation try await self.wrappedDynamoDBTable.getItems(forKeys: keys) @@ -258,49 +293,68 @@ public actor SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( partitionKeys: [String], attributesFilter: [String]?, - additionalWhereClause: String?) async throws + additionalWhereClause: String? + ) async throws -> [TypedTTLDatabaseItem] { // simply delegate to the wrapped implementation - try await self.wrappedDynamoDBTable.execute(partitionKeys: partitionKeys, - attributesFilter: attributesFilter, - additionalWhereClause: additionalWhereClause) + try await self.wrappedDynamoDBTable.execute( + partitionKeys: partitionKeys, + attributesFilter: attributesFilter, + additionalWhereClause: additionalWhereClause + ) } public func execute( partitionKeys: [String], attributesFilter: [String]?, - additionalWhereClause: String?, nextToken: String?) async throws - -> (items: [TypedTTLDatabaseItem], lastEvaluatedKey: String?) + additionalWhereClause: String?, + nextToken: String? + ) async throws + -> ( + items: [TypedTTLDatabaseItem], lastEvaluatedKey: String? + ) { // simply delegate to the wrapped implementation - try await self.wrappedDynamoDBTable.execute(partitionKeys: partitionKeys, - attributesFilter: attributesFilter, - additionalWhereClause: additionalWhereClause, - nextToken: nextToken) + try await self.wrappedDynamoDBTable.execute( + partitionKeys: partitionKeys, + attributesFilter: attributesFilter, + additionalWhereClause: additionalWhereClause, + nextToken: nextToken + ) } - public func query(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?) async throws + public func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition? + ) async throws -> [TypedTTLDatabaseItem] { // simply delegate to the wrapped implementation - try await self.wrappedDynamoDBTable.query(forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition) + try await self.wrappedDynamoDBTable.query( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition + ) } - public func query(forPartitionKey partitionKey: String, - sortKeyCondition: AttributeCondition?, - limit: Int?, - scanIndexForward: Bool, - exclusiveStartKey: String?) async throws - -> (items: [TypedTTLDatabaseItem], lastEvaluatedKey: String?) + public func query( + forPartitionKey partitionKey: String, + sortKeyCondition: AttributeCondition?, + limit: Int?, + scanIndexForward: Bool, + exclusiveStartKey: String? + ) async throws + -> ( + items: [TypedTTLDatabaseItem], lastEvaluatedKey: String? + ) { // simply delegate to the wrapped implementation - try await self.wrappedDynamoDBTable.query(forPartitionKey: partitionKey, - sortKeyCondition: sortKeyCondition, - limit: limit, - scanIndexForward: scanIndexForward, - exclusiveStartKey: exclusiveStartKey) + try await self.wrappedDynamoDBTable.query( + forPartitionKey: partitionKey, + sortKeyCondition: sortKeyCondition, + limit: limit, + scanIndexForward: scanIndexForward, + exclusiveStartKey: exclusiveStartKey + ) } } diff --git a/Sources/DynamoDBTables/String+DynamoDBKey.swift b/Sources/DynamoDBTables/String+DynamoDBKey.swift index 5f2ab34..168a746 100644 --- a/Sources/DynamoDBTables/String+DynamoDBKey.swift +++ b/Sources/DynamoDBTables/String+DynamoDBKey.swift @@ -27,15 +27,15 @@ import Foundation /// Extension for Arrays of Strings -public extension [String] { +extension [String] { // Transforms the Array into a Dynamo key - putting dots between each element. - var dynamodbKey: String { + public var dynamodbKey: String { // return all elements joined with dots self.joined(separator: ".") } // Transforms an Array into a DynamoDB key prefix - a DynamoDB key with a dot on the end. - var dynamodbKeyPrefix: String { + public var dynamodbKeyPrefix: String { let dynamodbKey = self.dynamodbKey if dynamodbKey.count == 0 { return "" @@ -48,7 +48,7 @@ public extension [String] { dot) corresponding to this array dropped as a prefix. Returns nil if the provided string doesn't have the prefix. */ - func dropAsDynamoDBKeyPrefix(from string: String) -> String? { + public func dropAsDynamoDBKeyPrefix(from string: String) -> String? { let prefix = self.dynamodbKeyPrefix guard string.hasPrefix(prefix) else { @@ -61,13 +61,13 @@ public extension [String] { /** Transforms the Array into a DynamoDB key - putting dots between each element - with a prefix element specifying the version. - + - Parameters: - versionNumber: The version number to prefix. - minimumFieldWidth: the minimum field width of the version field. Leading zeros will be padded if required. */ - func dynamodbKeyWithPrefixedVersion(_ versionNumber: Int, minimumFieldWidth: Int) -> String { + public func dynamodbKeyWithPrefixedVersion(_ versionNumber: Int, minimumFieldWidth: Int) -> String { let versionAsString = String(format: "%0\(minimumFieldWidth)d", versionNumber) return (["v\(versionAsString)"] + self).dynamodbKey } diff --git a/Sources/DynamoDBTables/TimeToLive.swift b/Sources/DynamoDBTables/TimeToLive.swift index 9b50600..a38cb88 100644 --- a/Sources/DynamoDBTables/TimeToLive.swift +++ b/Sources/DynamoDBTables/TimeToLive.swift @@ -51,11 +51,17 @@ public struct TimeToLive: Sendable, Codabl public init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: DynamoDBAttributesTypeCodingKey.self) - self.timeToLiveTimestamp = try values.decode(Int64.self, forKey: DynamoDBAttributesTypeCodingKey(stringValue: AttributesType.timeToLiveAttributeName)!) + self.timeToLiveTimestamp = try values.decode( + Int64.self, + forKey: DynamoDBAttributesTypeCodingKey(stringValue: AttributesType.timeToLiveAttributeName)! + ) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: DynamoDBAttributesTypeCodingKey.self) - try container.encode(self.timeToLiveTimestamp, forKey: DynamoDBAttributesTypeCodingKey(stringValue: AttributesType.timeToLiveAttributeName)!) + try container.encode( + self.timeToLiveTimestamp, + forKey: DynamoDBAttributesTypeCodingKey(stringValue: AttributesType.timeToLiveAttributeName)! + ) } } diff --git a/Sources/DynamoDBTables/TypedDatabaseItemWithTimeToLive+RowWithItemVersionProtocol.swift b/Sources/DynamoDBTables/TypedDatabaseItemWithTimeToLive+RowWithItemVersionProtocol.swift index a80be5e..56613ad 100644 --- a/Sources/DynamoDBTables/TypedDatabaseItemWithTimeToLive+RowWithItemVersionProtocol.swift +++ b/Sources/DynamoDBTables/TypedDatabaseItemWithTimeToLive+RowWithItemVersionProtocol.swift @@ -28,22 +28,26 @@ import Foundation /// An extension for TypedTTLDatabaseItem that is constrained by the RowType conforming /// to RowWithItemVersionProtocol -public extension TypedTTLDatabaseItem where RowType: RowWithItemVersionProtocol { +extension TypedTTLDatabaseItem where RowType: RowWithItemVersionProtocol { /// Helper function wrapping createUpdatedItem that will verify if /// conditionalStatusVersion is provided that it matches the version /// of the current item - func createUpdatedRowWithItemVersion(withValue value: RowType.RowType, - conditionalStatusVersion: Int?, - andTimeToLive timeToLive: TimeToLive? = nil) throws + public func createUpdatedRowWithItemVersion( + withValue value: RowType.RowType, + conditionalStatusVersion: Int?, + andTimeToLive timeToLive: TimeToLive? = nil + ) throws -> TypedTTLDatabaseItem { // if we can only update a particular version if let overwriteVersion = conditionalStatusVersion, - rowValue.itemVersion != overwriteVersion + rowValue.itemVersion != overwriteVersion { - throw DynamoDBTableError.concurrencyError(partitionKey: compositePrimaryKey.partitionKey, - sortKey: compositePrimaryKey.sortKey, - message: "Current row did not have the required version '\(overwriteVersion)'") + throw DynamoDBTableError.concurrencyError( + partitionKey: compositePrimaryKey.partitionKey, + sortKey: compositePrimaryKey.sortKey, + message: "Current row did not have the required version '\(overwriteVersion)'" + ) } let updatedPayloadWithVersion: RowType = rowValue.createUpdatedItem(withValue: value) diff --git a/Sources/DynamoDBTables/TypedDatabaseItemWithTimeToLive.swift b/Sources/DynamoDBTables/TypedDatabaseItemWithTimeToLive.swift index 85ab1b3..a0b6d86 100644 --- a/Sources/DynamoDBTables/TypedDatabaseItemWithTimeToLive.swift +++ b/Sources/DynamoDBTables/TypedDatabaseItemWithTimeToLive.swift @@ -41,10 +41,11 @@ public struct RowStatus: Sendable, Codable { } } -public struct TypedTTLDatabaseItem: Codable, Sendable -{ + TimeToLiveAttributesType: TimeToLiveAttributes +>: Codable, Sendable { public let compositePrimaryKey: CompositePrimaryKey public let createDate: Date public let rowStatus: RowStatus @@ -56,9 +57,11 @@ public struct TypedTTLDatabaseItem, - andValue value: RowType, - andTimeToLive timeToLive: TimeToLive? = nil) + public static func newItem( + withKey key: CompositePrimaryKey, + andValue value: RowType, + andTimeToLive timeToLive: TimeToLive? = nil + ) -> TypedTTLDatabaseItem { TypedTTLDatabaseItem( @@ -66,28 +69,35 @@ public struct TypedTTLDatabaseItem? = nil) + public func createUpdatedItem( + withValue value: RowType, + andTimeToLive timeToLive: TimeToLive? = nil + ) -> TypedTTLDatabaseItem { TypedTTLDatabaseItem( compositePrimaryKey: self.compositePrimaryKey, createDate: self.createDate, - rowStatus: RowStatus(rowVersion: self.rowStatus.rowVersion + 1, - lastUpdatedDate: Date()), + rowStatus: RowStatus( + rowVersion: self.rowStatus.rowVersion + 1, + lastUpdatedDate: Date() + ), rowValue: value, - timeToLive: timeToLive) + timeToLive: timeToLive + ) } - init(compositePrimaryKey: CompositePrimaryKey, - createDate: Date, - rowStatus: RowStatus, - rowValue: RowType, - timeToLive: TimeToLive? = nil) - { + init( + compositePrimaryKey: CompositePrimaryKey, + createDate: Date, + rowStatus: RowStatus, + rowValue: RowType, + timeToLive: TimeToLive? = nil + ) { self.compositePrimaryKey = compositePrimaryKey self.createDate = createDate self.rowStatus = rowStatus diff --git a/Sources/DynamoDBTablesMacros/BaseEntryMacro.swift b/Sources/DynamoDBTablesMacros/BaseEntryMacro.swift index 33f3043..2262839 100644 --- a/Sources/DynamoDBTablesMacros/BaseEntryMacro.swift +++ b/Sources/DynamoDBTablesMacros/BaseEntryMacro.swift @@ -56,7 +56,10 @@ enum BaseEntryDiagnostic: String, DiagnosticMes } enum BaseEntryMacro: ExtensionMacro { - private static func getCases(caseMembers: [EnumCaseDeclSyntax], context: some MacroExpansionContext) + private static func getCases( + caseMembers: [EnumCaseDeclSyntax], + context: some MacroExpansionContext + ) -> (hasDiagnostics: Bool, handleCases: SwitchCaseListSyntax, compositePrimaryKeyCases: SwitchCaseListSyntax) { var handleCases: SwitchCaseListSyntax = [] @@ -66,7 +69,9 @@ enum BaseEntryMacro: ExtensionMacro { for element in caseMember.elements { // ensure that the enum case only has one parameter guard let parameterClause = element.parameterClause, parameterClause.parameters.count == 1 else { - context.diagnose(.init(node: element, message: BaseEntryDiagnostic.enumCasesMustHaveASingleParameter)) + context.diagnose( + .init(node: element, message: BaseEntryDiagnostic.enumCasesMustHaveASingleParameter) + ) hasDiagnostics = true // do nothing for this case continue @@ -79,7 +84,8 @@ enum BaseEntryMacro: ExtensionMacro { """ case let .\(element.name)(writeEntry): return try context.transform(writeEntry) - """) + """ + ) handleCases.append(handleCaseSyntax) @@ -87,7 +93,8 @@ enum BaseEntryMacro: ExtensionMacro { """ case let .\(element.name)(writeEntry): return writeEntry.compositePrimaryKey - """) + """ + ) compositePrimaryKeyCases.append(compositePrimaryKeyCaseSyntax) } @@ -101,17 +108,21 @@ enum BaseEntryMacro: ExtensionMacro { attachedTo declaration: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], - in context: some MacroExpansionContext) throws -> [ExtensionDeclSyntax] - { + in context: some MacroExpansionContext + ) throws -> [ExtensionDeclSyntax] { // make sure this is attached to an enum guard let enumDeclaration = declaration as? EnumDeclSyntax else { - context.diagnose(.init(node: declaration, message: BaseEntryDiagnostic.notAttachedToEnumDeclaration)) + context.diagnose( + .init(node: declaration, message: BaseEntryDiagnostic.notAttachedToEnumDeclaration) + ) return [] } let requiresProtocolConformance = protocols.reduce(false) { partialResult, protocolSyntax in - if let identifierTypeSyntax = protocolSyntax.as(IdentifierTypeSyntax.self), identifierTypeSyntax.name.text == Attributes.protocolName { + if let identifierTypeSyntax = protocolSyntax.as(IdentifierTypeSyntax.self), + identifierTypeSyntax.name.text == Attributes.protocolName + { return true } @@ -130,32 +141,41 @@ enum BaseEntryMacro: ExtensionMacro { // make sure this is attached to an enum guard !caseMembers.isEmpty else { - context.diagnose(.init(node: declaration, message: BaseEntryDiagnostic.enumMustNotHaveZeroCases)) + context.diagnose( + .init(node: declaration, message: BaseEntryDiagnostic.enumMustNotHaveZeroCases) + ) return [] } - let (hasDiagnostics, handleCases, compositePrimaryKeyCases) = self.getCases(caseMembers: caseMembers, context: context) + let (hasDiagnostics, handleCases, compositePrimaryKeyCases) = self.getCases( + caseMembers: caseMembers, + context: context + ) if hasDiagnostics { return [] } - let type = TypeSyntax(extendedGraphemeClusterLiteral: requiresProtocolConformance ? "\(type.trimmed): \(Attributes.protocolName) " - : "\(type.trimmed) ") + let type = TypeSyntax( + extendedGraphemeClusterLiteral: requiresProtocolConformance + ? "\(type.trimmed): \(Attributes.protocolName) " + : "\(type.trimmed) " + ) let extensionDecl = try ExtensionDeclSyntax( extendedType: type, memberBlockBuilder: { try FunctionDeclSyntax( - "func handle(context: Context) throws -> Context.\(raw: Attributes.transformType)") - { + "func handle(context: Context) throws -> Context.\(raw: Attributes.transformType)" + ) { SwitchExprSyntax(subject: ExprSyntax(stringLiteral: "self"), cases: handleCases) } try VariableDeclSyntax("var compositePrimaryKey: StandardCompositePrimaryKey") { SwitchExprSyntax(subject: ExprSyntax(stringLiteral: "self"), cases: compositePrimaryKeyCases) } - }) + } + ) return [extensionDecl] } diff --git a/Sources/DynamoDBTablesMacros/Plugin.swift b/Sources/DynamoDBTablesMacros/Plugin.swift index b3486e3..b068023 100644 --- a/Sources/DynamoDBTablesMacros/Plugin.swift +++ b/Sources/DynamoDBTablesMacros/Plugin.swift @@ -15,15 +15,15 @@ // #if canImport(SwiftCompilerPlugin) - import SwiftCompilerPlugin - import SwiftSyntaxMacros +import SwiftCompilerPlugin +import SwiftSyntaxMacros - @main - struct DynamoDBTablesMacrosCompilerPlugin: CompilerPlugin { - let providingMacros: [Macro.Type] = [ - PolymorphicWriteEntryMacro.self, - PolymorphicTransactionConstraintEntryMacro.self, - PolymorphicOperationReturnTypeMacro.self, - ] - } +@main +struct DynamoDBTablesMacrosCompilerPlugin: CompilerPlugin { + let providingMacros: [Macro.Type] = [ + PolymorphicWriteEntryMacro.self, + PolymorphicTransactionConstraintEntryMacro.self, + PolymorphicOperationReturnTypeMacro.self, + ] +} #endif diff --git a/Sources/DynamoDBTablesMacros/PolymorphicOperationReturnTypeMacro.swift b/Sources/DynamoDBTablesMacros/PolymorphicOperationReturnTypeMacro.swift index 2a1fede..3b6bc7e 100644 --- a/Sources/DynamoDBTablesMacros/PolymorphicOperationReturnTypeMacro.swift +++ b/Sources/DynamoDBTablesMacros/PolymorphicOperationReturnTypeMacro.swift @@ -41,19 +41,23 @@ public enum PolymorphicOperationReturnTypeMacro: ExtensionMacro { attachedTo declaration: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], - in context: some MacroExpansionContext) throws -> [ExtensionDeclSyntax] - { + in context: some MacroExpansionContext + ) throws -> [ExtensionDeclSyntax] { // make sure this is attached to an enum guard let enumDeclaration = declaration as? EnumDeclSyntax else { - context.diagnose(.init(node: declaration, message: BaseEntryDiagnostic.notAttachedToEnumDeclaration)) + context.diagnose( + .init(node: declaration, message: BaseEntryDiagnostic.notAttachedToEnumDeclaration) + ) return [] } let databaseItemType: String let standardDatabaseType = "StandardTypedDatabaseItem" - if let arguments = node.arguments, case let .argumentList(argumentList) = arguments, let firstArgument = argumentList.first, argumentList.count == 1, - firstArgument.label?.text == "databaseItemType", let expression = firstArgument.expression.as(StringLiteralExprSyntax.self) + if let arguments = node.arguments, case let .argumentList(argumentList) = arguments, + let firstArgument = argumentList.first, argumentList.count == 1, + firstArgument.label?.text == "databaseItemType", + let expression = firstArgument.expression.as(StringLiteralExprSyntax.self) { databaseItemType = expression.representedLiteralValue ?? standardDatabaseType } else { @@ -61,7 +65,9 @@ public enum PolymorphicOperationReturnTypeMacro: ExtensionMacro { } let requiresProtocolConformance = protocols.reduce(false) { partialResult, protocolSyntax in - if let identifierTypeSyntax = protocolSyntax.as(IdentifierTypeSyntax.self), identifierTypeSyntax.name.text == Attributes.protocolName { + if let identifierTypeSyntax = protocolSyntax.as(IdentifierTypeSyntax.self), + identifierTypeSyntax.name.text == Attributes.protocolName + { return true } @@ -80,19 +86,28 @@ public enum PolymorphicOperationReturnTypeMacro: ExtensionMacro { // make sure this is attached to an enum guard !caseMembers.isEmpty else { - context.diagnose(.init(node: declaration, message: BaseEntryDiagnostic.enumMustNotHaveZeroCases)) + context.diagnose( + .init(node: declaration, message: BaseEntryDiagnostic.enumMustNotHaveZeroCases) + ) return [] } - let (hasDiagnostics, handleCases) = self.getCases(caseMembers: caseMembers, context: context, databaseItemType: databaseItemType) + let (hasDiagnostics, handleCases) = self.getCases( + caseMembers: caseMembers, + context: context, + databaseItemType: databaseItemType + ) if hasDiagnostics { return [] } - let type = TypeSyntax(extendedGraphemeClusterLiteral: requiresProtocolConformance ? "\(type.trimmed): \(Attributes.protocolName) " - : "\(type.trimmed) ") + let type = TypeSyntax( + extendedGraphemeClusterLiteral: requiresProtocolConformance + ? "\(type.trimmed): \(Attributes.protocolName) " + : "\(type.trimmed) " + ) let extensionDecl = try ExtensionDeclSyntax( extendedType: type, memberBlockBuilder: { @@ -102,21 +117,28 @@ public enum PolymorphicOperationReturnTypeMacro: ExtensionMacro { let casesArray = ArrayExprSyntax( leftSquare: .leftSquareToken(), elements: handleCases, - rightSquare: .rightSquareToken()) + rightSquare: .rightSquareToken() + ) try VariableDeclSyntax( """ static let types: [(Codable.Type, PolymorphicOperationReturnOption)] = \(casesArray) - """) - }) + """ + ) + } + ) return [extensionDecl] } } extension PolymorphicOperationReturnTypeMacro { - private static func getCases(caseMembers: [EnumCaseDeclSyntax], context: some MacroExpansionContext, databaseItemType: String) + private static func getCases( + caseMembers: [EnumCaseDeclSyntax], + context: some MacroExpansionContext, + databaseItemType: String + ) -> (hasDiagnostics: Bool, handleCases: ArrayElementListSyntax) { var handleCases: ArrayElementListSyntax = [] @@ -124,32 +146,47 @@ extension PolymorphicOperationReturnTypeMacro { for caseMember in caseMembers { for element in caseMember.elements { // ensure that the enum case only has one parameter - guard let parameters = element.parameterClause?.parameters, let parameterType = parameters.first?.type.as(IdentifierTypeSyntax.self), - parameters.count == 1 + guard let parameters = element.parameterClause?.parameters, + let parameterType = parameters.first?.type.as(IdentifierTypeSyntax.self), + parameters.count == 1 else { - context.diagnose(.init(node: element, message: BaseEntryDiagnostic.enumCasesMustHaveASingleParameter)) + context.diagnose( + .init(node: element, message: BaseEntryDiagnostic.enumCasesMustHaveASingleParameter) + ) hasDiagnostics = true // do nothing for this case continue } guard parameterType.name.text == databaseItemType, - let firstArgumentType = parameterType.genericArgumentClause?.arguments.first?.argument + let firstArgumentType = parameterType.genericArgumentClause?.arguments.first?.argument else { - let message = "PolymorphicOperationReturnTypeMacro decorated enum cases parameter must be of \(databaseItemType) type." - context.diagnose(.init(node: element, message: BasicDiagnosticMessage(message: message, rawValue: "enumCasesMustBeOfTheExpectedType"))) + let message = + "PolymorphicOperationReturnTypeMacro decorated enum cases parameter must be of \(databaseItemType) type." + context.diagnose( + .init( + node: element, + message: BasicDiagnosticMessage( + message: message, + rawValue: "enumCasesMustBeOfTheExpectedType" + ) + ) + ) hasDiagnostics = true // do nothing for this case continue } let handleCaseSyntax = ArrayElementSyntax( - expression: ExprSyntax(""" - ( - \(firstArgumentType).self, .init { .\(element.name)($0) } - ) - """), - trailingComma: .commaToken()) + expression: ExprSyntax( + """ + ( + \(firstArgumentType).self, .init { .\(element.name)($0) } + ) + """ + ), + trailingComma: .commaToken() + ) handleCases.append(handleCaseSyntax) } diff --git a/Sources/DynamoDBTablesMacros/PolymorphicTransactionConstraintEntryMacro.swift b/Sources/DynamoDBTablesMacros/PolymorphicTransactionConstraintEntryMacro.swift index 898f3e8..48ee736 100644 --- a/Sources/DynamoDBTablesMacros/PolymorphicTransactionConstraintEntryMacro.swift +++ b/Sources/DynamoDBTablesMacros/PolymorphicTransactionConstraintEntryMacro.swift @@ -34,12 +34,14 @@ public enum PolymorphicTransactionConstraintEntryMacro: ExtensionMacro { attachedTo declaration: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], - in context: some MacroExpansionContext) throws -> [ExtensionDeclSyntax] - { - try BaseEntryMacro.expansion(of: node, - attachedTo: declaration, - providingExtensionsOf: type, - conformingTo: protocols, - in: context) + in context: some MacroExpansionContext + ) throws -> [ExtensionDeclSyntax] { + try BaseEntryMacro.expansion( + of: node, + attachedTo: declaration, + providingExtensionsOf: type, + conformingTo: protocols, + in: context + ) } } diff --git a/Sources/DynamoDBTablesMacros/PolymorphicWriteEntryMacro.swift b/Sources/DynamoDBTablesMacros/PolymorphicWriteEntryMacro.swift index f14fe13..285570e 100644 --- a/Sources/DynamoDBTablesMacros/PolymorphicWriteEntryMacro.swift +++ b/Sources/DynamoDBTablesMacros/PolymorphicWriteEntryMacro.swift @@ -34,12 +34,14 @@ public enum PolymorphicWriteEntryMacro: ExtensionMacro { attachedTo declaration: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], - in context: some MacroExpansionContext) throws -> [ExtensionDeclSyntax] - { - try BaseEntryMacro.expansion(of: node, - attachedTo: declaration, - providingExtensionsOf: type, - conformingTo: protocols, - in: context) + in context: some MacroExpansionContext + ) throws -> [ExtensionDeclSyntax] { + try BaseEntryMacro.expansion( + of: node, + attachedTo: declaration, + providingExtensionsOf: type, + conformingTo: protocols, + in: context + ) } } diff --git a/Tests/DynamoDBTablesTests/DynamoDBCompositePrimaryKeyTableClobberVersionedItemWithHistoricalRowTests.swift b/Tests/DynamoDBTablesTests/DynamoDBCompositePrimaryKeyTableClobberVersionedItemWithHistoricalRowTests.swift index 6bfe236..e8a1274 100644 --- a/Tests/DynamoDBTablesTests/DynamoDBCompositePrimaryKeyTableClobberVersionedItemWithHistoricalRowTests.swift +++ b/Tests/DynamoDBTablesTests/DynamoDBCompositePrimaryKeyTableClobberVersionedItemWithHistoricalRowTests.swift @@ -24,10 +24,11 @@ // DynamoDBTablesTests // -@testable import DynamoDBTables import Foundation import Testing +@testable import DynamoDBTables + struct DynamoDBCompositePrimaryKeyTableClobberVersionedItemWithHistoricalRowTests { @Test func clobberVersionedItemWithHistoricalRow() async throws { @@ -43,11 +44,13 @@ struct DynamoDBCompositePrimaryKeyTableClobberVersionedItemWithHistoricalRowTest let table = InMemoryDynamoDBCompositePrimaryKeyTable() - try await table.clobberVersionedItemWithHistoricalRow(forPrimaryKey: partitionKey, - andHistoricalKey: historicalPartitionKey, - item: payload1, - primaryKeyType: StandardPrimaryKeyAttributes.self, - generateSortKey: generateSortKey) + try await table.clobberVersionedItemWithHistoricalRow( + forPrimaryKey: partitionKey, + andHistoricalKey: historicalPartitionKey, + item: payload1, + primaryKeyType: StandardPrimaryKeyAttributes.self, + generateSortKey: generateSortKey + ) // the v0 row, copy of version 1 let key1 = StandardCompositePrimaryKey(partitionKey: partitionKey, sortKey: generateSortKey(withVersion: 0)) @@ -57,7 +60,10 @@ struct DynamoDBCompositePrimaryKeyTableClobberVersionedItemWithHistoricalRowTest #expect(payload1 == item1.rowValue.rowValue) // the v1 row, has version 1 - let key2 = StandardCompositePrimaryKey(partitionKey: historicalPartitionKey, sortKey: generateSortKey(withVersion: 1)) + let key2 = StandardCompositePrimaryKey( + partitionKey: historicalPartitionKey, + sortKey: generateSortKey(withVersion: 1) + ) let item2: StandardTypedDatabaseItem> = try await table.getItem(forKey: key2)! #expect(item2.rowValue.itemVersion == 1) #expect(item2.rowStatus.rowVersion == 1) @@ -65,11 +71,13 @@ struct DynamoDBCompositePrimaryKeyTableClobberVersionedItemWithHistoricalRowTest let payload2 = TestTypeA(firstly: "thirdly", secondly: "fourthly") - try await table.clobberVersionedItemWithHistoricalRow(forPrimaryKey: partitionKey, - andHistoricalKey: historicalPartitionKey, - item: payload2, - primaryKeyType: StandardPrimaryKeyAttributes.self, - generateSortKey: generateSortKey) + try await table.clobberVersionedItemWithHistoricalRow( + forPrimaryKey: partitionKey, + andHistoricalKey: historicalPartitionKey, + item: payload2, + primaryKeyType: StandardPrimaryKeyAttributes.self, + generateSortKey: generateSortKey + ) // the v0 row, copy of version 2 let key3 = StandardCompositePrimaryKey(partitionKey: partitionKey, sortKey: generateSortKey(withVersion: 0)) @@ -79,14 +87,20 @@ struct DynamoDBCompositePrimaryKeyTableClobberVersionedItemWithHistoricalRowTest #expect(payload2 == item3.rowValue.rowValue) // the v1 row, still has version 1 - let key4 = StandardCompositePrimaryKey(partitionKey: historicalPartitionKey, sortKey: generateSortKey(withVersion: 1)) + let key4 = StandardCompositePrimaryKey( + partitionKey: historicalPartitionKey, + sortKey: generateSortKey(withVersion: 1) + ) let item4: StandardTypedDatabaseItem> = try await table.getItem(forKey: key4)! #expect(item4.rowValue.itemVersion == 1) #expect(item4.rowStatus.rowVersion == 1) #expect(payload1 == item4.rowValue.rowValue) // the v2 row, has version 2 - let key5 = StandardCompositePrimaryKey(partitionKey: historicalPartitionKey, sortKey: generateSortKey(withVersion: 2)) + let key5 = StandardCompositePrimaryKey( + partitionKey: historicalPartitionKey, + sortKey: generateSortKey(withVersion: 2) + ) let item5: StandardTypedDatabaseItem> = try await table.getItem(forKey: key5)! #expect(item5.rowValue.itemVersion == 2) #expect(item5.rowStatus.rowVersion == 1) diff --git a/Tests/DynamoDBTablesTests/DynamoDBCompositePrimaryKeyTableHistoricalItemExtensionsTests.swift b/Tests/DynamoDBTablesTests/DynamoDBCompositePrimaryKeyTableHistoricalItemExtensionsTests.swift index b133b49..d4a9335 100644 --- a/Tests/DynamoDBTablesTests/DynamoDBCompositePrimaryKeyTableHistoricalItemExtensionsTests.swift +++ b/Tests/DynamoDBTablesTests/DynamoDBCompositePrimaryKeyTableHistoricalItemExtensionsTests.swift @@ -25,28 +25,25 @@ // DynamoDBTablesTests // -@testable import DynamoDBTables import Foundation import Testing +@testable import DynamoDBTables + private typealias DatabaseRowType = TypedTTLDatabaseItem, StandardTimeToLiveAttributes> -/** - * For these tests, a primary item Provider should always return a default value for nil arguments. The Provider Provider requires a non-nil default in order to initialize a Provider. - */ -private func primaryItemProviderProvider(_ defaultItem: DatabaseRowType) -> - (DatabaseRowType?) -> DatabaseRowType -{ - func primaryItemProvider(_ item: DatabaseRowType?) -> - DatabaseRowType - { +/// For these tests, a primary item Provider should always return a default value for nil arguments. The Provider Provider requires a non-nil default in order to initialize a Provider. +private func primaryItemProviderProvider(_ defaultItem: DatabaseRowType) -> (DatabaseRowType?) -> DatabaseRowType { + func primaryItemProvider(_ item: DatabaseRowType?) -> DatabaseRowType { guard let item else { return defaultItem } - let newItemRowValue = item.rowValue.createUpdatedItem(withVersion: item.rowValue.itemVersion + 1, - withValue: defaultItem.rowValue.rowValue) + let newItemRowValue = item.rowValue.createUpdatedItem( + withVersion: item.rowValue.itemVersion + 1, + withValue: defaultItem.rowValue.rowValue + ) return item.createUpdatedItem(withValue: newItemRowValue) } @@ -58,13 +55,20 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { static let dPayload = TestTypeA(firstly: "firstly", secondly: "secondly") private let testPrimaryItemProvider = primaryItemProviderProvider( - StandardTypedDatabaseItem.newItem(withKey: dKey, - andValue: RowWithItemVersion.newItem(withValue: dPayload))) + StandardTypedDatabaseItem.newItem( + withKey: dKey, + andValue: RowWithItemVersion.newItem(withValue: dPayload) + ) + ) private func testHistoricalItemProvider(_ item: DatabaseRowType) -> DatabaseRowType { - DatabaseRowType.newItem(withKey: StandardCompositePrimaryKey(partitionKey: "historical.\(item.compositePrimaryKey.partitionKey)", - sortKey: "v0000\(item.rowValue.itemVersion).\(item.compositePrimaryKey.sortKey)"), - andValue: item.rowValue) + DatabaseRowType.newItem( + withKey: StandardCompositePrimaryKey( + partitionKey: "historical.\(item.compositePrimaryKey.partitionKey)", + sortKey: "v0000\(item.rowValue.itemVersion).\(item.compositePrimaryKey.sortKey)" + ), + andValue: item.rowValue + ) } @Test @@ -122,7 +126,11 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { let updatedPayload = versionedPayload.createUpdatedItem(withValue: versionedPayload.rowValue) let updatedItem = databaseItem.createUpdatedItem(withValue: updatedPayload) - try await table.updateItemWithHistoricalRow(primaryItem: updatedItem, existingItem: databaseItem, historicalItem: self.testHistoricalItemProvider(updatedItem)) + try await table.updateItemWithHistoricalRow( + primaryItem: updatedItem, + existingItem: databaseItem, + historicalItem: self.testHistoricalItemProvider(updatedItem) + ) let inserted: DatabaseRowType = try await table.getItem(forKey: key)! #expect(inserted.compositePrimaryKey.partitionKey == databaseItem.compositePrimaryKey.partitionKey) @@ -144,13 +152,19 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { let updatedPayload = versionedPayload.createUpdatedItem(withValue: versionedPayload.rowValue) let updatedItem = databaseItem.createUpdatedItem(withValue: updatedPayload) - try await table.updateItemWithHistoricalRow(primaryItem: updatedItem, existingItem: databaseItem, - historicalItem: self.testHistoricalItemProvider(updatedItem)) + try await table.updateItemWithHistoricalRow( + primaryItem: updatedItem, + existingItem: databaseItem, + historicalItem: self.testHistoricalItemProvider(updatedItem) + ) do { // Second update will fail. - try await table.updateItemWithHistoricalRow(primaryItem: databaseItem.createUpdatedItem(withValue: versionedPayload), - existingItem: databaseItem, historicalItem: historicalItem) + try await table.updateItemWithHistoricalRow( + primaryItem: databaseItem.createUpdatedItem(withValue: versionedPayload), + existingItem: databaseItem, + historicalItem: historicalItem + ) } catch DynamoDBTableError.conditionalCheckFailed { // Success } catch { @@ -164,7 +178,10 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { let databaseItem = self.testPrimaryItemProvider(nil) - try await table.clobberItemWithHistoricalRow(primaryItemProvider: self.testPrimaryItemProvider, historicalItemProvider: self.testHistoricalItemProvider) + try await table.clobberItemWithHistoricalRow( + primaryItemProvider: self.testPrimaryItemProvider, + historicalItemProvider: self.testHistoricalItemProvider + ) let inserted: DatabaseRowType = try await (table.getItem(forKey: databaseItem.compositePrimaryKey))! #expect(inserted.compositePrimaryKey.partitionKey == databaseItem.compositePrimaryKey.partitionKey) #expect(inserted.compositePrimaryKey.sortKey == databaseItem.compositePrimaryKey.sortKey) @@ -175,11 +192,15 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { let databaseItem = self.testPrimaryItemProvider(nil) let wrappedTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: wrappedTable, - simulateConcurrencyModifications: 5) - - try await table.clobberItemWithHistoricalRow(primaryItemProvider: self.testPrimaryItemProvider, - historicalItemProvider: self.testHistoricalItemProvider) + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: wrappedTable, + simulateConcurrencyModifications: 5 + ) + + try await table.clobberItemWithHistoricalRow( + primaryItemProvider: self.testPrimaryItemProvider, + historicalItemProvider: self.testHistoricalItemProvider + ) let inserted: DatabaseRowType = try await table.getItem(forKey: databaseItem.compositePrimaryKey)! #expect(inserted.rowStatus.rowVersion > databaseItem.rowStatus.rowVersion) } @@ -187,12 +208,17 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { @Test func clobberItemFailure() async throws { let wrappedTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: wrappedTable, - simulateConcurrencyModifications: 12) + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: wrappedTable, + simulateConcurrencyModifications: 12 + ) do { - try await table.clobberItemWithHistoricalRow(primaryItemProvider: self.testPrimaryItemProvider, - historicalItemProvider: self.testHistoricalItemProvider, withRetries: 9) + try await table.clobberItemWithHistoricalRow( + primaryItemProvider: self.testPrimaryItemProvider, + historicalItemProvider: self.testHistoricalItemProvider, + withRetries: 9 + ) Issue.record("Expected error not thrown.") } catch DynamoDBTableError.concurrencyError { @@ -208,7 +234,8 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { return try existingItem.createUpdatedRowWithItemVersion( withValue: dPayload, - conditionalStatusVersion: nil) + conditionalStatusVersion: nil + ) } private func getConditionalUpdatePrimaryItemProviderAsync() -> ((DatabaseRowType) async throws -> DatabaseRowType) { @@ -218,18 +245,23 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { return try existingItem.createUpdatedRowWithItemVersion( withValue: dPayload, - conditionalStatusVersion: nil) + conditionalStatusVersion: nil + ) } return provider } - private let historicalCompositePrimaryKey = StandardCompositePrimaryKey(partitionKey: "historicalPartitionKey", - sortKey: "historicalSortKey") + private let historicalCompositePrimaryKey = StandardCompositePrimaryKey( + partitionKey: "historicalPartitionKey", + sortKey: "historicalSortKey" + ) private func conditionalUpdateHistoricalItemProvider(updatedItem: DatabaseRowType) -> DatabaseRowType { // create an item for the history partition - TypedTTLDatabaseItem.newItem(withKey: self.historicalCompositePrimaryKey, - andValue: updatedItem.rowValue) + TypedTTLDatabaseItem.newItem( + withKey: self.historicalCompositePrimaryKey, + andValue: updatedItem.rowValue + ) } @Test @@ -242,7 +274,8 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { let updated = try await table.conditionallyUpdateItemWithHistoricalRow( compositePrimaryKey: Self.dKey, primaryItemProvider: self.conditionalUpdatePrimaryItemProvider, - historicalItemProvider: self.conditionalUpdateHistoricalItemProvider) + historicalItemProvider: self.conditionalUpdateHistoricalItemProvider + ) let inserted: DatabaseRowType = try await (table.getItem(forKey: databaseItem.compositePrimaryKey))! #expect(inserted.rowValue.rowValue.firstly == "firstly_1") @@ -273,7 +306,8 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { let updated = try await table.conditionallyUpdateItemWithHistoricalRow( compositePrimaryKey: Self.dKey, primaryItemProvider: asyncConditionalUpdatePrimaryItemProvider, - historicalItemProvider: self.conditionalUpdateHistoricalItemProvider) + historicalItemProvider: self.conditionalUpdateHistoricalItemProvider + ) let inserted: DatabaseRowType = try await (table.getItem(forKey: databaseItem.compositePrimaryKey))! #expect(inserted.rowValue.rowValue.firstly == "firstly_1") @@ -296,9 +330,11 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { @Test func conditionallyUpdateItemWithHistoricalRowAcceptableConcurrency() async throws { let wrappedTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: wrappedTable, - simulateConcurrencyModifications: 5, - simulateOnInsertItem: false) + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: wrappedTable, + simulateConcurrencyModifications: 5, + simulateOnInsertItem: false + ) let databaseItem = self.testPrimaryItemProvider(nil) try await table.insertItem(databaseItem) @@ -306,7 +342,8 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { let updated = try await table.conditionallyUpdateItemWithHistoricalRow( compositePrimaryKey: Self.dKey, primaryItemProvider: self.conditionalUpdatePrimaryItemProvider, - historicalItemProvider: self.conditionalUpdateHistoricalItemProvider) + historicalItemProvider: self.conditionalUpdateHistoricalItemProvider + ) let inserted: DatabaseRowType = try await (table.getItem(forKey: databaseItem.compositePrimaryKey))! #expect(inserted.rowValue.rowValue.firstly == "firstly_6") @@ -326,9 +363,11 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { @Test func conditionallyUpdateItemWithHistoricalRowAcceptableConcurrencyWithAsyncProvider() async throws { let wrappedTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: wrappedTable, - simulateConcurrencyModifications: 5, - simulateOnInsertItem: false) + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: wrappedTable, + simulateConcurrencyModifications: 5, + simulateOnInsertItem: false + ) let databaseItem = self.testPrimaryItemProvider(nil) try await table.insertItem(databaseItem) @@ -337,7 +376,8 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { let updated = try await table.conditionallyUpdateItemWithHistoricalRow( compositePrimaryKey: Self.dKey, primaryItemProvider: asyncConditionalUpdatePrimaryItemProvider, - historicalItemProvider: self.conditionalUpdateHistoricalItemProvider) + historicalItemProvider: self.conditionalUpdateHistoricalItemProvider + ) let inserted: DatabaseRowType = try await (table.getItem(forKey: databaseItem.compositePrimaryKey))! #expect(inserted.rowValue.rowValue.firstly == "firstly_6") @@ -357,9 +397,11 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { @Test func conditionallyUpdateItemWithHistoricalRowUnacceptableConcurrency() async throws { let wrappedTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: wrappedTable, - simulateConcurrencyModifications: 50, - simulateOnInsertItem: false) + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: wrappedTable, + simulateConcurrencyModifications: 50, + simulateOnInsertItem: false + ) let databaseItem = self.testPrimaryItemProvider(nil) try await table.insertItem(databaseItem) @@ -368,7 +410,8 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { _ = try await table.conditionallyUpdateItemWithHistoricalRow( compositePrimaryKey: Self.dKey, primaryItemProvider: self.conditionalUpdatePrimaryItemProvider, - historicalItemProvider: self.conditionalUpdateHistoricalItemProvider) + historicalItemProvider: self.conditionalUpdateHistoricalItemProvider + ) Issue.record("Expected error not thrown.") } catch DynamoDBTableError.concurrencyError { @@ -388,9 +431,11 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { @Test func conditionallyUpdateItemWithHistoricalRowUnacceptableConcurrencyWithAsyncProvider() async throws { let wrappedTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: wrappedTable, - simulateConcurrencyModifications: 50, - simulateOnInsertItem: false) + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: wrappedTable, + simulateConcurrencyModifications: 50, + simulateOnInsertItem: false + ) let databaseItem = self.testPrimaryItemProvider(nil) try await table.insertItem(databaseItem) @@ -400,7 +445,8 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { _ = try await table.conditionallyUpdateItemWithHistoricalRow( compositePrimaryKey: Self.dKey, primaryItemProvider: asyncConditionalUpdatePrimaryItemProvider, - historicalItemProvider: self.conditionalUpdateHistoricalItemProvider) + historicalItemProvider: self.conditionalUpdateHistoricalItemProvider + ) Issue.record("Expected error not thrown.") } catch DynamoDBTableError.concurrencyError { @@ -424,9 +470,11 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { @Test func conditionallyUpdateItemWithHistoricalRowPrimaryItemProviderError() async throws { let wrappedTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: wrappedTable, - simulateConcurrencyModifications: 5, - simulateOnInsertItem: false) + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: wrappedTable, + simulateConcurrencyModifications: 5, + simulateOnInsertItem: false + ) let databaseItem = self.testPrimaryItemProvider(nil) try await table.insertItem(databaseItem) @@ -443,14 +491,16 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { return try existingItem.createUpdatedRowWithItemVersion( withValue: dPayload, - conditionalStatusVersion: nil) + conditionalStatusVersion: nil + ) } do { _ = try await table.conditionallyUpdateItemWithHistoricalRow( compositePrimaryKey: Self.dKey, primaryItemProvider: primaryItemProvider, - historicalItemProvider: self.conditionalUpdateHistoricalItemProvider) + historicalItemProvider: self.conditionalUpdateHistoricalItemProvider + ) Issue.record("Expected error not thrown.") } catch TestError.everythingIsWrong { @@ -470,9 +520,11 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { @Test func conditionallyUpdateItemWithHistoricalRowPrimaryItemProviderErrorWithAsyncProvider() async throws { let wrappedTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: wrappedTable, - simulateConcurrencyModifications: 5, - simulateOnInsertItem: false) + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: wrappedTable, + simulateConcurrencyModifications: 5, + simulateOnInsertItem: false + ) let databaseItem = self.testPrimaryItemProvider(nil) try await table.insertItem(databaseItem) @@ -489,14 +541,16 @@ struct CompositePrimaryKeyDynamoDBHistoricalClientTests { return try existingItem.createUpdatedRowWithItemVersion( withValue: dPayload, - conditionalStatusVersion: nil) + conditionalStatusVersion: nil + ) } do { _ = try await table.conditionallyUpdateItemWithHistoricalRow( compositePrimaryKey: Self.dKey, primaryItemProvider: primaryItemProvider, - historicalItemProvider: self.conditionalUpdateHistoricalItemProvider) + historicalItemProvider: self.conditionalUpdateHistoricalItemProvider + ) Issue.record("Expected error not thrown.") } catch TestError.everythingIsWrong { diff --git a/Tests/DynamoDBTablesTests/DynamoDBCompositePrimaryKeyTableTests.swift b/Tests/DynamoDBTablesTests/DynamoDBCompositePrimaryKeyTableTests.swift index 829d977..d3df668 100644 --- a/Tests/DynamoDBTablesTests/DynamoDBCompositePrimaryKeyTableTests.swift +++ b/Tests/DynamoDBTablesTests/DynamoDBCompositePrimaryKeyTableTests.swift @@ -24,27 +24,44 @@ // DynamoDBTablesTests // -@testable import DynamoDBTables import Testing +@testable import DynamoDBTables + struct DynamoDBCompositePrimaryKeyTableTests { @Test func bulkWriteWithFallback() async throws { // Length of insert statements of payload1 is larger than the limitation let payload1 = TestTypeA( - firstly: "firstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstly", - secondly: "secondly") + firstly: + "firstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstlyfirstly", + secondly: "secondly" + ) let partitionKey1 = "partitionId1" - let key1 = StandardCompositePrimaryKey(partitionKey: partitionKey1, - sortKey: "sortId") + let key1 = StandardCompositePrimaryKey( + partitionKey: partitionKey1, + sortKey: "sortId" + ) let payload2 = TestTypeA(firstly: "firstly", secondly: "secondly") let partitionKey2 = "partitionId2" - let key2 = StandardCompositePrimaryKey(partitionKey: partitionKey2, - sortKey: "sortId") + let key2 = StandardCompositePrimaryKey( + partitionKey: partitionKey2, + sortKey: "sortId" + ) var nodeEntries: [(key: String, entry: TestTypeAWriteEntry)] = [] - nodeEntries.append((partitionKey1, TestTypeAWriteEntry.insert(new: StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1)))) - nodeEntries.append((partitionKey2, TestTypeAWriteEntry.insert(new: StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2)))) + nodeEntries.append( + ( + partitionKey1, + TestTypeAWriteEntry.insert(new: StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1)) + ) + ) + nodeEntries.append( + ( + partitionKey2, + TestTypeAWriteEntry.insert(new: StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2)) + ) + ) let table = InMemoryDynamoDBCompositePrimaryKeyTable() do { diff --git a/Tests/DynamoDBTablesTests/DynamoDBCompositePrimaryKeyTableUpdateItemConditionallyAtKeyTests.swift b/Tests/DynamoDBTablesTests/DynamoDBCompositePrimaryKeyTableUpdateItemConditionallyAtKeyTests.swift index e8a77ce..bdde1f2 100644 --- a/Tests/DynamoDBTablesTests/DynamoDBCompositePrimaryKeyTableUpdateItemConditionallyAtKeyTests.swift +++ b/Tests/DynamoDBTablesTests/DynamoDBCompositePrimaryKeyTableUpdateItemConditionallyAtKeyTests.swift @@ -24,9 +24,10 @@ // DynamoDBTablesTests // -@testable import DynamoDBTables import Testing +@testable import DynamoDBTables + struct DynamoDBCompositePrimaryKeyTableUpdateItemConditionallyAtKeyTests { func updatedPayloadProvider(item _: TestTypeA) -> TestTypeA { TestTypeA(firstly: "firstlyX2", secondly: "secondlyX2") @@ -34,19 +35,24 @@ struct DynamoDBCompositePrimaryKeyTableUpdateItemConditionallyAtKeyTests { typealias TestTypeADatabaseItem = StandardTypedDatabaseItem func updatedItemProvider(item _: TestTypeADatabaseItem) -> TestTypeADatabaseItem { - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId") + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId" + ) return TestTypeADatabaseItem.newItem( withKey: key, - andValue: TestTypeA(firstly: "firstlyX2", secondly: "secondlyX2")) + andValue: TestTypeA(firstly: "firstlyX2", secondly: "secondlyX2") + ) } @Test func updateItemConditionallyAtKey() async throws { let table = InMemoryDynamoDBCompositePrimaryKeyTable() - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId") + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId" + ) let payload = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) @@ -71,8 +77,10 @@ struct DynamoDBCompositePrimaryKeyTableUpdateItemConditionallyAtKeyTests { func updateItemConditionallyAtKeyWithItemProvider() async throws { let table = InMemoryDynamoDBCompositePrimaryKeyTable() - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId") + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId" + ) let payload = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) @@ -96,12 +104,16 @@ struct DynamoDBCompositePrimaryKeyTableUpdateItemConditionallyAtKeyTests { @Test func updateItemConditionallyAtKeyWithAcceptableConcurrency() async throws { let wrappedTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: wrappedTable, - simulateConcurrencyModifications: 5, - simulateOnInsertItem: false) - - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId") + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: wrappedTable, + simulateConcurrencyModifications: 5, + simulateOnInsertItem: false + ) + + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId" + ) let payload = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) @@ -125,12 +137,16 @@ struct DynamoDBCompositePrimaryKeyTableUpdateItemConditionallyAtKeyTests { @Test func updateItemConditionallyAtKeyWithAcceptableConcurrencyWithItemProvider() async throws { let wrappedTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: wrappedTable, - simulateConcurrencyModifications: 5, - simulateOnInsertItem: false) - - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId") + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: wrappedTable, + simulateConcurrencyModifications: 5, + simulateOnInsertItem: false + ) + + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId" + ) let payload = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) @@ -154,12 +170,16 @@ struct DynamoDBCompositePrimaryKeyTableUpdateItemConditionallyAtKeyTests { @Test func updateItemConditionallyAtKeyWithUnacceptableConcurrency() async throws { let wrappedTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: wrappedTable, - simulateConcurrencyModifications: 100, - simulateOnInsertItem: false) - - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId") + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: wrappedTable, + simulateConcurrencyModifications: 100, + simulateOnInsertItem: false + ) + + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId" + ) let payload = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) @@ -192,12 +212,16 @@ struct DynamoDBCompositePrimaryKeyTableUpdateItemConditionallyAtKeyTests { @Test func updateItemConditionallyAtKeyWithUnacceptableConcurrencyWithItemProvider() async throws { let wrappedTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: wrappedTable, - simulateConcurrencyModifications: 100, - simulateOnInsertItem: false) - - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId") + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: wrappedTable, + simulateConcurrencyModifications: 100, + simulateOnInsertItem: false + ) + + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId" + ) let payload = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) @@ -230,12 +254,16 @@ struct DynamoDBCompositePrimaryKeyTableUpdateItemConditionallyAtKeyTests { @Test func updateItemConditionallyAtKeyWithUnacceptableConcurrencyWithPayloadProvider() async throws { let wrappedTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: wrappedTable, - simulateConcurrencyModifications: 100, - simulateOnInsertItem: false) - - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId") + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: wrappedTable, + simulateConcurrencyModifications: 100, + simulateOnInsertItem: false + ) + + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId" + ) let payload = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) @@ -272,12 +300,16 @@ struct DynamoDBCompositePrimaryKeyTableUpdateItemConditionallyAtKeyTests { @Test func updateItemConditionallyAtKeyWithFailingUpdate() async throws { let wrappedTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: wrappedTable, - simulateConcurrencyModifications: 100, - simulateOnInsertItem: false) - - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId") + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: wrappedTable, + simulateConcurrencyModifications: 100, + simulateOnInsertItem: false + ) + + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId" + ) let payload = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) @@ -322,12 +354,16 @@ struct DynamoDBCompositePrimaryKeyTableUpdateItemConditionallyAtKeyTests { @Test func updateItemConditionallyAtKeyWithFailingUpdateWithItemProvider() async throws { let wrappedTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: wrappedTable, - simulateConcurrencyModifications: 100, - simulateOnInsertItem: false) - - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId") + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: wrappedTable, + simulateConcurrencyModifications: 100, + simulateOnInsertItem: false + ) + + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId" + ) let payload = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) @@ -344,11 +380,14 @@ struct DynamoDBCompositePrimaryKeyTableUpdateItemConditionallyAtKeyTests { func failingUpdatedItemProvider(item _: TestTypeADatabaseItem) throws -> TestTypeADatabaseItem { if passCount < 5 { passCount += 1 - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId") + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId" + ) return TestTypeADatabaseItem.newItem( withKey: key, - andValue: TestTypeA(firstly: "firstlyX2", secondly: "secondlyX2")) + andValue: TestTypeA(firstly: "firstlyX2", secondly: "secondlyX2") + ) } else { // fail before the retry limit with a custom error throw TestError.everythingIsWrong @@ -377,8 +416,10 @@ struct DynamoDBCompositePrimaryKeyTableUpdateItemConditionallyAtKeyTests { func updateItemConditionallyAtKeyWithUnknownItem() async throws { let table = InMemoryDynamoDBCompositePrimaryKeyTable() - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId") + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId" + ) do { try await table.conditionallyUpdateItem(forKey: key, updatedPayloadProvider: self.updatedPayloadProvider) @@ -399,8 +440,10 @@ struct DynamoDBCompositePrimaryKeyTableUpdateItemConditionallyAtKeyTests { func updateItemConditionallyAtKeyWithUnknownItemWithItemProvider() async throws { let table = InMemoryDynamoDBCompositePrimaryKeyTable() - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId") + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId" + ) do { try await table.conditionallyUpdateItem(forKey: key, updatedItemProvider: self.updatedItemProvider) diff --git a/Tests/DynamoDBTablesTests/DynamoDBEncoderDecoderTests.swift b/Tests/DynamoDBTablesTests/DynamoDBEncoderDecoderTests.swift index 5639b89..bf483be 100644 --- a/Tests/DynamoDBTablesTests/DynamoDBEncoderDecoderTests.swift +++ b/Tests/DynamoDBTablesTests/DynamoDBEncoderDecoderTests.swift @@ -24,9 +24,10 @@ // DynamoDBTablesTests // -@testable import DynamoDBTables import Testing +@testable import DynamoDBTables + struct CoreAccountAttributes: Codable { var description: String var mappedValues: [String: String] @@ -59,13 +60,17 @@ struct DynamoDBEncoderDecoderTests { let attributes = CoreAccountAttributes( description: "Description", mappedValues: ["A": "one", "B": "two"], - notificationTargets: NotificationTargets(currentIDs: [], maximum: 20)) + notificationTargets: NotificationTargets(currentIDs: [], maximum: 20) + ) @Test func encoderDecoder() throws { // create key and database item to create let key = StandardCompositePrimaryKey(partitionKey: partitionKey, sortKey: sortKey) - let newDatabaseItem: DatabaseItemType = StandardTypedDatabaseItem.newItem(withKey: key, andValue: self.attributes) + let newDatabaseItem: DatabaseItemType = StandardTypedDatabaseItem.newItem( + withKey: key, + andValue: self.attributes + ) let encodedAttributeValue = try DynamoDBEncoder().encode(newDatabaseItem) @@ -88,7 +93,8 @@ struct DynamoDBEncoderDecoderTests { let newDatabaseItem: DatabaseItemType = StandardTypedDatabaseItem.newItem( withKey: key, andValue: self.attributes, - andTimeToLive: timeToLive) + andTimeToLive: timeToLive + ) let encodedAttributeValue = try DynamoDBEncoder().encode(newDatabaseItem) diff --git a/Tests/DynamoDBTablesTests/InMemoryDynamoDBCompositePrimaryKeyTableTests.swift b/Tests/DynamoDBTablesTests/InMemoryDynamoDBCompositePrimaryKeyTableTests.swift index ca1e10b..16d49b2 100644 --- a/Tests/DynamoDBTablesTests/InMemoryDynamoDBCompositePrimaryKeyTableTests.swift +++ b/Tests/DynamoDBTablesTests/InMemoryDynamoDBCompositePrimaryKeyTableTests.swift @@ -25,9 +25,10 @@ // import AWSDynamoDB -@testable import DynamoDBTables import Testing +@testable import DynamoDBTables + @PolymorphicOperationReturnType enum TestPolymorphicOperationReturnType { case testTypeA(StandardTypedDatabaseItem) @@ -38,8 +39,10 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func insertAndUpdate() async throws { let table = InMemoryDynamoDBCompositePrimaryKeyTable() - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId") + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId" + ) let payload = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) @@ -66,8 +69,10 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func doubleInsert() async throws { let table = InMemoryDynamoDBCompositePrimaryKeyTable() - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId") + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId" + ) let payload = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) @@ -87,15 +92,19 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func updateWithoutInsert() async throws { let table = InMemoryDynamoDBCompositePrimaryKeyTable() - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId") + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId" + ) let payload = TestTypeA(firstly: "firstly", secondly: "secondly") let updatedPayload = TestTypeA(firstly: "firstlyX2", secondly: "secondlyX2") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) do { - try await table.updateItem(newItem: databaseItem.createUpdatedItem(withValue: updatedPayload), - existingItem: databaseItem) + try await table.updateItem( + newItem: databaseItem.createUpdatedItem(withValue: updatedPayload), + existingItem: databaseItem + ) Issue.record() } catch DynamoDBTableError.conditionalCheckFailed { // expected error @@ -111,9 +120,11 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { var items: [StandardTypedDatabaseItem] = [] // add to the database a lot of items - a number that isn't a multiple of the pagination page size - for index in 0 ..< 1376 { - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId_\(index)") + for index in 0..<1376 { + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId_\(index)" + ) let payload = TestTypeA(firstly: "firstly_\(index)", secondly: "secondly_\(index)") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) @@ -128,10 +139,12 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { // get everything back from the database while true { let paginatedItems: ([TestPolymorphicOperationReturnType], String?) = - try await table.polymorphicQuery(forPartitionKey: "partitionId", - sortKeyCondition: nil, - limit: 100, - exclusiveStartKey: exclusiveStartKey) + try await table.polymorphicQuery( + forPartitionKey: "partitionId", + sortKeyCondition: nil, + limit: 100, + exclusiveStartKey: exclusiveStartKey + ) retrievedItems += paginatedItems.0 @@ -146,9 +159,11 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { #expect(items.count == retrievedItems.count) // items are returned in sorted order - let sortedItems = items.sorted { left, right in left.compositePrimaryKey.sortKey < right.compositePrimaryKey.sortKey } + let sortedItems = items.sorted { left, right in + left.compositePrimaryKey.sortKey < right.compositePrimaryKey.sortKey + } - for index in 0 ..< sortedItems.count { + for index in 0..] = [] // add to the database a lot of items - a number that isn't a multiple of the pagination page size - for index in 0 ..< 1376 { - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId_\(index)") + for index in 0..<1376 { + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId_\(index)" + ) let payload = TestTypeA(firstly: "firstly_\(index)", secondly: "secondly_\(index)") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) @@ -188,11 +205,13 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { // get everything back from the database while true { let paginatedItems: ([TestPolymorphicOperationReturnType], String?) = - try await table.polymorphicQuery(forPartitionKey: "partitionId", - sortKeyCondition: nil, - limit: 100, - scanIndexForward: false, - exclusiveStartKey: exclusiveStartKey) + try await table.polymorphicQuery( + forPartitionKey: "partitionId", + sortKeyCondition: nil, + limit: 100, + scanIndexForward: false, + exclusiveStartKey: exclusiveStartKey + ) retrievedItems += paginatedItems.0 @@ -207,9 +226,11 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { #expect(items.count == retrievedItems.count) // items are returned in reversed sorted order - let sortedItems = items.sorted { left, right in left.compositePrimaryKey.sortKey > right.compositePrimaryKey.sortKey } + let sortedItems = items.sorted { left, right in + left.compositePrimaryKey.sortKey > right.compositePrimaryKey.sortKey + } - for index in 0 ..< sortedItems.count { + for index in 0..] = [] // add to the database a lot of items - a number that isn't a multiple of the pagination page size - for index in 0 ..< 1376 { - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId_\(index)") + for index in 0..<1376 { + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId_\(index)" + ) let payload = TestTypeA(firstly: "firstly_\(index)", secondly: "secondly_\(index)") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) @@ -243,12 +266,14 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { } let retrievedItems: [TestPolymorphicOperationReturnType] = - try await table.polymorphicQuery(forPartitionKey: "partitionId", - sortKeyCondition: nil) + try await table.polymorphicQuery( + forPartitionKey: "partitionId", + sortKeyCondition: nil + ) #expect(items.count == retrievedItems.count) - for index in 0 ..< items.count { + for index in 0..] = [] // add to the database a lot of items - a number that isn't a multiple of the pagination page size - for index in 0 ..< 1376 { - let key = StandardCompositePrimaryKey(partitionKey: "partitionId", - sortKey: "sortId_\(index)") + for index in 0..<1376 { + let key = StandardCompositePrimaryKey( + partitionKey: "partitionId", + sortKey: "sortId_\(index)" + ) let payload = TestTypeA(firstly: "firstly_\(index)", secondly: "secondly_\(index)") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) @@ -276,12 +303,14 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { } let retrievedItems: [StandardTypedDatabaseItem] = - try await table.query(forPartitionKey: "partitionId", - sortKeyCondition: nil) + try await table.query( + forPartitionKey: "partitionId", + sortKeyCondition: nil + ) #expect(items.count == retrievedItems.count) - for index in 0 ..< items.count { + for index in 0..] - = try await table.getItems(forKeys: [key1, key2]) + let batch: [StandardCompositePrimaryKey: StandardTypedDatabaseItem] = try await table.getItems( + forKeys: [key1, key2]) guard let retrievedDatabaseItem1 = batch[key1] else { Issue.record() @@ -468,13 +515,17 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func transactWrite() async throws { let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly1", secondly: "secondly1") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeA(firstly: "firstly2", secondly: "secondly2") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) @@ -502,23 +553,31 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func transactWriteWithMissingRequired() async throws { let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly1", secondly: "secondly1") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeA(firstly: "firstly2", secondly: "secondly2") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) - let key3 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId3") + let key3 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId3" + ) let payload3 = TestTypeA(firstly: "firstly1A", secondly: "secondly1A") let databaseItem3 = StandardTypedDatabaseItem.newItem(withKey: key3, andValue: payload3) - let key4 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId4") + let key4 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId4" + ) let payload4 = TestTypeA(firstly: "firstly2A", secondly: "secondly2A") let databaseItem4 = StandardTypedDatabaseItem.newItem(withKey: key4, andValue: payload4) @@ -548,23 +607,31 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func transactWriteTransactWriteWithExistingRequired() async throws { let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly1", secondly: "secondly1") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeA(firstly: "firstly2", secondly: "secondly2") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) - let key3 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId3") + let key3 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId3" + ) let payload3 = TestTypeA(firstly: "firstly1A", secondly: "secondly1A") let databaseItem3 = StandardTypedDatabaseItem.newItem(withKey: key3, andValue: payload3) - let key4 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId4") + let key4 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId4" + ) let payload4 = TestTypeA(firstly: "firstly2A", secondly: "secondly2A") let databaseItem4 = StandardTypedDatabaseItem.newItem(withKey: key4, andValue: payload4) @@ -600,23 +667,31 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func transactWriteWithIncorrectVersionForRequired() async throws { let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly1", secondly: "secondly1") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeA(firstly: "firstly2", secondly: "secondly2") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) - let key3 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId3") + let key3 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId3" + ) let payload3 = TestTypeA(firstly: "firstly1A", secondly: "secondly1A") let databaseItem3 = StandardTypedDatabaseItem.newItem(withKey: key3, andValue: payload3) - let key4 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId4") + let key4 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId4" + ) let payload4 = TestTypeA(firstly: "firstly2A", secondly: "secondly2A") let databaseItem4 = StandardTypedDatabaseItem.newItem(withKey: key4, andValue: payload4) @@ -660,13 +735,17 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func transactWriteWithIncorrectVersionForUpdate() async throws { let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly1", secondly: "secondly1") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeA(firstly: "firstly2", secondly: "secondly2") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) try await table.insertItem(databaseItem2) @@ -675,8 +754,10 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { let databaseItem3 = databaseItem2.createUpdatedItem(withValue: payload3) try await table.updateItem(newItem: databaseItem3, existingItem: databaseItem2) - let key4 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId4") + let key4 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId4" + ) let payload4 = TestTypeA(firstly: "firstly2A", secondly: "secondly2A") let databaseItem4 = StandardTypedDatabaseItem.newItem(withKey: key4, andValue: payload4) @@ -708,13 +789,17 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func polymorphicTransactWrite() async throws { let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeB(thirdly: "thirdly", fourthly: "fourthly") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) @@ -742,23 +827,31 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func polymorphicTransactWriteWithMissingRequired() async throws { let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeB(thirdly: "thirdly", fourthly: "fourthly") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) - let key3 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId3") + let key3 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId3" + ) let payload3 = TestTypeA(firstly: "firstlyB", secondly: "secondlyB") let databaseItem3 = StandardTypedDatabaseItem.newItem(withKey: key3, andValue: payload3) - let key4 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId4") + let key4 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId4" + ) let payload4 = TestTypeB(thirdly: "thirdlyB", fourthly: "fourthlyB") let databaseItem4 = StandardTypedDatabaseItem.newItem(withKey: key4, andValue: payload4) @@ -788,23 +881,31 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func polymorphicTransactWriteTransactWriteWithExistingRequired() async throws { let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeB(thirdly: "thirdly", fourthly: "fourthly") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) - let key3 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId3") + let key3 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId3" + ) let payload3 = TestTypeA(firstly: "firstlyB", secondly: "secondlyB") let databaseItem3 = StandardTypedDatabaseItem.newItem(withKey: key3, andValue: payload3) - let key4 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId4") + let key4 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId4" + ) let payload4 = TestTypeB(thirdly: "thirdlyB", fourthly: "fourthlyB") let databaseItem4 = StandardTypedDatabaseItem.newItem(withKey: key4, andValue: payload4) @@ -840,23 +941,31 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func polymorphicTransactWriteWithIncorrectVersionForRequired() async throws { let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeB(thirdly: "thirdly", fourthly: "fourthly") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) - let key3 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId3") + let key3 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId3" + ) let payload3 = TestTypeA(firstly: "firstlyB", secondly: "secondlyB") let databaseItem3 = StandardTypedDatabaseItem.newItem(withKey: key3, andValue: payload3) - let key4 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId4") + let key4 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId4" + ) let payload4 = TestTypeB(thirdly: "thirdlyB", fourthly: "fourthlyB") let databaseItem4 = StandardTypedDatabaseItem.newItem(withKey: key4, andValue: payload4) @@ -900,13 +1009,17 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func polymorphicTransactWriteWithIncorrectVersionForUpdate() async throws { let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeB(thirdly: "thirdly", fourthly: "fourthly") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) try await table.insertItem(databaseItem2) @@ -950,8 +1063,9 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { } func injectErrors( - inputKeys _: [CompositePrimaryKey?], table _: InMemoryDynamoDBCompositePrimaryKeyTable) async throws -> [DynamoDBTableError] - { + inputKeys _: [CompositePrimaryKey?], + table _: InMemoryDynamoDBCompositePrimaryKeyTable + ) async throws -> [DynamoDBTableError] { self.errors } } @@ -960,25 +1074,35 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func transactWriteWithInjectedErrors() async throws { let errors = [DynamoDBTableError.transactionConflict(message: "There is a Conflict!!")] let transactionDelegate = TestInMemoryTransactionDelegate(errors: errors) - let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable(transactionDelegate: transactionDelegate) - - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable( + transactionDelegate: transactionDelegate + ) + + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly1", secondly: "secondly1") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeA(firstly: "firstly2", secondly: "secondly2") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) - let key3 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId3") + let key3 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId3" + ) let payload3 = TestTypeA(firstly: "firstly1A", secondly: "secondly1A") let databaseItem3 = StandardTypedDatabaseItem.newItem(withKey: key3, andValue: payload3) - let key4 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId4") + let key4 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId4" + ) let payload4 = TestTypeA(firstly: "firstly2A", secondly: "secondly2A") let databaseItem4 = StandardTypedDatabaseItem.newItem(withKey: key4, andValue: payload4) @@ -1017,13 +1141,17 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func transactWriteWithExistingItem() async throws { let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeA(firstly: "firstly2", secondly: "secondly2") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) @@ -1057,25 +1185,35 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func polymorphicTransactWriteWithInjectedErrors() async throws { let errors = [DynamoDBTableError.transactionConflict(message: "There is a Conflict!!")] let transactionDelegate = TestInMemoryTransactionDelegate(errors: errors) - let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable(transactionDelegate: transactionDelegate) - - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable( + transactionDelegate: transactionDelegate + ) + + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeB(thirdly: "thirdly", fourthly: "fourthly") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) - let key3 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId3") + let key3 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId3" + ) let payload3 = TestTypeA(firstly: "firstlyB", secondly: "secondlyB") let databaseItem3 = StandardTypedDatabaseItem.newItem(withKey: key3, andValue: payload3) - let key4 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId4") + let key4 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId4" + ) let payload4 = TestTypeB(thirdly: "thirdlyB", fourthly: "fourthlyB") let databaseItem4 = StandardTypedDatabaseItem.newItem(withKey: key4, andValue: payload4) @@ -1114,13 +1252,17 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func polymorphicTransactWriteWithExistingItem() async throws { let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeB(thirdly: "thirdly", fourthly: "fourthly") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) @@ -1154,13 +1296,17 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func bulkWrite() async throws { let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeA(firstly: "firstly2", secondly: "secondly2") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) @@ -1188,13 +1334,17 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func bulkWriteWithExistingItem() async throws { let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeA(firstly: "firstly2", secondly: "secondly2") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) @@ -1226,13 +1376,17 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func polymorphicBulkWrite() async throws { let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeB(thirdly: "thirdly", fourthly: "fourthly") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) @@ -1260,13 +1414,17 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func polymorphicBulkWriteWithExistingItem() async throws { let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly", secondly: "secondly") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeB(thirdly: "thirdly", fourthly: "fourthly") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) @@ -1298,21 +1456,28 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func transactWriteForKeys() async throws { let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly1", secondly: "secondly1") let payload3 = TestTypeA(firstly: "firstly3", secondly: "secondly3") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeA(firstly: "firstly2", secondly: "secondly2") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) try await table.insertItem(databaseItem1) @Sendable - func writeEntryProvider(key: StandardCompositePrimaryKey, existingItem: StandardTypedDatabaseItem?) throws + func writeEntryProvider( + key: StandardCompositePrimaryKey, + existingItem: StandardTypedDatabaseItem? + ) throws -> StandardWriteEntry? { if key == key1 { @@ -1348,21 +1513,28 @@ struct InMemoryDynamoDBCompositePrimaryKeyTableTests { func polymorphicTransactWriteForKeys() async throws { let table: DynamoDBCompositePrimaryKeyTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let key1 = StandardCompositePrimaryKey(partitionKey: "partitionId1", - sortKey: "sortId1") + let key1 = StandardCompositePrimaryKey( + partitionKey: "partitionId1", + sortKey: "sortId1" + ) let payload1 = TestTypeA(firstly: "firstly", secondly: "secondly") let payload3 = TestTypeA(firstly: "firstly3", secondly: "secondly3") let databaseItem1 = StandardTypedDatabaseItem.newItem(withKey: key1, andValue: payload1) - let key2 = StandardCompositePrimaryKey(partitionKey: "partitionId2", - sortKey: "sortId2") + let key2 = StandardCompositePrimaryKey( + partitionKey: "partitionId2", + sortKey: "sortId2" + ) let payload2 = TestTypeB(thirdly: "thirdly", fourthly: "fourthly") let databaseItem2 = StandardTypedDatabaseItem.newItem(withKey: key2, andValue: payload2) try await table.insertItem(databaseItem1) @Sendable - func writeEntryProvider(key: StandardCompositePrimaryKey, existingItem: TestQueryableTypes?) throws + func writeEntryProvider( + key: StandardCompositePrimaryKey, + existingItem: TestQueryableTypes? + ) throws -> TestPolymorphicWriteEntry? { if key == key1 { diff --git a/Tests/DynamoDBTablesTests/SimulateConcurrencyDynamoDBCompositePrimaryKeyTableTests.swift b/Tests/DynamoDBTablesTests/SimulateConcurrencyDynamoDBCompositePrimaryKeyTableTests.swift index de06d52..0a537c1 100644 --- a/Tests/DynamoDBTablesTests/SimulateConcurrencyDynamoDBCompositePrimaryKeyTableTests.swift +++ b/Tests/DynamoDBTablesTests/SimulateConcurrencyDynamoDBCompositePrimaryKeyTableTests.swift @@ -25,9 +25,10 @@ // import AWSDynamoDB -@testable import DynamoDBTables import Testing +@testable import DynamoDBTables + private typealias DatabaseRowType = StandardTypedDatabaseItem typealias CustomTypedDatabaseItem = StandardTypedDatabaseItem @@ -45,8 +46,10 @@ struct SimulateConcurrencyDynamoDBCompositePrimaryKeyTableTests { let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: InMemoryDynamoDBCompositePrimaryKeyTable(), - simulateConcurrencyModifications: 5) + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: InMemoryDynamoDBCompositePrimaryKeyTable(), + simulateConcurrencyModifications: 5 + ) do { try await table.insertItem(databaseItem) @@ -56,22 +59,26 @@ struct SimulateConcurrencyDynamoDBCompositePrimaryKeyTableTests { } } - private func verifyWithUpdate(table: SimulateConcurrencyDynamoDBCompositePrimaryKeyTable, - databaseItem: StandardTypedDatabaseItem, - key: StandardCompositePrimaryKey, - expectedFailureCount: Int) async throws - { + private func verifyWithUpdate( + table: SimulateConcurrencyDynamoDBCompositePrimaryKeyTable, + databaseItem: StandardTypedDatabaseItem, + key: StandardCompositePrimaryKey, + expectedFailureCount: Int + ) async throws { try await table.insertItem(databaseItem) var errorCount = 0 - for _ in 0 ..< 10 { + for _ in 0..<10 { guard let item: DatabaseRowType = try await table.getItem(forKey: key) else { Issue.record("Expected to retrieve item and there was none") return } do { - try await table.updateItem(newItem: item.createUpdatedItem(withValue: item.rowValue), existingItem: item) + try await table.updateItem( + newItem: item.createUpdatedItem(withValue: item.rowValue), + existingItem: item + ) } catch { errorCount += 1 } @@ -93,9 +100,11 @@ struct SimulateConcurrencyDynamoDBCompositePrimaryKeyTableTests { let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: InMemoryDynamoDBCompositePrimaryKeyTable(), - simulateConcurrencyModifications: 5, - simulateOnInsertItem: false) + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: InMemoryDynamoDBCompositePrimaryKeyTable(), + simulateConcurrencyModifications: 5, + simulateOnInsertItem: false + ) try await verifyWithUpdate(table: table, databaseItem: databaseItem, key: key, expectedFailureCount: 5) } @@ -107,10 +116,12 @@ struct SimulateConcurrencyDynamoDBCompositePrimaryKeyTableTests { let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: InMemoryDynamoDBCompositePrimaryKeyTable(), - simulateConcurrencyModifications: 5, - simulateOnInsertItem: false, - simulateOnUpdateItem: false) + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: InMemoryDynamoDBCompositePrimaryKeyTable(), + simulateConcurrencyModifications: 5, + simulateOnInsertItem: false, + simulateOnUpdateItem: false + ) try await verifyWithUpdate(table: table, databaseItem: databaseItem, key: key, expectedFailureCount: 0) } @@ -122,16 +133,20 @@ struct SimulateConcurrencyDynamoDBCompositePrimaryKeyTableTests { let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: InMemoryDynamoDBCompositePrimaryKeyTable(), - simulateConcurrencyModifications: 5, - simulateOnInsertItem: false) + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: InMemoryDynamoDBCompositePrimaryKeyTable(), + simulateConcurrencyModifications: 5, + simulateOnInsertItem: false + ) try await table.insertItem(databaseItem) var errorCount = 0 - for _ in 0 ..< 10 { - let query: [ExpectedQueryableTypes] = try await table.polymorphicQuery(forPartitionKey: "partitionId", - sortKeyCondition: .equals("sortId")) + for _ in 0..<10 { + let query: [ExpectedQueryableTypes] = try await table.polymorphicQuery( + forPartitionKey: "partitionId", + sortKeyCondition: .equals("sortId") + ) guard query.count == 1, case let .testTypeA(firstDatabaseItem) = query[0] else { Issue.record("Expected to retrieve item and there wasn't the correct number or type.") @@ -140,10 +155,12 @@ struct SimulateConcurrencyDynamoDBCompositePrimaryKeyTableTests { let firstValue = firstDatabaseItem.rowValue - let existingItem = DatabaseRowType(compositePrimaryKey: firstDatabaseItem.compositePrimaryKey, - createDate: firstDatabaseItem.createDate, - rowStatus: firstDatabaseItem.rowStatus, - rowValue: firstValue) + let existingItem = DatabaseRowType( + compositePrimaryKey: firstDatabaseItem.compositePrimaryKey, + createDate: firstDatabaseItem.createDate, + rowStatus: firstDatabaseItem.rowStatus, + rowValue: firstValue + ) let item = firstDatabaseItem.createUpdatedItem(withValue: firstValue) do { @@ -169,26 +186,32 @@ struct SimulateConcurrencyDynamoDBCompositePrimaryKeyTableTests { let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: InMemoryDynamoDBCompositePrimaryKeyTable(), - simulateConcurrencyModifications: 5, - simulateOnInsertItem: false) + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: InMemoryDynamoDBCompositePrimaryKeyTable(), + simulateConcurrencyModifications: 5, + simulateOnInsertItem: false + ) try await table.insertItem(databaseItem) var errorCount = 0 - for _ in 0 ..< 10 { - let query: [DatabaseRowType] = try await table.query(forPartitionKey: "partitionId", - sortKeyCondition: .equals("sortId")) + for _ in 0..<10 { + let query: [DatabaseRowType] = try await table.query( + forPartitionKey: "partitionId", + sortKeyCondition: .equals("sortId") + ) guard query.count == 1, let firstQuery = query.first else { Issue.record("Expected to retrieve item and there wasn't the correct number or type.") return } - let existingItem = DatabaseRowType(compositePrimaryKey: firstQuery.compositePrimaryKey, - createDate: firstQuery.createDate, - rowStatus: firstQuery.rowStatus, - rowValue: firstQuery.rowValue) + let existingItem = DatabaseRowType( + compositePrimaryKey: firstQuery.compositePrimaryKey, + createDate: firstQuery.createDate, + rowStatus: firstQuery.rowStatus, + rowValue: firstQuery.rowValue + ) let item = firstQuery.createUpdatedItem(withValue: firstQuery.rowValue) do { @@ -215,12 +238,14 @@ struct SimulateConcurrencyDynamoDBCompositePrimaryKeyTableTests { let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload) let wrappedTable = InMemoryDynamoDBCompositePrimaryKeyTable() - let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable(wrappedDynamoDBTable: wrappedTable, - simulateConcurrencyModifications: 5) + let table = SimulateConcurrencyDynamoDBCompositePrimaryKeyTable( + wrappedDynamoDBTable: wrappedTable, + simulateConcurrencyModifications: 5 + ) try await wrappedTable.insertItem(databaseItem) - for _ in 0 ..< 10 { + for _ in 0..<10 { guard let item: DatabaseRowType = try await table.getItem(forKey: key) else { Issue.record("Expected to retrieve item and there was none") return @@ -228,7 +253,10 @@ struct SimulateConcurrencyDynamoDBCompositePrimaryKeyTableTests { #expect(databaseItem.rowStatus.rowVersion == item.rowStatus.rowVersion) - try await wrappedTable.updateItem(newItem: item.createUpdatedItem(withValue: item.rowValue), existingItem: item) + try await wrappedTable.updateItem( + newItem: item.createUpdatedItem(withValue: item.rowValue), + existingItem: item + ) try await table.clobberItem(item) } diff --git a/Tests/DynamoDBTablesTests/SmokeDynamoDBTestInput.swift b/Tests/DynamoDBTablesTests/SmokeDynamoDBTestInput.swift index 333f9a3..e7af428 100644 --- a/Tests/DynamoDBTablesTests/SmokeDynamoDBTestInput.swift +++ b/Tests/DynamoDBTablesTests/SmokeDynamoDBTestInput.swift @@ -19,12 +19,13 @@ // //===----------------------------------------------------------------------===// +import Foundation + // // SmokeDynamoDBTestInput.swift // DynamoDBTablesTests // @testable import DynamoDBTables -import Foundation @PolymorphicOperationReturnType enum AllQueryableTypes { @@ -66,42 +67,10 @@ struct TypeB: Codable, CustomRowTypeIdentifier { } let serializedTypeADatabaseItem = """ -{ - "M" : { - "PK" : { "S": "partitionKey" }, - "SK" : { "S": "sortKey" }, - "CreateDate" : { "S" : "2018-01-06T23:36:20.355Z" }, - "RowVersion" : { "N": "5" }, - "LastUpdatedDate" : { "S" : "2018-01-06T23:36:20.355Z" }, - "RowType": { "S": "TypeA" }, - "firstly" : { "S": "aaa" }, - "secondly": { "S": "bbb" } - } -} -""" - -let serializedTypeADatabaseItemWithTimeToLive = """ -{ - "M" : { - "PK" : { "S": "partitionKey" }, - "SK" : { "S": "sortKey" }, - "CreateDate" : { "S" : "2018-01-06T23:36:20.355Z" }, - "RowVersion" : { "N": "5" }, - "LastUpdatedDate" : { "S" : "2018-01-06T23:36:20.355Z" }, - "RowType": { "S": "TypeA" }, - "firstly" : { "S": "aaa" }, - "secondly": { "S": "bbb" }, - "ExpireDate": { "N": "123456789" } - } -} -""" - -let serializedPolymorphicDatabaseItemList = """ -[ { "M" : { - "PK" : { "S": "partitionKey1" }, - "SK" : { "S": "sortKey1" }, + "PK" : { "S": "partitionKey" }, + "SK" : { "S": "sortKey" }, "CreateDate" : { "S" : "2018-01-06T23:36:20.355Z" }, "RowVersion" : { "N": "5" }, "LastUpdatedDate" : { "S" : "2018-01-06T23:36:20.355Z" }, @@ -109,48 +78,80 @@ let serializedPolymorphicDatabaseItemList = """ "firstly" : { "S": "aaa" }, "secondly": { "S": "bbb" } } - }, - { - "M" : { - "PK" : { "S": "partitionKey2" }, - "SK" : { "S": "sortKey2" }, - "CreateDate" : { "S" : "2018-01-06T23:36:20.355Z" }, - "RowVersion" : { "N": "12" }, - "LastUpdatedDate" : { "S" : "2018-01-06T23:36:20.355Z" }, - "RowType": { "S": "TypeBCustom" }, - "thirdly" : { "S": "ccc" }, - "fourthly": { "S": "ddd" } - } } -] -""" + """ -let serializedPolymorphicDatabaseItemListWithIndex = """ -[ +let serializedTypeADatabaseItemWithTimeToLive = """ { "M" : { - "PK" : { "S": "partitionKey1" }, - "SK" : { "S": "sortKey1" }, - "GSI-1-PK" : { "S": "gsi-index" }, + "PK" : { "S": "partitionKey" }, + "SK" : { "S": "sortKey" }, "CreateDate" : { "S" : "2018-01-06T23:36:20.355Z" }, "RowVersion" : { "N": "5" }, "LastUpdatedDate" : { "S" : "2018-01-06T23:36:20.355Z" }, - "RowType": { "S": "TypeAWithGSI1PKIndex" }, + "RowType": { "S": "TypeA" }, "firstly" : { "S": "aaa" }, - "secondly": { "S": "bbb" } - } - }, - { - "M" : { - "PK" : { "S": "partitionKey2" }, - "SK" : { "S": "sortKey2" }, - "CreateDate" : { "S" : "2018-01-06T23:36:20.355Z" }, - "RowVersion" : { "N": "12" }, - "LastUpdatedDate" : { "S" : "2018-01-06T23:36:20.355Z" }, - "RowType": { "S": "TypeBCustom" }, - "thirdly" : { "S": "ccc" }, - "fourthly": { "S": "ddd" } + "secondly": { "S": "bbb" }, + "ExpireDate": { "N": "123456789" } } } -] -""" + """ + +let serializedPolymorphicDatabaseItemList = """ + [ + { + "M" : { + "PK" : { "S": "partitionKey1" }, + "SK" : { "S": "sortKey1" }, + "CreateDate" : { "S" : "2018-01-06T23:36:20.355Z" }, + "RowVersion" : { "N": "5" }, + "LastUpdatedDate" : { "S" : "2018-01-06T23:36:20.355Z" }, + "RowType": { "S": "TypeA" }, + "firstly" : { "S": "aaa" }, + "secondly": { "S": "bbb" } + } + }, + { + "M" : { + "PK" : { "S": "partitionKey2" }, + "SK" : { "S": "sortKey2" }, + "CreateDate" : { "S" : "2018-01-06T23:36:20.355Z" }, + "RowVersion" : { "N": "12" }, + "LastUpdatedDate" : { "S" : "2018-01-06T23:36:20.355Z" }, + "RowType": { "S": "TypeBCustom" }, + "thirdly" : { "S": "ccc" }, + "fourthly": { "S": "ddd" } + } + } + ] + """ + +let serializedPolymorphicDatabaseItemListWithIndex = """ + [ + { + "M" : { + "PK" : { "S": "partitionKey1" }, + "SK" : { "S": "sortKey1" }, + "GSI-1-PK" : { "S": "gsi-index" }, + "CreateDate" : { "S" : "2018-01-06T23:36:20.355Z" }, + "RowVersion" : { "N": "5" }, + "LastUpdatedDate" : { "S" : "2018-01-06T23:36:20.355Z" }, + "RowType": { "S": "TypeAWithGSI1PKIndex" }, + "firstly" : { "S": "aaa" }, + "secondly": { "S": "bbb" } + } + }, + { + "M" : { + "PK" : { "S": "partitionKey2" }, + "SK" : { "S": "sortKey2" }, + "CreateDate" : { "S" : "2018-01-06T23:36:20.355Z" }, + "RowVersion" : { "N": "12" }, + "LastUpdatedDate" : { "S" : "2018-01-06T23:36:20.355Z" }, + "RowType": { "S": "TypeBCustom" }, + "thirdly" : { "S": "ccc" }, + "fourthly": { "S": "ddd" } + } + } + ] + """ diff --git a/Tests/DynamoDBTablesTests/SmokeDynamoDBTests.swift b/Tests/DynamoDBTablesTests/SmokeDynamoDBTests.swift index 1ab2dd6..5aadb76 100644 --- a/Tests/DynamoDBTablesTests/SmokeDynamoDBTests.swift +++ b/Tests/DynamoDBTablesTests/SmokeDynamoDBTests.swift @@ -25,18 +25,19 @@ // import AWSDynamoDB -@testable import DynamoDBTables import Foundation import Testing +@testable import DynamoDBTables + private func createDecoder() -> JSONDecoder { let jsonDecoder = JSONDecoder() #if os(Linux) - jsonDecoder.dateDecodingStrategy = .iso8601 + jsonDecoder.dateDecodingStrategy = .iso8601 #elseif os(OSX) - if #available(OSX 10.12, *) { - jsonDecoder.dateDecodingStrategy = .iso8601 - } + if #available(OSX 10.12, *) { + jsonDecoder.dateDecodingStrategy = .iso8601 + } #endif return jsonDecoder @@ -59,20 +60,26 @@ struct DynamoDBTablesTests { func encodeTypedItem() { let inputData = serializedTypeADatabaseItem.data(using: .utf8)! - guard let jsonAttributeValue = try assertNoThrow( - jsonDecoder.decode(DynamoDBClientTypes.AttributeValue.self, from: inputData)) + guard + let jsonAttributeValue = try assertNoThrow( + jsonDecoder.decode(DynamoDBClientTypes.AttributeValue.self, from: inputData) + ) else { return } - guard let databaseItem: StandardTypedDatabaseItem = try assertNoThrow( - DynamoDBDecoder().decode(jsonAttributeValue)) + guard + let databaseItem: StandardTypedDatabaseItem = try assertNoThrow( + DynamoDBDecoder().decode(jsonAttributeValue) + ) else { return } - guard let decodeAttributeValue = try assertNoThrow( - DynamoDBEncoder().encode(databaseItem)) + guard + let decodeAttributeValue = try assertNoThrow( + DynamoDBEncoder().encode(databaseItem) + ) else { return } @@ -89,20 +96,26 @@ struct DynamoDBTablesTests { func encodeTypedItemWithTimeToLive() { let inputData = serializedTypeADatabaseItemWithTimeToLive.data(using: .utf8)! - guard let jsonAttributeValue = try assertNoThrow( - jsonDecoder.decode(DynamoDBClientTypes.AttributeValue.self, from: inputData)) + guard + let jsonAttributeValue = try assertNoThrow( + jsonDecoder.decode(DynamoDBClientTypes.AttributeValue.self, from: inputData) + ) else { return } - guard let databaseItem: StandardTypedDatabaseItem = try assertNoThrow( - DynamoDBDecoder().decode(jsonAttributeValue)) + guard + let databaseItem: StandardTypedDatabaseItem = try assertNoThrow( + DynamoDBDecoder().decode(jsonAttributeValue) + ) else { return } - guard let decodeAttributeValue = try assertNoThrow( - DynamoDBEncoder().encode(databaseItem)) + guard + let decodeAttributeValue = try assertNoThrow( + DynamoDBEncoder().encode(databaseItem) + ) else { return } @@ -119,14 +132,18 @@ struct DynamoDBTablesTests { func typedDatabaseItem() { let inputData = serializedTypeADatabaseItem.data(using: .utf8)! - guard let attributeValue = try assertNoThrow( - jsonDecoder.decode(DynamoDBClientTypes.AttributeValue.self, from: inputData)) + guard + let attributeValue = try assertNoThrow( + jsonDecoder.decode(DynamoDBClientTypes.AttributeValue.self, from: inputData) + ) else { return } - guard let databaseItem: StandardTypedDatabaseItem = try assertNoThrow( - DynamoDBDecoder().decode(attributeValue)) + guard + let databaseItem: StandardTypedDatabaseItem = try assertNoThrow( + DynamoDBDecoder().decode(attributeValue) + ) else { return } @@ -146,14 +163,18 @@ struct DynamoDBTablesTests { func typedDatabaseItemWithTimeToLive() { let inputData = serializedTypeADatabaseItemWithTimeToLive.data(using: .utf8)! - guard let attributeValue = try assertNoThrow( - jsonDecoder.decode(DynamoDBClientTypes.AttributeValue.self, from: inputData)) + guard + let attributeValue = try assertNoThrow( + jsonDecoder.decode(DynamoDBClientTypes.AttributeValue.self, from: inputData) + ) else { return } - guard let databaseItem: StandardTypedDatabaseItem = try assertNoThrow( - DynamoDBDecoder().decode(attributeValue)) + guard + let databaseItem: StandardTypedDatabaseItem = try assertNoThrow( + DynamoDBDecoder().decode(attributeValue) + ) else { return } @@ -165,8 +186,10 @@ struct DynamoDBTablesTests { // create an updated item from the decoded one let newItem = TypeA(firstly: "hello", secondly: "world!!") - let updatedItem = databaseItem.createUpdatedItem(withValue: newItem, - andTimeToLive: StandardTimeToLive(timeToLiveTimestamp: 234_567_890)) + let updatedItem = databaseItem.createUpdatedItem( + withValue: newItem, + andTimeToLive: StandardTimeToLive(timeToLiveTimestamp: 234_567_890) + ) #expect(updatedItem.rowValue.firstly == "hello") #expect(updatedItem.rowValue.secondly == "world!!") #expect(updatedItem.rowStatus.rowVersion == 6) @@ -177,8 +200,10 @@ struct DynamoDBTablesTests { func polymorphicDatabaseItemList() { let inputData = serializedPolymorphicDatabaseItemList.data(using: .utf8)! - guard let attributeValues = try assertNoThrow( - jsonDecoder.decode([DynamoDBClientTypes.AttributeValue].self, from: inputData)) + guard + let attributeValues = try assertNoThrow( + jsonDecoder.decode([DynamoDBClientTypes.AttributeValue].self, from: inputData) + ) else { return } @@ -186,7 +211,8 @@ struct DynamoDBTablesTests { let itemsOptional: [ReturnTypeDecodable]? = try assertNoThrow( attributeValues.map { value in try DynamoDBDecoder().decode(value) - }) + } + ) guard let items = itemsOptional else { Issue.record("No items returned.") @@ -229,8 +255,10 @@ struct DynamoDBTablesTests { func polymorphicDatabaseItemListUnknownType() { let inputData = serializedPolymorphicDatabaseItemList.data(using: .utf8)! - guard let attributeValues = try assertNoThrow( - jsonDecoder.decode([DynamoDBClientTypes.AttributeValue].self, from: inputData)) + guard + let attributeValues = try assertNoThrow( + jsonDecoder.decode([DynamoDBClientTypes.AttributeValue].self, from: inputData) + ) else { return } @@ -254,8 +282,10 @@ struct DynamoDBTablesTests { func polymorphicDatabaseItemListWithIndex() { let inputData = serializedPolymorphicDatabaseItemListWithIndex.data(using: .utf8)! - guard let attributeValues = try assertNoThrow( - jsonDecoder.decode([DynamoDBClientTypes.AttributeValue].self, from: inputData)) + guard + let attributeValues = try assertNoThrow( + jsonDecoder.decode([DynamoDBClientTypes.AttributeValue].self, from: inputData) + ) else { return } @@ -263,7 +293,8 @@ struct DynamoDBTablesTests { let itemsOptional: [ReturnTypeDecodable]? = try assertNoThrow( attributeValues.map { value in try DynamoDBDecoder().decode(value) - }) + } + ) guard let items = itemsOptional else { Issue.record("No items returned.") diff --git a/Tests/DynamoDBTablesTests/String+DynamoDBKeyTests.swift b/Tests/DynamoDBTablesTests/String+DynamoDBKeyTests.swift index 621dead..36db6a9 100644 --- a/Tests/DynamoDBTablesTests/String+DynamoDBKeyTests.swift +++ b/Tests/DynamoDBTablesTests/String+DynamoDBKeyTests.swift @@ -24,9 +24,10 @@ // DynamoDBTablesTests // -@testable import DynamoDBTables import Testing +@testable import DynamoDBTables + struct StringDynamoDBKeyTests { @Test func dynamoDBKeyTests() { @@ -38,10 +39,8 @@ struct StringDynamoDBKeyTests { @Test func dropAsDynamoDBKeyPrefix() { - #expect(["one", "two"].dropAsDynamoDBKeyPrefix(from: "one.two.three.four.five.six")! == - "three.four.five.six") - #expect([].dropAsDynamoDBKeyPrefix(from: "one.two.three.four.five.six")! == - "one.two.three.four.five.six") + #expect(["one", "two"].dropAsDynamoDBKeyPrefix(from: "one.two.three.four.five.six")! == "three.four.five.six") + #expect([].dropAsDynamoDBKeyPrefix(from: "one.two.three.four.five.six")! == "one.two.three.four.five.six") #expect(["four", "two"].dropAsDynamoDBKeyPrefix(from: "one.two.three.four.five.six") == nil) } @@ -58,8 +57,10 @@ struct StringDynamoDBKeyTests { #expect([].dynamodbKeyWithPrefixedVersion(8, minimumFieldWidth: 5) == "v00008") #expect(["one"].dynamodbKeyWithPrefixedVersion(8, minimumFieldWidth: 5) == "v00008.one") #expect(["one", "two"].dynamodbKeyWithPrefixedVersion(8, minimumFieldWidth: 5) == "v00008.one.two") - #expect(["one", "two", "three", "four", "five", "six"].dynamodbKeyWithPrefixedVersion(8, minimumFieldWidth: 5) == - "v00008.one.two.three.four.five.six") + #expect( + ["one", "two", "three", "four", "five", "six"].dynamodbKeyWithPrefixedVersion(8, minimumFieldWidth: 5) + == "v00008.one.two.three.four.five.six" + ) #expect(["one", "two"].dynamodbKeyWithPrefixedVersion(8, minimumFieldWidth: 2) == "v08.one.two") #expect(["one", "two"].dynamodbKeyWithPrefixedVersion(4888, minimumFieldWidth: 2) == "v4888.one.two") diff --git a/Tests/DynamoDBTablesTests/TestConfiguration.swift b/Tests/DynamoDBTablesTests/TestConfiguration.swift index d9bc86d..52d02b7 100644 --- a/Tests/DynamoDBTablesTests/TestConfiguration.swift +++ b/Tests/DynamoDBTablesTests/TestConfiguration.swift @@ -24,9 +24,10 @@ // DynamoDBTablesTests // -@testable import DynamoDBTables import Foundation +@testable import DynamoDBTables + struct TestTypeA: Codable, Equatable { let firstly: String let secondly: String diff --git a/Tests/DynamoDBTablesTests/TypedDatabaseItem+RowWithItemVersionProtocolTests.swift b/Tests/DynamoDBTablesTests/TypedDatabaseItem+RowWithItemVersionProtocolTests.swift index d48c047..89afd26 100644 --- a/Tests/DynamoDBTablesTests/TypedDatabaseItem+RowWithItemVersionProtocolTests.swift +++ b/Tests/DynamoDBTablesTests/TypedDatabaseItem+RowWithItemVersionProtocolTests.swift @@ -24,11 +24,11 @@ // DynamoDBTablesTests // +import AWSDynamoDB import Foundation +import Testing -import AWSDynamoDB @testable import DynamoDBTables -import Testing private let ORIGINAL_PAYLOAD = "Payload" private let ORIGINAL_TIME_TO_LIVE: Int64 = 123_456_789 @@ -38,13 +38,17 @@ private let UPDATED_TIME_TO_LIVE: Int64 = 234_567_890 struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { @Test func createUpdatedRowWithItemVersion() throws { - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let rowWithItemVersion = RowWithItemVersion.newItem(withValue: "Payload") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: rowWithItemVersion) - let updatedItem = try databaseItem.createUpdatedRowWithItemVersion(withValue: "Updated", - conditionalStatusVersion: nil) + let updatedItem = try databaseItem.createUpdatedRowWithItemVersion( + withValue: "Updated", + conditionalStatusVersion: nil + ) #expect(databaseItem.rowStatus.rowVersion == 1) #expect(databaseItem.rowValue.itemVersion == 1) @@ -58,16 +62,22 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { @Test func createUpdatedRowWithItemVersionWithTimeToLive() throws { - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let rowWithItemVersion = RowWithItemVersion.newItem(withValue: "Payload") - let databaseItem = TypedTTLDatabaseItem.newItem(withKey: compositeKey, - andValue: rowWithItemVersion, - andTimeToLive: StandardTimeToLive(timeToLiveTimestamp: 123_456_789)) - - let updatedItem = try databaseItem.createUpdatedRowWithItemVersion(withValue: "Updated", - conditionalStatusVersion: nil, - andTimeToLive: StandardTimeToLive(timeToLiveTimestamp: 234_567_890)) + let databaseItem = TypedTTLDatabaseItem.newItem( + withKey: compositeKey, + andValue: rowWithItemVersion, + andTimeToLive: StandardTimeToLive(timeToLiveTimestamp: 123_456_789) + ) + + let updatedItem = try databaseItem.createUpdatedRowWithItemVersion( + withValue: "Updated", + conditionalStatusVersion: nil, + andTimeToLive: StandardTimeToLive(timeToLiveTimestamp: 234_567_890) + ) #expect(databaseItem.rowStatus.rowVersion == 1) #expect(databaseItem.rowValue.itemVersion == 1) @@ -81,13 +91,17 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { @Test func createUpdatedRowWithItemVersionWithCorrectConditionalVersion() throws { - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let rowWithItemVersion = RowWithItemVersion.newItem(withValue: "Payload") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: rowWithItemVersion) - let updatedItem = try databaseItem.createUpdatedRowWithItemVersion(withValue: "Updated", - conditionalStatusVersion: 1) + let updatedItem = try databaseItem.createUpdatedRowWithItemVersion( + withValue: "Updated", + conditionalStatusVersion: 1 + ) #expect(databaseItem.rowStatus.rowVersion == 1) #expect(databaseItem.rowValue.itemVersion == 1) @@ -99,14 +113,18 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { @Test func createUpdatedRowWithItemVersionWithIncorrectConditionalVersion() { - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let rowWithItemVersion = RowWithItemVersion.newItem(withValue: "Payload") let databaseItem = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: rowWithItemVersion) do { - _ = try databaseItem.createUpdatedRowWithItemVersion(withValue: "Updated", - conditionalStatusVersion: 8) + _ = try databaseItem.createUpdatedRowWithItemVersion( + withValue: "Updated", + conditionalStatusVersion: 8 + ) Issue.record("Expected error not thrown.") } catch DynamoDBTableError.concurrencyError { @@ -116,27 +134,45 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { } } - func getTable(escapeSingleQuoteInPartiQL: Bool = false) throws -> GenericAWSDynamoDBCompositePrimaryKeyTable { - GenericAWSDynamoDBCompositePrimaryKeyTable(tableName: "DummyTable", - client: MockDynamoDBClientProtocol(), - tableConfiguration: .init(escapeSingleQuoteInPartiQL: escapeSingleQuoteInPartiQL)) + func getTable( + escapeSingleQuoteInPartiQL: Bool = false + ) throws -> GenericAWSDynamoDBCompositePrimaryKeyTable { + GenericAWSDynamoDBCompositePrimaryKeyTable( + tableName: "DummyTable", + client: MockDynamoDBClientProtocol(), + tableConfiguration: .init(escapeSingleQuoteInPartiQL: escapeSingleQuoteInPartiQL) + ) } @Test func stringFieldDifference() throws { let theStruct = TestTypeA(firstly: "firstly", secondly: "secondly") - let payloadA = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: ["thirdly", "fourthly"]) - let payloadB = TestTypeC(theString: "eigthly", theNumber: 4, theStruct: theStruct, theList: ["thirdly", "fourthly"]) - - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let payloadA = TestTypeC( + theString: "firstly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdly", "fourthly"] + ) + let payloadB = TestTypeC( + theString: "eigthly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdly", "fourthly"] + ) + + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = databaseItemA.createUpdatedItem(withValue: payloadB) let table = try getTable() - let differences = try table.diffItems(newItem: databaseItemB, - existingItem: databaseItemA) + let differences = try table.diffItems( + newItem: databaseItemB, + existingItem: databaseItemA + ) let pathMap = differences.pathMap #expect(pathMap["\"theString\""] == .update(path: "\"theString\"", value: "'eigthly'")) @@ -146,18 +182,32 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { @Test func stringFieldDifferenceWithEscapedQuotes() throws { let theStruct = TestTypeA(firstly: "firstly", secondly: "secondly") - let payloadA = TestTypeC(theString: "f'ir''st'''ly", theNumber: 4, theStruct: theStruct, theList: ["t'hi''rdly", "fourt''hl'y"]) - let payloadB = TestTypeC(theString: "e'ig''thl'''y", theNumber: 4, theStruct: theStruct, theList: ["t'hi''rdly", "fourt''hl'y"]) - - let compositeKey = StandardCompositePrimaryKey(partitionKey: "par'titio''nKey", - sortKey: "so'rt'''Key") + let payloadA = TestTypeC( + theString: "f'ir''st'''ly", + theNumber: 4, + theStruct: theStruct, + theList: ["t'hi''rdly", "fourt''hl'y"] + ) + let payloadB = TestTypeC( + theString: "e'ig''thl'''y", + theNumber: 4, + theStruct: theStruct, + theList: ["t'hi''rdly", "fourt''hl'y"] + ) + + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "par'titio''nKey", + sortKey: "so'rt'''Key" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = databaseItemA.createUpdatedItem(withValue: payloadB) let table = try getTable(escapeSingleQuoteInPartiQL: true) - let differences = try table.diffItems(newItem: databaseItemB, - existingItem: databaseItemA) + let differences = try table.diffItems( + newItem: databaseItemB, + existingItem: databaseItemA + ) let pathMap = differences.pathMap #expect(pathMap["\"theString\""] == .update(path: "\"theString\"", value: "'e''ig''''thl''''''y'")) @@ -167,18 +217,32 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { @Test func numberFieldDifference() throws { let theStruct = TestTypeA(firstly: "firstly", secondly: "secondly") - let payloadA = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: ["thirdly", "fourthly"]) - let payloadB = TestTypeC(theString: "firstly", theNumber: 12, theStruct: theStruct, theList: ["thirdly", "fourthly"]) - - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let payloadA = TestTypeC( + theString: "firstly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdly", "fourthly"] + ) + let payloadB = TestTypeC( + theString: "firstly", + theNumber: 12, + theStruct: theStruct, + theList: ["thirdly", "fourthly"] + ) + + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = databaseItemA.createUpdatedItem(withValue: payloadB) let table = try getTable() - let differences = try table.diffItems(newItem: databaseItemB, - existingItem: databaseItemA) + let differences = try table.diffItems( + newItem: databaseItemB, + existingItem: databaseItemA + ) let pathMap = differences.pathMap #expect(pathMap["\"theNumber\""] == .update(path: "\"theNumber\"", value: "12")) @@ -189,18 +253,32 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { func structFieldDifference() throws { let theStructA = TestTypeA(firstly: "firstly", secondly: "secondly") let theStructB = TestTypeA(firstly: "eigthly", secondly: "secondly") - let payloadA = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStructA, theList: ["thirdly", "fourthly"]) - let payloadB = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStructB, theList: ["thirdly", "fourthly"]) - - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let payloadA = TestTypeC( + theString: "firstly", + theNumber: 4, + theStruct: theStructA, + theList: ["thirdly", "fourthly"] + ) + let payloadB = TestTypeC( + theString: "firstly", + theNumber: 4, + theStruct: theStructB, + theList: ["thirdly", "fourthly"] + ) + + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = databaseItemA.createUpdatedItem(withValue: payloadB) let table = try getTable() - let differences = try table.diffItems(newItem: databaseItemB, - existingItem: databaseItemA) + let differences = try table.diffItems( + newItem: databaseItemB, + existingItem: databaseItemA + ) let pathMap = differences.pathMap #expect(pathMap["\"theStruct\".\"firstly\""] == .update(path: "\"theStruct\".\"firstly\"", value: "'eigthly'")) @@ -211,39 +289,70 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { func structFieldDifferenceWithEscapedQuotes() throws { let theStructA = TestTypeA(firstly: "f'ir''st'''ly", secondly: "se'con''dl'''y") let theStructB = TestTypeA(firstly: "e'ig''thly'''", secondly: "se'con''dl'''y") - let payloadA = TestTypeC(theString: "fi''rst'ly", theNumber: 4, theStruct: theStructA, theList: ["t'hi'rdl'y", "f'ou'rthl'y"]) - let payloadB = TestTypeC(theString: "fi''rstl'y", theNumber: 4, theStruct: theStructB, theList: ["t'hi'rdl'y", "f'ou'rthl'y"]) - - let compositeKey = StandardCompositePrimaryKey(partitionKey: "par'titio''nKey", - sortKey: "so'rt'''Key") + let payloadA = TestTypeC( + theString: "fi''rst'ly", + theNumber: 4, + theStruct: theStructA, + theList: ["t'hi'rdl'y", "f'ou'rthl'y"] + ) + let payloadB = TestTypeC( + theString: "fi''rstl'y", + theNumber: 4, + theStruct: theStructB, + theList: ["t'hi'rdl'y", "f'ou'rthl'y"] + ) + + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "par'titio''nKey", + sortKey: "so'rt'''Key" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = databaseItemA.createUpdatedItem(withValue: payloadB) let table = try getTable(escapeSingleQuoteInPartiQL: true) - let differences = try table.diffItems(newItem: databaseItemB, - existingItem: databaseItemA) + let differences = try table.diffItems( + newItem: databaseItemB, + existingItem: databaseItemA + ) let pathMap = differences.pathMap - #expect(pathMap["\"theStruct\".\"firstly\""] == .update(path: "\"theStruct\".\"firstly\"", value: "'e''ig''''thly'''''''")) + #expect( + pathMap["\"theStruct\".\"firstly\""] + == .update(path: "\"theStruct\".\"firstly\"", value: "'e''ig''''thly'''''''") + ) #expect(pathMap["\"RowVersion\""] == .update(path: "\"RowVersion\"", value: "2")) } @Test func listFieldDifference() throws { let theStruct = TestTypeA(firstly: "firstly", secondly: "secondly") - let payloadA = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: ["thirdly", "fourthly"]) - let payloadB = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: ["thirdly", "eigthly", "ninthly", "tenthly"]) - - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let payloadA = TestTypeC( + theString: "firstly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdly", "fourthly"] + ) + let payloadB = TestTypeC( + theString: "firstly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdly", "eigthly", "ninthly", "tenthly"] + ) + + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = databaseItemA.createUpdatedItem(withValue: payloadB) let table = try getTable() - let differences = try table.diffItems(newItem: databaseItemB, - existingItem: databaseItemA) + let differences = try table.diffItems( + newItem: databaseItemB, + existingItem: databaseItemA + ) let pathMap = differences.pathMap #expect(pathMap["\"theList\"[1]"] == .update(path: "\"theList\"[1]", value: "'eigthly'")) @@ -261,15 +370,19 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { let payloadA = NestedLists(nestedList: [["one", "two"], ["three", "four"]]) let payloadB = NestedLists(nestedList: [["one", "five"], ["three", "four", "eight"], ["six", "seven"]]) - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = databaseItemA.createUpdatedItem(withValue: payloadB) let table = try getTable() - let differences = try table.diffItems(newItem: databaseItemB, - existingItem: databaseItemA) + let differences = try table.diffItems( + newItem: databaseItemB, + existingItem: databaseItemA + ) let pathMap = differences.pathMap #expect(pathMap["\"nestedList\"[0][1]"] == .update(path: "\"nestedList\"[0][1]", value: "'five'")) @@ -281,22 +394,40 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { @Test func listFieldDifferenceWithEscapedQuotes() throws { let theStruct = TestTypeA(firstly: "f'irstl''y", secondly: "se'cond''ly") - let payloadA = TestTypeC(theString: "f'irst''ly", theNumber: 4, theStruct: theStruct, theList: ["th'irdly", - "fo''urthly"]) - let payloadB = TestTypeC(theString: "f'irst''ly", theNumber: 4, theStruct: theStruct, theList: ["th'irdly", - "eigth'''ly", - "ni'n''thly", - "ten'thly'"]) - - let compositeKey = StandardCompositePrimaryKey(partitionKey: "par'titio''nKey", - sortKey: "so'rt'''Key") + let payloadA = TestTypeC( + theString: "f'irst''ly", + theNumber: 4, + theStruct: theStruct, + theList: [ + "th'irdly", + "fo''urthly", + ] + ) + let payloadB = TestTypeC( + theString: "f'irst''ly", + theNumber: 4, + theStruct: theStruct, + theList: [ + "th'irdly", + "eigth'''ly", + "ni'n''thly", + "ten'thly'", + ] + ) + + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "par'titio''nKey", + sortKey: "so'rt'''Key" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = databaseItemA.createUpdatedItem(withValue: payloadB) let table = try getTable(escapeSingleQuoteInPartiQL: true) - let differences = try table.diffItems(newItem: databaseItemB, - existingItem: databaseItemA) + let differences = try table.diffItems( + newItem: databaseItemB, + existingItem: databaseItemA + ) let pathMap = differences.pathMap #expect(pathMap["\"theList\"[1]"] == .update(path: "\"theList\"[1]", value: "'eigth''''''ly'")) @@ -309,17 +440,26 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { func stringFieldAddition() throws { let theStruct = TestTypeA(firstly: "firstly", secondly: "secondly") let payloadA = TestTypeC(theString: nil, theNumber: 4, theStruct: theStruct, theList: ["thirdly", "fourthly"]) - let payloadB = TestTypeC(theString: "eigthly", theNumber: 4, theStruct: theStruct, theList: ["thirdly", "fourthly"]) - - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let payloadB = TestTypeC( + theString: "eigthly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdly", "fourthly"] + ) + + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = databaseItemA.createUpdatedItem(withValue: payloadB) let table = try getTable() - let differences = try table.diffItems(newItem: databaseItemB, - existingItem: databaseItemA) + let differences = try table.diffItems( + newItem: databaseItemB, + existingItem: databaseItemA + ) let pathMap = differences.pathMap #expect(pathMap["\"theString\""] == .update(path: "\"theString\"", value: "'eigthly'")) @@ -329,18 +469,32 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { @Test func numberFieldAddition() throws { let theStruct = TestTypeA(firstly: "firstly", secondly: "secondly") - let payloadA = TestTypeC(theString: "firstly", theNumber: nil, theStruct: theStruct, theList: ["thirdly", "fourthly"]) - let payloadB = TestTypeC(theString: "firstly", theNumber: 12, theStruct: theStruct, theList: ["thirdly", "fourthly"]) - - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let payloadA = TestTypeC( + theString: "firstly", + theNumber: nil, + theStruct: theStruct, + theList: ["thirdly", "fourthly"] + ) + let payloadB = TestTypeC( + theString: "firstly", + theNumber: 12, + theStruct: theStruct, + theList: ["thirdly", "fourthly"] + ) + + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = databaseItemA.createUpdatedItem(withValue: payloadB) let table = try getTable() - let differences = try table.diffItems(newItem: databaseItemB, - existingItem: databaseItemA) + let differences = try table.diffItems( + newItem: databaseItemB, + existingItem: databaseItemA + ) let pathMap = differences.pathMap #expect(pathMap["\"theNumber\""] == .update(path: "\"theNumber\"", value: "12")) @@ -351,17 +505,26 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { func structFieldAddition() throws { let theStruct = TestTypeA(firstly: "firstly", secondly: "secondly") let payloadA = TestTypeC(theString: "firstly", theNumber: 4, theStruct: nil, theList: ["thirdly", "fourthly"]) - let payloadB = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: ["thirdly", "fourthly"]) - - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let payloadB = TestTypeC( + theString: "firstly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdly", "fourthly"] + ) + + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = databaseItemA.createUpdatedItem(withValue: payloadB) let table = try getTable() - let differences = try table.diffItems(newItem: databaseItemB, - existingItem: databaseItemA) + let differences = try table.diffItems( + newItem: databaseItemB, + existingItem: databaseItemA + ) let pathMap = differences.pathMap guard case let .update(_, value) = pathMap["\"theStruct\""] else { @@ -369,8 +532,9 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { return } - let valueMatches = (value == "{'firstly': 'firstly', 'secondly': 'secondly'}") || - (value == "{'secondly': 'secondly', 'firstly': 'firstly'}") + let valueMatches = + (value == "{'firstly': 'firstly', 'secondly': 'secondly'}") + || (value == "{'secondly': 'secondly', 'firstly': 'firstly'}") #expect(valueMatches) #expect(pathMap["\"RowVersion\""] == .update(path: "\"RowVersion\"", value: "2")) @@ -380,38 +544,59 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { func listFieldAddition() throws { let theStruct = TestTypeA(firstly: "firstly", secondly: "secondly") let payloadA = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: nil) - let payloadB = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: ["thirdly", "eigthly", "ninthly", "tenthly"]) - - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let payloadB = TestTypeC( + theString: "firstly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdly", "eigthly", "ninthly", "tenthly"] + ) + + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = databaseItemA.createUpdatedItem(withValue: payloadB) let table = try getTable() - let differences = try table.diffItems(newItem: databaseItemB, - existingItem: databaseItemA) + let differences = try table.diffItems( + newItem: databaseItemB, + existingItem: databaseItemA + ) let pathMap = differences.pathMap - #expect(pathMap["\"theList\""] == .update(path: "\"theList\"", value: "['thirdly', 'eigthly', 'ninthly', 'tenthly']")) + #expect( + pathMap["\"theList\""] + == .update(path: "\"theList\"", value: "['thirdly', 'eigthly', 'ninthly', 'tenthly']") + ) #expect(pathMap["\"RowVersion\""] == .update(path: "\"RowVersion\"", value: "2")) } @Test func stringFieldRemoval() throws { let theStruct = TestTypeA(firstly: "firstly", secondly: "secondly") - let payloadA = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: ["thirdly", "fourthly"]) + let payloadA = TestTypeC( + theString: "firstly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdly", "fourthly"] + ) let payloadB = TestTypeC(theString: nil, theNumber: 4, theStruct: theStruct, theList: ["thirdly", "fourthly"]) - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = databaseItemA.createUpdatedItem(withValue: payloadB) let table = try getTable() - let differences = try table.diffItems(newItem: databaseItemB, - existingItem: databaseItemA) + let differences = try table.diffItems( + newItem: databaseItemB, + existingItem: databaseItemA + ) let pathMap = differences.pathMap #expect(pathMap["\"theString\""] == .remove(path: "\"theString\"")) @@ -421,18 +606,32 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { @Test func numberFieldRemoval() throws { let theStruct = TestTypeA(firstly: "firstly", secondly: "secondly") - let payloadA = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: ["thirdly", "fourthly"]) - let payloadB = TestTypeC(theString: "firstly", theNumber: nil, theStruct: theStruct, theList: ["thirdly", "fourthly"]) - - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let payloadA = TestTypeC( + theString: "firstly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdly", "fourthly"] + ) + let payloadB = TestTypeC( + theString: "firstly", + theNumber: nil, + theStruct: theStruct, + theList: ["thirdly", "fourthly"] + ) + + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = databaseItemA.createUpdatedItem(withValue: payloadB) let table = try getTable() - let differences = try table.diffItems(newItem: databaseItemB, - existingItem: databaseItemA) + let differences = try table.diffItems( + newItem: databaseItemB, + existingItem: databaseItemA + ) let pathMap = differences.pathMap #expect(pathMap["\"theNumber\""] == .remove(path: "\"theNumber\"")) @@ -442,18 +641,27 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { @Test func structFieldRemoval() throws { let theStruct = TestTypeA(firstly: "firstly", secondly: "secondly") - let payloadA = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: ["thirdly", "fourthly"]) + let payloadA = TestTypeC( + theString: "firstly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdly", "fourthly"] + ) let payloadB = TestTypeC(theString: "firstly", theNumber: 4, theStruct: nil, theList: ["thirdly", "fourthly"]) - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = databaseItemA.createUpdatedItem(withValue: payloadB) let table = try getTable() - let differences = try table.diffItems(newItem: databaseItemB, - existingItem: databaseItemA) + let differences = try table.diffItems( + newItem: databaseItemB, + existingItem: databaseItemA + ) let pathMap = differences.pathMap #expect(pathMap["\"theStruct\""] == .remove(path: "\"theStruct\"")) @@ -463,18 +671,27 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { @Test func listFieldRemoval() throws { let theStruct = TestTypeA(firstly: "firstly", secondly: "secondly") - let payloadA = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: ["thirdly", "fourthly"]) + let payloadA = TestTypeC( + theString: "firstly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdly", "fourthly"] + ) let payloadB = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: nil) - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = databaseItemA.createUpdatedItem(withValue: payloadB) let table = try getTable() - let differences = try table.diffItems(newItem: databaseItemB, - existingItem: databaseItemA) + let differences = try table.diffItems( + newItem: databaseItemB, + existingItem: databaseItemA + ) let pathMap = differences.pathMap #expect(pathMap["\"theList\""] == .remove(path: "\"theList\"")) @@ -485,52 +702,82 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { func listFieldDifferenceExpression() throws { let tableName = "TableName" let theStruct = TestTypeA(firstly: "firstly", secondly: "secondly") - let payloadA = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: ["thirdly", "fourthly"]) - let payloadB = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: ["thirdly", "eigthly", "ninthly", "tenthly"]) - - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let payloadA = TestTypeC( + theString: "firstly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdly", "fourthly"] + ) + let payloadB = TestTypeC( + theString: "firstly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdly", "eigthly", "ninthly", "tenthly"] + ) + + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadB) let table = try getTable() - let expression = try table.getUpdateExpression(tableName: tableName, - newItem: databaseItemB, - existingItem: databaseItemA) - #expect(expression == "UPDATE \"TableName\" " - + "SET \"theList\"[1]='eigthly' " - + "SET \"theList\"[2]='ninthly' " - + "SET \"theList\"[3]='tenthly' " - + "WHERE PK='partitionKey' AND SK='sortKey' " - + "AND RowVersion=1") + let expression = try table.getUpdateExpression( + tableName: tableName, + newItem: databaseItemB, + existingItem: databaseItemA + ) + #expect( + expression == "UPDATE \"TableName\" " + + "SET \"theList\"[1]='eigthly' " + + "SET \"theList\"[2]='ninthly' " + + "SET \"theList\"[3]='tenthly' " + + "WHERE PK='partitionKey' AND SK='sortKey' " + + "AND RowVersion=1" + ) } @Test func listFieldDifferenceExpressionWithEscapedQuotes() throws { let tableName = "TableName" let theStruct = TestTypeA(firstly: "f'irstly", secondly: "s'econdly") - let payloadA = TestTypeC(theString: "fi''rstly", theNumber: 4, theStruct: theStruct, - theList: ["thirdl'y", "fourt''hly"]) - let payloadB = TestTypeC(theString: "fi''rstly", theNumber: 4, theStruct: theStruct, - theList: ["thirdl'y", "eigth''ly", "n'inthly", "tenth'''ly"]) - - let compositeKey = StandardCompositePrimaryKey(partitionKey: "pa'rtition''Key", - sortKey: "so'rt''Key") + let payloadA = TestTypeC( + theString: "fi''rstly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdl'y", "fourt''hly"] + ) + let payloadB = TestTypeC( + theString: "fi''rstly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdl'y", "eigth''ly", "n'inthly", "tenth'''ly"] + ) + + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "pa'rtition''Key", + sortKey: "so'rt''Key" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadB) let table = try getTable(escapeSingleQuoteInPartiQL: true) - let expression = try table.getUpdateExpression(tableName: tableName, - newItem: databaseItemB, - existingItem: databaseItemA) - #expect(expression == "UPDATE \"TableName\" " - + "SET \"theList\"[1]='eigth''''ly' " - + "SET \"theList\"[2]='n''inthly' " - + "SET \"theList\"[3]='tenth''''''ly' " - + "WHERE PK='pa''rtition''''Key' AND SK='so''rt''''Key' " - + "AND RowVersion=1") + let expression = try table.getUpdateExpression( + tableName: tableName, + newItem: databaseItemB, + existingItem: databaseItemA + ) + #expect( + expression == "UPDATE \"TableName\" " + + "SET \"theList\"[1]='eigth''''ly' " + + "SET \"theList\"[2]='n''inthly' " + + "SET \"theList\"[3]='tenth''''''ly' " + + "WHERE PK='pa''rtition''''Key' AND SK='so''rt''''Key' " + + "AND RowVersion=1" + ) } @Test @@ -538,102 +785,157 @@ struct TypedTTLDatabaseItemRowWithItemVersionProtocolTests { let tableName = "TableName" let theStruct = TestTypeA(firstly: "firstly", secondly: "secondly") let payloadA = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: nil) - let payloadB = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: ["thirdly", "eigthly", "ninthly", "tenthly"]) - - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let payloadB = TestTypeC( + theString: "firstly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdly", "eigthly", "ninthly", "tenthly"] + ) + + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadB) let table = try getTable() - let expression = try table.getUpdateExpression(tableName: tableName, - newItem: databaseItemB, - existingItem: databaseItemA) - #expect(expression == "UPDATE \"TableName\" " - + "SET \"theList\"=['thirdly', 'eigthly', 'ninthly', 'tenthly'] " - + "WHERE PK='partitionKey' AND SK='sortKey' " - + "AND RowVersion=1") + let expression = try table.getUpdateExpression( + tableName: tableName, + newItem: databaseItemB, + existingItem: databaseItemA + ) + #expect( + expression == "UPDATE \"TableName\" " + + "SET \"theList\"=['thirdly', 'eigthly', 'ninthly', 'tenthly'] " + + "WHERE PK='partitionKey' AND SK='sortKey' " + + "AND RowVersion=1" + ) } @Test func listFieldRemovalExpression() throws { let tableName = "TableName" let theStruct = TestTypeA(firstly: "firstly", secondly: "secondly") - let payloadA = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: ["thirdly", "fourthly"]) + let payloadA = TestTypeC( + theString: "firstly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdly", "fourthly"] + ) let payloadB = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: nil) - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let databaseItemB = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadB) let table = try getTable() - let expression = try table.getUpdateExpression(tableName: tableName, - newItem: databaseItemB, - existingItem: databaseItemA) - #expect(expression == "UPDATE \"TableName\" " - + "REMOVE \"theList\" " - + "WHERE PK='partitionKey' AND SK='sortKey' " - + "AND RowVersion=1") + let expression = try table.getUpdateExpression( + tableName: tableName, + newItem: databaseItemB, + existingItem: databaseItemA + ) + #expect( + expression == "UPDATE \"TableName\" " + + "REMOVE \"theList\" " + + "WHERE PK='partitionKey' AND SK='sortKey' " + + "AND RowVersion=1" + ) } @Test func deleteItemExpression() throws { let tableName = "TableName" let theStruct = TestTypeA(firstly: "firstly", secondly: "secondly") - let payloadA = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: ["thirdly", "fourthly"]) - - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let payloadA = TestTypeC( + theString: "firstly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdly", "fourthly"] + ) + + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let table = try getTable() - let expression = try table.getDeleteExpression(tableName: tableName, - existingItem: databaseItemA) - #expect(expression == "DELETE FROM \"TableName\" " - + "WHERE PK='partitionKey' AND SK='sortKey' " - + "AND RowVersion=1") + let expression = try table.getDeleteExpression( + tableName: tableName, + existingItem: databaseItemA + ) + #expect( + expression == "DELETE FROM \"TableName\" " + + "WHERE PK='partitionKey' AND SK='sortKey' " + + "AND RowVersion=1" + ) } @Test func deleteItemExpressionWithEscapedQuotes() throws { let tableName = "TableName" let theStruct = TestTypeA(firstly: "fir'stly", secondly: "secondl'y") - let payloadA = TestTypeC(theString: "f'irstly", theNumber: 4, theStruct: theStruct, theList: ["third''ly", "fou'''rthly"]) - - let compositeKey = StandardCompositePrimaryKey(partitionKey: "p'artitionKe''y", - sortKey: "sort'''Key") + let payloadA = TestTypeC( + theString: "f'irstly", + theNumber: 4, + theStruct: theStruct, + theList: ["third''ly", "fou'''rthly"] + ) + + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "p'artitionKe''y", + sortKey: "sort'''Key" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let table = try getTable(escapeSingleQuoteInPartiQL: true) - let expression = try table.getDeleteExpression(tableName: tableName, - existingItem: databaseItemA) - #expect(expression == "DELETE FROM \"TableName\" " - + "WHERE PK='p''artitionKe''''y' AND SK='sort''''''Key' " - + "AND RowVersion=1") + let expression = try table.getDeleteExpression( + tableName: tableName, + existingItem: databaseItemA + ) + #expect( + expression == "DELETE FROM \"TableName\" " + + "WHERE PK='p''artitionKe''''y' AND SK='sort''''''Key' " + + "AND RowVersion=1" + ) } @Test func deleteKeyExpression() throws { let tableName = "TableName" let theStruct = TestTypeA(firstly: "firstly", secondly: "secondly") - let payloadA = TestTypeC(theString: "firstly", theNumber: 4, theStruct: theStruct, theList: ["thirdly", "fourthly"]) - - let compositeKey = StandardCompositePrimaryKey(partitionKey: "partitionKey", - sortKey: "sortKey") + let payloadA = TestTypeC( + theString: "firstly", + theNumber: 4, + theStruct: theStruct, + theList: ["thirdly", "fourthly"] + ) + + let compositeKey = StandardCompositePrimaryKey( + partitionKey: "partitionKey", + sortKey: "sortKey" + ) let databaseItemA = StandardTypedDatabaseItem.newItem(withKey: compositeKey, andValue: payloadA) let table = try getTable() - let expression = try table.getDeleteExpression(tableName: tableName, - existingItem: databaseItemA) - #expect(expression == "DELETE FROM \"TableName\" " - + "WHERE PK='partitionKey' AND SK='sortKey' " - + "AND RowVersion=1") + let expression = try table.getDeleteExpression( + tableName: tableName, + existingItem: databaseItemA + ) + #expect( + expression == "DELETE FROM \"TableName\" " + + "WHERE PK='partitionKey' AND SK='sortKey' " + + "AND RowVersion=1" + ) } } From 6823a9ff86a4da5047734c13f50ae96346f59a34 Mon Sep 17 00:00:00 2001 From: Simon Pilkington Date: Fri, 15 Aug 2025 13:23:20 +1000 Subject: [PATCH 2/2] Fix linting. --- ...CompositePrimaryKeyTable+updateItems.swift | 193 ++++------ .../CustomRowTypeIdentifier.swift | 2 +- lint | 361 ++++++++++++++++++ 3 files changed, 439 insertions(+), 117 deletions(-) create mode 100644 lint diff --git a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift index 775965b..ab2497f 100644 --- a/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift +++ b/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift @@ -255,6 +255,73 @@ extension GenericAWSDynamoDBCompositePrimaryKeyTable { ) } + private func getErrorReasons( + cancellationReasons: [DynamoDBClientTypes.CancellationReason], + keys: [CompositePrimaryKey], + entryCount: Int + ) throws -> (reasons: [DynamoDBTableError], isTransactionConflict: Bool) { + var isTransactionConflict = false + let reasons = try zip(cancellationReasons, keys).compactMap { + cancellationReason, + entryKey -> DynamoDBTableError? in + let key: CompositePrimaryKey? = + if let item = cancellationReason.item { + try DynamoDBDecoder().decode(.m(item)) + } else { + nil + } + + let partitionKey = key?.partitionKey ?? entryKey.partitionKey + let sortKey = key?.sortKey ?? entryKey.sortKey + + // https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ExecuteTransaction.html + switch cancellationReason.code { + case "None": + return nil + case "ConditionalCheckFailed": + return DynamoDBTableError.conditionalCheckFailed( + partitionKey: partitionKey, + sortKey: sortKey, + message: cancellationReason.message + ) + case "DuplicateItem": + return DynamoDBTableError.duplicateItem( + partitionKey: partitionKey, + sortKey: sortKey, + message: cancellationReason.message + ) + case "ItemCollectionSizeLimitExceeded": + return DynamoDBTableError.itemCollectionSizeLimitExceeded( + attemptedSize: entryCount, + maximumSize: AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement + ) + case "TransactionConflict": + isTransactionConflict = true + + return DynamoDBTableError.transactionConflict(message: cancellationReason.message) + case "ProvisionedThroughputExceeded": + return DynamoDBTableError.provisionedThroughputExceeded(message: cancellationReason.message) + case "ThrottlingError": + return DynamoDBTableError.throttling(message: cancellationReason.message) + case "ValidationError": + return DynamoDBTableError.validation( + partitionKey: partitionKey, + sortKey: sortKey, + message: cancellationReason.message + ) + default: + return DynamoDBTableError.unknown( + code: cancellationReason.code, + partitionKey: partitionKey, + sortKey: sortKey, + message: cancellationReason.message + ) + } + } + + return (reasons, isTransactionConflict) + } + private func transactWrite( _ entries: [WriteEntry], constraints: [TransactionConstraintEntry], @@ -281,64 +348,11 @@ extension GenericAWSDynamoDBCompositePrimaryKeyTable { let keys = entries.map(\.compositePrimaryKey) + constraints.map(\.compositePrimaryKey) - var isTransactionConflict = false - let reasons = try zip(cancellationReasons, keys).compactMap { - cancellationReason, - entryKey -> DynamoDBTableError? in - let key: CompositePrimaryKey? = - if let item = cancellationReason.item { - try DynamoDBDecoder().decode(.m(item)) - } else { - nil - } - - let partitionKey = key?.partitionKey ?? entryKey.partitionKey - let sortKey = key?.sortKey ?? entryKey.sortKey - - // https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ExecuteTransaction.html - switch cancellationReason.code { - case "None": - return nil - case "ConditionalCheckFailed": - return DynamoDBTableError.conditionalCheckFailed( - partitionKey: partitionKey, - sortKey: sortKey, - message: cancellationReason.message - ) - case "DuplicateItem": - return DynamoDBTableError.duplicateItem( - partitionKey: partitionKey, - sortKey: sortKey, - message: cancellationReason.message - ) - case "ItemCollectionSizeLimitExceeded": - return DynamoDBTableError.itemCollectionSizeLimitExceeded( - attemptedSize: entryCount, - maximumSize: AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement - ) - case "TransactionConflict": - isTransactionConflict = true - - return DynamoDBTableError.transactionConflict(message: cancellationReason.message) - case "ProvisionedThroughputExceeded": - return DynamoDBTableError.provisionedThroughputExceeded(message: cancellationReason.message) - case "ThrottlingError": - return DynamoDBTableError.throttling(message: cancellationReason.message) - case "ValidationError": - return DynamoDBTableError.validation( - partitionKey: partitionKey, - sortKey: sortKey, - message: cancellationReason.message - ) - default: - return DynamoDBTableError.unknown( - code: cancellationReason.code, - partitionKey: partitionKey, - sortKey: sortKey, - message: cancellationReason.message - ) - } - } + let (reasons, isTransactionConflict) = try getErrorReasons( + cancellationReasons: cancellationReasons, + keys: keys, + entryCount: entryCount + ) if isTransactionConflict, retriesRemaining > 0 { return try await retryTransactWrite( @@ -413,64 +427,11 @@ extension GenericAWSDynamoDBCompositePrimaryKeyTable { throw DynamoDBTableError.transactionCanceled(reasons: []) } - var isTransactionConflict = false - let reasons = try zip(cancellationReasons, inputKeys).compactMap { - cancellationReason, - entryKey -> DynamoDBTableError? in - let key: CompositePrimaryKey? = - if let item = cancellationReason.item { - try DynamoDBDecoder().decode(.m(item)) - } else { - nil - } - - let partitionKey = key?.partitionKey ?? entryKey.partitionKey - let sortKey = key?.sortKey ?? entryKey.sortKey - - // https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ExecuteTransaction.html - switch cancellationReason.code { - case "None": - return nil - case "ConditionalCheckFailed": - return DynamoDBTableError.conditionalCheckFailed( - partitionKey: partitionKey, - sortKey: sortKey, - message: cancellationReason.message - ) - case "DuplicateItem": - return DynamoDBTableError.duplicateItem( - partitionKey: partitionKey, - sortKey: sortKey, - message: cancellationReason.message - ) - case "ItemCollectionSizeLimitExceeded": - return DynamoDBTableError.itemCollectionSizeLimitExceeded( - attemptedSize: inputKeys.count, - maximumSize: AWSDynamoDBLimits.maximumUpdatesPerTransactionStatement - ) - case "TransactionConflict": - isTransactionConflict = true - - return DynamoDBTableError.transactionConflict(message: cancellationReason.message) - case "ProvisionedThroughputExceeded": - return DynamoDBTableError.provisionedThroughputExceeded(message: cancellationReason.message) - case "ThrottlingError": - return DynamoDBTableError.throttling(message: cancellationReason.message) - case "ValidationError": - return DynamoDBTableError.validation( - partitionKey: partitionKey, - sortKey: sortKey, - message: cancellationReason.message - ) - default: - return DynamoDBTableError.unknown( - code: cancellationReason.code, - partitionKey: partitionKey, - sortKey: sortKey, - message: cancellationReason.message - ) - } - } + let (reasons, isTransactionConflict) = try getErrorReasons( + cancellationReasons: cancellationReasons, + keys: inputKeys, + entryCount: inputKeys.count + ) if isTransactionConflict, retriesRemaining > 0 { return try await retryPolymorphicTransactWrite( diff --git a/Sources/DynamoDBTables/CustomRowTypeIdentifier.swift b/Sources/DynamoDBTables/CustomRowTypeIdentifier.swift index eeb8d9e..b997bdf 100644 --- a/Sources/DynamoDBTables/CustomRowTypeIdentifier.swift +++ b/Sources/DynamoDBTables/CustomRowTypeIdentifier.swift @@ -31,7 +31,7 @@ public protocol CustomRowTypeIdentifier { } func getTypeRowIdentifier(type: Any.Type) -> String { - let typeRowIdentifier: String// if this type has a custom row identity + let typeRowIdentifier: String // if this type has a custom row identity = if let customAttributesTypeType = type as? CustomRowTypeIdentifier.Type, let identifier = customAttributesTypeType.rowTypeIdentifier diff --git a/lint b/lint new file mode 100644 index 0000000..85e2495 --- /dev/null +++ b/lint @@ -0,0 +1,361 @@ +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/RowWithIndex.swift:56:1: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transact.swift:29:57: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transact.swift:57:9: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TransactionConstraintEntryType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transact.swift:40:46: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transact.swift:41:54: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transact.swift:43:44: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transact.swift:78:44: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transact.swift:97:44: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transact.swift:109:46: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transact.swift:110:44: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transact.swift:36:9: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transact.swift:66:9: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeysProjection+DynamoDBKeysProjectionAsync.swift:6:1: warning: Line Length Violation: Line should be 150 characters or less: currently 163 characters (line_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeysProjection+DynamoDBKeysProjectionAsync.swift:143:54: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeysProjection+DynamoDBKeysProjectionAsync.swift:38:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeysProjection+DynamoDBKeysProjectionAsync.swift:53:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeysProjection+DynamoDBKeysProjectionAsync.swift:87:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeysProjection+DynamoDBKeysProjectionAsync.swift:105:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/QueryInput+forSortKeyCondition.swift:31:5: warning: Function Body Length Violation: Function body should span 50 lines or less excluding comments and whitespace: currently spans 55 lines (function_body_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/QueryInput+forSortKeyCondition.swift:31:5: warning: Function Parameter Count Violation: Function should have 5 parameters or less: it currently has 8 (function_parameter_count) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/QueryInput+forSortKeyCondition.swift:42:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/QueryInput+forSortKeyCondition.swift:81:59: warning: Trailing Comma Violation: Collection literals should not have trailing commas. (trailing_comma) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InternalKeyedDecodingContainer.swift:131:34: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InternalKeyedDecodingContainer.swift:132:20: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InternalKeyedDecodingContainer.swift:133:17: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InternalKeyedDecodingContainer.swift:114:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+deleteItems.swift:44:38: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+deleteItems.swift:72:47: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+deleteItems.swift:94:70: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+deleteItems.swift:100:48: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+deleteItems.swift:117:71: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+deleteItems.swift:123:48: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+deleteItems.swift:66:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeysProjectionStore.swift:37:29: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeysProjectionStore.swift:48:85: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeysProjectionStore.swift:45:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeysProjectionStore.swift:114:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeysProjectionStore.swift:132:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeysProjectionStore.swift:33:7: warning: Type Name Violation: Type name should be between 3 and 40 characters long: 'InMemoryDynamoDBCompositePrimaryKeysProjectionStore' (type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableStore.swift:1:43: warning: Superfluous Disable Command Violation: SwiftLint rule 'cyclomatic_complexity' did not trigger a violation in the disabled region. Please remove the disable command. (superfluous_disable_command) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableStore.swift:33:7: warning: Type Name Violation: Type name should be between 3 and 40 characters long: 'InMemoryDynamoDBCompositePrimaryKeyTableStore' (type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeysProjection.swift:1:43: warning: Superfluous Disable Command Violation: SwiftLint rule 'cyclomatic_complexity' did not trigger a violation in the disabled region. Please remove the disable command. (superfluous_disable_command) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeysProjection.swift:43:9: warning: Implicit Getter Violation: Computed read-only properties should avoid using the get keyword. (implicit_getter) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeysProjection.swift:53:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeysProjection.swift:64:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeysProjection.swift:81:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeysProjection.swift:31:15: warning: Type Name Violation: Type name should be between 3 and 40 characters long: 'InMemoryDynamoDBCompositePrimaryKeysProjection' (type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InternalUnkeyedEncodingContainer.swift:132:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift:529:1: warning: File Line Length Violation: File should contain 400 lines or less: currently contains 529 (file_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift:66:17: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift:110:17: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift:336:44: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift:377:49: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift:414:45: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift:475:59: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift:65:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift:109:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift:176:17: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift:225:17: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift:335:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift:376:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift:413:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift:474:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+transform.swift:79:8: warning: Type Name Violation: Type name should be between 3 and 40 characters long: 'InMemoryPolymorphicTransactionConstraintTransform' (type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:286:17: warning: Closure Parameter Position Violation: Closure parameters should be on the same line as opening brace. (closure_parameter_position) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:287:17: warning: Closure Parameter Position Violation: Closure parameters should be on the same line as opening brace. (closure_parameter_position) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:418:17: warning: Closure Parameter Position Violation: Closure parameters should be on the same line as opening brace. (closure_parameter_position) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:419:17: warning: Closure Parameter Position Violation: Closure parameters should be on the same line as opening brace. (closure_parameter_position) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:258:13: warning: Cyclomatic Complexity Violation: Function should have complexity 10 or less: currently complexity equals 18 (cyclomatic_complexity) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:394:13: warning: Cyclomatic Complexity Violation: Function should have complexity 10 or less: currently complexity equals 18 (cyclomatic_complexity) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:696:1: warning: File Line Length Violation: File should contain 400 lines or less: currently contains 696 (file_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:258:13: error: Function Body Length Violation: Function body should span 50 lines or less excluding comments and whitespace: currently spans 80 lines (function_body_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:394:13: error: Function Body Length Violation: Function body should span 50 lines or less excluding comments and whitespace: currently spans 78 lines (function_body_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:142:66: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:208:57: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:239:9: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TransactionConstraintEntryType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:258:58: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:377:63: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:650:75: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:151:46: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:157:58: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:181:46: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:188:58: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:285:72: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:417:77: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:542:41: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:562:73: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:568:48: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:589:41: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:631:73: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:637:48: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:655:44: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:42:1: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:57:1: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:40:16: warning: Type Name Violation: Type name should be between 3 and 40 characters long: 'AWSDynamoDBPolymorphicWriteEntryTransform' (type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+updateItems.swift:55:16: warning: Type Name Violation: Type name should be between 3 and 40 characters long: 'AWSDynamoDBPolymorphicTransactionConstraintTransform' (type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBClientTypes_AttributeValue+Codable.swift:102:40: warning: Redundant Optional Initialization Violation: Initializing an optional variable with nil is redundant. (redundant_optional_initialization) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBClientTypes_AttributeValue+Codable.swift:116:40: warning: Redundant Optional Initialization Violation: Initializing an optional variable with nil is redundant. (redundant_optional_initialization) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBClientTypes_AttributeValue+Codable.swift:130:43: warning: Redundant Optional Initialization Violation: Initializing an optional variable with nil is redundant. (redundant_optional_initialization) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBClientTypes_AttributeValue+Codable.swift:147:75: warning: Redundant Optional Initialization Violation: Initializing an optional variable with nil is redundant. (redundant_optional_initialization) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBClientTypes_AttributeValue+Codable.swift:161:61: warning: Redundant Optional Initialization Violation: Initializing an optional variable with nil is redundant. (redundant_optional_initialization) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/TypedDatabaseItemWithTimeToLive+RowWithItemVersionProtocol.swift:45:9: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTableHistoricalItemExtensions.swift:37:71: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTableHistoricalItemExtensions.swift:45:71: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTableHistoricalItemExtensions.swift:66:72: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTableHistoricalItemExtensions.swift:136:84: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTableHistoricalItemExtensions.swift:153:93: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyUpdateItem.swift:56:13: warning: Colon Violation: Colons should be next to the identifier when specifying a type and next to the key in dictionary literals. (colon) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyUpdateItem.swift:49:9: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyUpdateItem.swift:86:9: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyUpdateItem.swift:104:9: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyInTransaction.swift:73:13: warning: Colon Violation: Colons should be next to the identifier when specifying a type and next to the key in dictionary literals. (colon) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyInTransaction.swift:54:57: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyInTransaction.swift:148:9: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TransactionConstraintEntryType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyInTransaction.swift:6:1: warning: Line Length Violation: Line should be 150 characters or less: currently 153 characters (line_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyInTransaction.swift:78:53: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyInTransaction.swift:172:53: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyInTransaction.swift:65:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyInTransaction.swift:121:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+conditionallyInTransaction.swift:161:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+clobberVersionedItemWithHistoricalRow.swift:50:9: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+clobberVersionedItemWithHistoricalRow.swift:6:1: warning: Line Length Violation: Line should be 150 characters or less: currently 164 characters (line_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+clobberVersionedItemWithHistoricalRow.swift:65:9: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable+clobberVersionedItemWithHistoricalRow.swift:90:9: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift:40:8: warning: Class Delegate Protocol Violation: Delegate protocols should be class-only so they can be weakly referenced. (class_delegate_protocol) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift:1:43: warning: Superfluous Disable Command Violation: SwiftLint rule 'cyclomatic_complexity' did not trigger a violation in the disabled region. Please remove the disable command. (superfluous_disable_command) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift:108:54: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift:123:51: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift:65:9: warning: Implicit Getter Violation: Computed read-only properties should avoid using the get keyword. (implicit_getter) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift:82:44: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift:91:40: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift:114:44: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift:140:44: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift:147:29: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift:146:44: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift:154:49: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift:156:41: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift:155:44: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift:164:44: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift:127:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable.swift:51:12: warning: Weak Delegate Violation: Delegates should be weak to avoid reference cycles. (weak_delegate) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InternalSingleValueEncodingContainer.swift:173:42: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InternalSingleValueEncodingContainer.swift:177:48: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:1:60: warning: Superfluous Disable Command Violation: SwiftLint rule 'cyclomatic_complexity' did not trigger a violation in the disabled region. Please remove the disable command. (superfluous_disable_command) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:479:1: warning: File Line Length Violation: File should contain 400 lines or less: currently contains 479 (file_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:77:54: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:93:57: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:106:9: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TransactionConstraintEntryType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:140:51: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:342:52: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:352:49: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:379:49: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:417:51: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:445:51: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:62:1: warning: Line Length Violation: Line should be 150 characters or less: currently 155 characters (line_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:120:39: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:169:36: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:177:45: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:144:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:152:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:190:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:219:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:253:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:316:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:348:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:357:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:389:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:454:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTableWithIndex.swift:37:15: warning: Type Name Violation: Type name should be between 3 and 40 characters long: 'InMemoryDynamoDBCompositePrimaryKeyTableWithIndex' (type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable.swift:84:5: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable.swift:129:47: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable.swift:152:50: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable.swift:159:9: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TransactionConstraintEntryType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable.swift:178:44: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable.swift:289:45: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable.swift:302:42: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable.swift:313:42: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable.swift:331:44: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable.swift:344:44: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable.swift:184:1: warning: Line Length Violation: Line should be 150 characters or less: currently 162 characters (line_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyTable.swift:287:1: warning: Line Length Violation: Line should be 150 characters or less: currently 162 characters (line_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+DynamoDBTableAsync.swift:50:54: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+DynamoDBTableAsync.swift:59:51: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+DynamoDBTableAsync.swift:252:49: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+DynamoDBTableAsync.swift:266:57: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+DynamoDBTableAsync.swift:298:49: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+DynamoDBTableAsync.swift:213:54: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+DynamoDBTableAsync.swift:346:54: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+DynamoDBTableAsync.swift:63:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+DynamoDBTableAsync.swift:110:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+DynamoDBTableAsync.swift:157:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+DynamoDBTableAsync.swift:175:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+DynamoDBTableAsync.swift:257:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+DynamoDBTableAsync.swift:308:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/PolymorphicWriteEntry.swift:91:15: warning: Colon Violation: Colons should be next to the identifier when specifying a type and next to the key in dictionary literals. (colon) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/PolymorphicWriteEntry.swift:67:5: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/PolymorphicWriteEntry.swift:122:5: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'WriteEntryTransformType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/PolymorphicWriteEntry.swift:123:5: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'WriteTransactionConstraintType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/PolymorphicWriteEntry.swift:93:1: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/PolymorphicWriteEntry.swift:138:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/PolymorphicWriteEntry.swift:146:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/PolymorphicWriteEntry.swift:91:15: warning: Type Name Violation: Type name should be between 3 and 40 characters long: 'EmptyPolymorphicTransactionConstraintEntry' (type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+getItems.swift:48:9: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+getItems.swift:137:52: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+getItems.swift:125:1: warning: Line Length Violation: Line should be 150 characters or less: currently 161 characters (line_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+getItems.swift:51:9: warning: Nesting Violation: Types should be nested at most 1 level deep (nesting) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+getItems.swift:82:47: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+getItems.swift:81:42: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+getItems.swift:146:55: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+getItems.swift:143:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InternalUnkeyedDecodingContainer.swift:128:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+polymorphicGetItems.swift:143:13: warning: Closure Parameter Position Violation: Closure parameters should be on the same line as opening brace. (closure_parameter_position) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+polymorphicGetItems.swift:123:1: warning: Line Length Violation: Line should be 150 characters or less: currently 161 characters (line_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+polymorphicGetItems.swift:49:9: warning: Nesting Violation: Types should be nested at most 1 level deep (nesting) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+polymorphicGetItems.swift:78:47: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+polymorphicGetItems.swift:77:42: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+polymorphicGetItems.swift:142:55: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+polymorphicGetItems.swift:139:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/Sequence+concurrency.swift:320:24: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift:32:13: warning: Colon Violation: Colons should be next to the identifier when specifying a type and next to the key in dictionary literals. (colon) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift:84:12: warning: Cyclomatic Complexity Violation: Function should have complexity 10 or less: currently complexity equals 17 (cyclomatic_complexity) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift:209:12: warning: Cyclomatic Complexity Violation: Function should have complexity 10 or less: currently complexity equals 18 (cyclomatic_complexity) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift:23:52: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift:209:49: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift:284:49: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift:37:26: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift:29:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift:48:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift:70:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift:89:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift:152:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift:170:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift:214:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift:265:17: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+query.swift:294:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyGSILogic.swift:69:9: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyGSILogic.swift:48:1: warning: Line Length Violation: Line should be 150 characters or less: currently 153 characters (line_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyGSILogic.swift:56:1: warning: Line Length Violation: Line should be 150 characters or less: currently 154 characters (line_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBCompositePrimaryKeyGSILogic.swift:64:1: warning: Line Length Violation: Line should be 150 characters or less: currently 152 characters (line_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/TypedDatabaseItemWithTimeToLive.swift:47:5: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/TypedDatabaseItemWithTimeToLive.swift:66:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/TypedDatabaseItemWithTimeToLive.swift:81:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/PolymorphicOperationReturnType.swift:106:13: warning: Colon Violation: Colons should be next to the identifier when specifying a type and next to the key in dictionary literals. (colon) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/PolymorphicOperationReturnType.swift:47:5: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/PolymorphicOperationReturnType.swift:41:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/CompositePrimaryKey.swift:74:1: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:220:13: warning: Closure Parameter Position Violation: Closure parameters should be on the same line as opening brace. (closure_parameter_position) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:305:5: warning: Cyclomatic Complexity Violation: Function should have complexity 10 or less: currently complexity equals 13 (cyclomatic_complexity) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:407:1: warning: File Line Length Violation: File should contain 400 lines or less: currently contains 407 (file_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:147:51: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:209:51: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:268:59: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:108:54: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:135:69: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:194:54: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:219:69: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:383:31: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:70:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:131:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:156:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:215:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:241:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:275:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:312:9: warning: Switch and Case Statement Alignment Violation: Case statements should vertically align with their enclosing switch statement. (switch_case_alignment) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:314:9: warning: Switch and Case Statement Alignment Violation: Case statements should vertically align with their enclosing switch statement. (switch_case_alignment) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:320:9: warning: Switch and Case Statement Alignment Violation: Case statements should vertically align with their enclosing switch statement. (switch_case_alignment) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:326:9: warning: Switch and Case Statement Alignment Violation: Case statements should vertically align with their enclosing switch statement. (switch_case_alignment) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:328:9: warning: Switch and Case Statement Alignment Violation: Case statements should vertically align with their enclosing switch statement. (switch_case_alignment) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:333:9: warning: Switch and Case Statement Alignment Violation: Case statements should vertically align with their enclosing switch statement. (switch_case_alignment) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:335:9: warning: Switch and Case Statement Alignment Violation: Case statements should vertically align with their enclosing switch statement. (switch_case_alignment) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:337:9: warning: Switch and Case Statement Alignment Violation: Case statements should vertically align with their enclosing switch statement. (switch_case_alignment) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:343:9: warning: Switch and Case Statement Alignment Violation: Case statements should vertically align with their enclosing switch statement. (switch_case_alignment) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:345:9: warning: Switch and Case Statement Alignment Violation: Case statements should vertically align with their enclosing switch statement. (switch_case_alignment) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:347:9: warning: Switch and Case Statement Alignment Violation: Case statements should vertically align with their enclosing switch statement. (switch_case_alignment) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+execute.swift:353:9: warning: Switch and Case Statement Alignment Violation: Case statements should vertically align with their enclosing switch statement. (switch_case_alignment) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeysProjection.swift:34:15: warning: Type Name Violation: Type name should be between 3 and 40 characters long: 'AWSDynamoDBCompositePrimaryKeysProjection' (type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InternalKeyedEncodingContainer.swift:175:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/CustomRowTypeIdentifier.swift:38:9: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable.swift:157:58: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable.swift:234:58: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable.swift:203:32: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable.swift:267:59: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable.swift:89:1: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable.swift:141:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable.swift:202:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable.swift:145:104: warning: Trailing Comma Violation: Collection literals should not have trailing commas. (trailing_comma) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable.swift:166:40: warning: Trailing Comma Violation: Collection literals should not have trailing commas. (trailing_comma) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable.swift:170:99: warning: Trailing Comma Violation: Collection literals should not have trailing commas. (trailing_comma) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable.swift:246:40: warning: Trailing Comma Violation: Collection literals should not have trailing commas. (trailing_comma) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable.swift:250:99: warning: Trailing Comma Violation: Collection literals should not have trailing commas. (trailing_comma) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable.swift:87:15: warning: Type Name Violation: Type name should be between 3 and 40 characters long: 'GenericAWSDynamoDBCompositePrimaryKeyTable' (type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+bulkUpdateSupport.swift:235:13: warning: Colon Violation: Colons should be next to the identifier when specifying a type and next to the key in dictionary literals. (colon) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+bulkUpdateSupport.swift:153:13: warning: Cyclomatic Complexity Violation: Function should have complexity 10 or less: currently complexity equals 14 (cyclomatic_complexity) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+bulkUpdateSupport.swift:285:5: warning: Cyclomatic Complexity Violation: Function should have complexity 10 or less: currently complexity equals 11 (cyclomatic_complexity) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+bulkUpdateSupport.swift:66:56: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+bulkUpdateSupport.swift:143:46: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+bulkUpdateSupport.swift:77:48: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+bulkUpdateSupport.swift:210:42: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+bulkUpdateSupport.swift:250:39: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+bulkUpdateSupport.swift:313:58: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+bulkUpdateSupport.swift:322:58: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/AWSDynamoDBCompositePrimaryKeyTable+bulkUpdateSupport.swift:52:1: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDataRepresentations.swift:47:51: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDataRepresentations.swift:49:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDataRepresentations.swift:73:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDataRepresentations.swift:100:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDataRepresentations.swift:106:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDataRepresentations.swift:125:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDataRepresentations.swift:133:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/DynamoDBDecoder.swift:42:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+execute.swift:71:13: warning: Closure Parameter Position Violation: Closure parameters should be on the same line as opening brace. (closure_parameter_position) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+execute.swift:110:13: warning: Closure Parameter Position Violation: Closure parameters should be on the same line as opening brace. (closure_parameter_position) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+execute.swift:60:51: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+execute.swift:94:51: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+execute.swift:33:58: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+execute.swift:53:58: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+execute.swift:70:118: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+execute.swift:109:118: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+execute.swift:47:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/InMemoryDynamoDBCompositePrimaryKeyTable+execute.swift:103:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTablesMacros/PolymorphicOperationReturnTypeMacro.swift:39:12: warning: Function Body Length Violation: Function body should span 50 lines or less excluding comments and whitespace: currently spans 52 lines (function_body_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTablesMacros/PolymorphicOperationReturnTypeMacro.swift:79:71: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTablesMacros/PolymorphicOperationReturnTypeMacro.swift:61:9: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTablesMacros/PolymorphicOperationReturnTypeMacro.swift:70:13: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTablesMacros/PolymorphicOperationReturnTypeMacro.swift:143:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTablesMacros/PolymorphicOperationReturnTypeMacro.swift:67:53: warning: Reduce Boolean Violation: Use `contains` instead (reduce_boolean) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTablesMacros/Plugin.swift:26:49: warning: Trailing Comma Violation: Collection literals should not have trailing commas. (trailing_comma) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTablesMacros/PolymorphicTransactionConstraintEntryMacro.swift:21:8: warning: Type Name Violation: Type name should be between 3 and 40 characters long: 'PolymorphicTransactionConstraintEntryMacroAttributes' (type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTablesMacros/PolymorphicTransactionConstraintEntryMacro.swift:31:13: warning: Type Name Violation: Type name should be between 3 and 40 characters long: 'PolymorphicTransactionConstraintEntryMacro' (type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:96:54: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:121:57: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:134:9: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TransactionConstraintEntryType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:166:51: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:282:52: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:293:51: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:308:51: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:327:49: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:340:49: warning: Generic Type Name Violation: Generic type name should be between 1 and 20 characters long: 'TimeToLiveAttributesType' (generic_type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:71:1: warning: Line Length Violation: Line should be 150 characters or less: currently 155 characters (line_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:148:39: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:39:1: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:170:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:179:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:206:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:221:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:239:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:256:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:272:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:288:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:299:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:317:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:332:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:350:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTables/SimulateConcurrencyDynamoDBCompositePrimaryKeyTable.swift:37:14: warning: Type Name Violation: Type name should be between 3 and 40 characters long: 'SimulateConcurrencyDynamoDBCompositePrimaryKeyTable' (type_name) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTablesMacros/BaseEntryMacro.swift:63:12: warning: Large Tuple Violation: Tuples should have at most 2 members. (large_tuple) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTablesMacros/BaseEntryMacro.swift:80:1: warning: Line Length Violation: Line should be 150 characters or less: currently 154 characters (line_length) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTablesMacros/BaseEntryMacro.swift:134:71: warning: No Space in Method Call Violation: Don't add a space between the method name and the parentheses. (no_space_in_method_call) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTablesMacros/BaseEntryMacro.swift:64:5: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTablesMacros/BaseEntryMacro.swift:125:13: warning: Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration. (opening_brace) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTablesMacros/BaseEntryMacro.swift:122:53: warning: Reduce Boolean Violation: Use `contains` instead (reduce_boolean) +/Users/simonpilkington/Packages/dynamo-db-tables/Sources/DynamoDBTablesMacros/BaseEntryMacro.swift:80:20: warning: Todo Violation: TODOs should be resolved (when made possible by the lang...). (todo)