diff --git a/lambda/swift/.gitignore b/lambda/swift/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/lambda/swift/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/lambda/swift/Package.resolved b/lambda/swift/Package.resolved new file mode 100644 index 0000000..d9734f7 --- /dev/null +++ b/lambda/swift/Package.resolved @@ -0,0 +1,276 @@ +{ + "originHash" : "a4fa19aabe80d81e0f0f95673729d47df2629fdde072463c34fb00c0244a2316", + "pins" : [ + { + "identity" : "aws-crt-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/awslabs/aws-crt-swift", + "state" : { + "revision" : "2f4f02595a25a43f9ca9acdc9f4d4a8ff6e5d3ac", + "version" : "0.54.0" + } + }, + { + "identity" : "aws-sdk-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/awslabs/aws-sdk-swift", + "state" : { + "revision" : "96c1dbb8eaa667c1feca7917a03d37a9cea4b868", + "version" : "1.5.61" + } + }, + { + "identity" : "grpc-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/grpc/grpc-swift.git", + "state" : { + "revision" : "a56a157218877ef3e9625f7e1f7b2cb7e46ead1b", + "version" : "1.26.1" + } + }, + { + "identity" : "opentelemetry-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/open-telemetry/opentelemetry-swift", + "state" : { + "revision" : "ef63c346d05f4fa7c9ca883f92631fd139eb2cfe", + "version" : "1.17.1" + } + }, + { + "identity" : "opentracing-objc", + "kind" : "remoteSourceControl", + "location" : "https://github.com/undefinedlabs/opentracing-objc", + "state" : { + "revision" : "18c1a35ca966236cee0c5a714a51a73ff33384c1", + "version" : "0.5.2" + } + }, + { + "identity" : "smithy-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/smithy-lang/smithy-swift", + "state" : { + "revision" : "a3576238801cd1c962d36b98d860e28af78b1160", + "version" : "0.164.0" + } + }, + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms.git", + "state" : { + "revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023", + "version" : "1.2.1" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "cdd0ef3755280949551dc26dee5de9ddeda89f54", + "version" : "1.6.2" + } + }, + { + "identity" : "swift-asn1", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-asn1.git", + "state" : { + "revision" : "f70225981241859eb4aa1a18a75531d26637c8cc", + "version" : "1.4.0" + } + }, + { + "identity" : "swift-async-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-async-algorithms.git", + "state" : { + "revision" : "042e1c4d9d19748c9c228f8d4ebc97bb1e339b0b", + "version" : "1.0.4" + } + }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-aws-lambda-events", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/swift-aws-lambda-events.git", + "state" : { + "revision" : "5aa3a6305864a2060b69f30cf3e24f4a2b5b16ba", + "version" : "1.2.1" + } + }, + { + "identity" : "swift-aws-lambda-runtime", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/swift-aws-lambda-runtime.git", + "state" : { + "revision" : "461c18af6f944f6324fcf21cd4ce8ac12f38d66b", + "version" : "2.1.0" + } + }, + { + "identity" : "swift-certificates", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-certificates.git", + "state" : { + "revision" : "4b092f15164144c24554e0a75e080a960c5190a6", + "version" : "1.14.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "95ba0316a9b733e92bb6b071255ff46263bbe7dc", + "version" : "3.15.1" + } + }, + { + "identity" : "swift-http-structured-headers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-structured-headers.git", + "state" : { + "revision" : "1625f271afb04375bf48737a5572613248d0e7a0", + "version" : "1.4.0" + } + }, + { + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types.git", + "state" : { + "revision" : "a0a57e949a8903563aba4615869310c0ebf14c03", + "version" : "1.4.0" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "ce592ae52f982c847a4efc0dd881cc9eb32d29f2", + "version" : "1.6.4" + } + }, + { + "identity" : "swift-metrics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-metrics.git", + "state" : { + "revision" : "0743a9364382629da3bf5677b46a2c4b1ce5d2a6", + "version" : "2.7.1" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "a18bddb0acf7a40d982b2f128ce73ce4ee31f352", + "version" : "2.86.2" + } + }, + { + "identity" : "swift-nio-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-extras.git", + "state" : { + "revision" : "a55c3dd3a81d035af8a20ce5718889c0dcab073d", + "version" : "1.29.0" + } + }, + { + "identity" : "swift-nio-http2", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-http2.git", + "state" : { + "revision" : "5e9e99ec96c53bc2c18ddd10c1e25a3cd97c55e5", + "version" : "1.38.0" + } + }, + { + "identity" : "swift-nio-ssl", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-ssl.git", + "state" : { + "revision" : "b2b043a8810ab6d51b3ff4df17f057d87ef1ec7c", + "version" : "2.34.1" + } + }, + { + "identity" : "swift-nio-transport-services", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-transport-services.git", + "state" : { + "revision" : "df6c28355051c72c884574a6c858bc54f7311ff9", + "version" : "1.25.2" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics.git", + "state" : { + "revision" : "0c0290ff6b24942dadb83a929ffaaa1481df04a2", + "version" : "1.1.1" + } + }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "c6fe6442e6a64250495669325044052e113e990c", + "version" : "1.32.0" + } + }, + { + "identity" : "swift-service-lifecycle", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/swift-service-lifecycle.git", + "state" : { + "revision" : "0fcc4c9c2d58dd98504c06f7308c86de775396ff", + "version" : "2.9.0" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "395a77f0aa927f0ff73941d7ac35f2b46d47c9db", + "version" : "1.6.3" + } + }, + { + "identity" : "thrift-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/undefinedlabs/Thrift-Swift", + "state" : { + "revision" : "18ff09e6b30e589ed38f90a1af23e193b8ecef8e", + "version" : "1.1.2" + } + } + ], + "version" : 3 +} diff --git a/lambda/swift/Package.swift b/lambda/swift/Package.swift new file mode 100644 index 0000000..906e9b0 --- /dev/null +++ b/lambda/swift/Package.swift @@ -0,0 +1,29 @@ +// swift-tools-version: 6.0 +import PackageDescription + +let package = Package( + name: "SwiftExample", + platforms: [.macOS(.v15)], + dependencies: [ + .package(url: "https://github.com/awslabs/aws-sdk-swift", from: "1.0.0"), + .package(url: "https://github.com/swift-server/swift-aws-lambda-events.git", from: "1.2.1"), + .package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", from: "2.0.0"), + .package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.0.0"), + ], + targets: [ + .executableTarget( + name: "SwiftExample", + dependencies: [ + .product(name: "AWSDynamoDB", package: "aws-sdk-swift"), + .product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"), + .product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"), + .product(name: "ServiceLifecycle", package: "swift-service-lifecycle"), + ] + ), + .testTarget( + name: "SwiftExampleTests", + dependencies: ["SwiftExample"], + resources: [.process("Resources")] + ) + ] +) diff --git a/lambda/swift/Sources/SwiftExample/Event.swift b/lambda/swift/Sources/SwiftExample/Event.swift new file mode 100644 index 0000000..1906648 --- /dev/null +++ b/lambda/swift/Sources/SwiftExample/Event.swift @@ -0,0 +1,26 @@ +import AWSDynamoDB + +struct Event { + let id: String + let emitterCode: Int + let action: String + let user: User + + func asItem() -> [String: DynamoDBClientTypes.AttributeValue] { + [ + CodingKeys.id.rawValue: .s(id), + CodingKeys.emitterCode.rawValue: .n(String(emitterCode)), + CodingKeys.action.rawValue: .s(action), + CodingKeys.user.rawValue: .m(user.asItem()) + ] + } +} + +extension Event: Codable { + enum CodingKeys: String, CodingKey { + case id = "eventId" + case emitterCode + case action + case user + } +} diff --git a/lambda/swift/Sources/SwiftExample/User.swift b/lambda/swift/Sources/SwiftExample/User.swift new file mode 100644 index 0000000..9275a6d --- /dev/null +++ b/lambda/swift/Sources/SwiftExample/User.swift @@ -0,0 +1,25 @@ +import AWSDynamoDB + +struct User { + let id: String + let sessionID: String + let deviceID: String + + func asItem() -> [String: DynamoDBClientTypes.AttributeValue] { + [ + CodingKeys.id.rawValue: .s(id), + CodingKeys.sessionID.rawValue: .s(sessionID), + CodingKeys.deviceID.rawValue: .s(deviceID), + ] + } + + +} + +extension User: Codable { + enum CodingKeys: String, CodingKey { + case id + case sessionID = "sessionId" + case deviceID = "deviceId" + } +} diff --git a/lambda/swift/Sources/SwiftExample/main.swift b/lambda/swift/Sources/SwiftExample/main.swift new file mode 100644 index 0000000..19331ab --- /dev/null +++ b/lambda/swift/Sources/SwiftExample/main.swift @@ -0,0 +1,40 @@ +@preconcurrency import AWSDynamoDB +@preconcurrency import AWSSDKIdentity +import AWSLambdaRuntime +import AWSLambdaEvents +import Foundation + +let dbConfiguration = try DynamoDBClient.DynamoDBClientConfiguration( + awsCredentialIdentityResolver: EnvironmentAWSCredentialIdentityResolver(), +) +let ddbClient = DynamoDBClient(config: dbConfiguration) +let jsonDecoder = JSONDecoder() +let tableName = ProcessInfo.processInfo.environment["TABLE_NAME"] + +let runtime = LambdaRuntime { (input: SQSEvent, context: LambdaContext) async throws in + await withThrowingTaskGroup { taskGroup in + for message in input.records { + taskGroup.addTask { + // todo: validate json against schema + // jsonSchema.validate(message.body) + + let event = try message.decodeBody( + SwiftExample.Event.self, + using: jsonDecoder + ) + + let input = PutItemInput( + item: event.asItem(), + tableName: tableName + ) + + return try await ddbClient.putItem(input: input) + } + } + } + + return input.records.count +} + + +try await runtime.run() diff --git a/lambda/swift/Tests/SwiftExampleTests/EventTests.swift b/lambda/swift/Tests/SwiftExampleTests/EventTests.swift new file mode 100644 index 0000000..fa34a72 --- /dev/null +++ b/lambda/swift/Tests/SwiftExampleTests/EventTests.swift @@ -0,0 +1,49 @@ +import Foundation +@testable import SwiftExample +import Testing + +struct EventTests { + @Test("Successfully decodes an event from JSON schema") + func decodeUser() throws { + let eventJSONURL = try #require( + Bundle.module.url(forResource: "example", withExtension: "json") + ) + let eventJSON = try Data(contentsOf: eventJSONURL) + + let event = try JSONDecoder().decode(Event.self, from: eventJSON) + #expect(event.id == "event_id") + #expect(event.emitterCode == 50) + #expect(event.action == "sign_in") + + #expect(event.user.id == "id") + #expect(event.user.sessionID == "session_id") + #expect(event.user.deviceID == "device_id") + } + + @Test("Successfully transforms event into a DynamoDB item") + func asItem() throws { + let event = Event( + id: "mock_id", + emitterCode: 300, + action: "action_name", + user: User( + id: "user_id", + sessionID: "session_id", + deviceID: "device_id" + ) + ) + + #expect(event.asItem() == [ + "eventId" : .s("mock_id"), + "emitterCode": .n("300"), + "action": .s("action_name"), + "user": .m([ + "id" : .s("user_id"), + "sessionId" : .s("session_id"), + "deviceId" : .s("device_id") + ]) + ]) + + + } +} diff --git a/lambda/swift/Tests/SwiftExampleTests/Resources/example.json b/lambda/swift/Tests/SwiftExampleTests/Resources/example.json new file mode 120000 index 0000000..dc78e28 --- /dev/null +++ b/lambda/swift/Tests/SwiftExampleTests/Resources/example.json @@ -0,0 +1 @@ +../../../../../schema/example.json \ No newline at end of file