I have used the devhr python lambda function as a model for my Swift Lambda function using Soto for AWS. The Lambda function will be triggered by an S3 event when an image is uploaded to the S3, and using the AWS Rekognition service to add labels (description of objects in the image) to a DynamoDB database. I have problems getting the rekFunction function correct, and I hope someone can advice how to make it work.
The Swift code I have made so far:
import AWSLambdaRuntime
import AWSLambdaEvents
import NIO
import Foundation
import SotoS3
import SotoRekognition
import SotoDynamoDB
struct Bucket: Decodable {
let name: String
}
struct Object: Decodable {
let key: String
}
struct MyS3: Decodable {
let bucket: Bucket
let object: Object
}
struct Record: Decodable {
let s3: MyS3
}
struct Input: Decodable {
let records: [Record]
}
struct Output: Encodable {
let result: String
}
struct MyHandler: EventLoopLambdaHandler {
typealias In = APIGateway.V2.Request
typealias Out = APIGateway.V2.Response
let minConfidence: Float = 50
let awsClient: AWSClient
init(context: Lambda.InitializationContext) {
self.awsClient = AWSClient(httpClientProvider: .createNewWithEventLoopGroup(context.eventLoop))
}
func shutdown(context: Lambda.ShutdownContext) -> EventLoopFuture<Void> {
let promise = context.eventLoop.makePromise(of: Void.self)
awsClient.shutdown { error in
if let error = error {
promise.fail(error)
} else {
promise.succeed(())
}
}
return context.eventLoop.makeSucceededFuture(())
}
func handle(context: Lambda.Context, event: In) -> EventLoopFuture<Out> {
guard let input: Input = try? event.bodyObject() else {
return context.eventLoop.makeSucceededFuture(APIGateway.V2.Response(with: APIError.requestError, statusCode: .badRequest))
}
for record in input.records {
let ourBucket = record.s3.bucket.name
let ourKey = record.s3.object.key
// For each message (photo) get the bucket name and key
rekFunction(bucket: ourBucket, key: ourKey)
}
let output = Output(result: "Finished!")
let apigatewayOutput = APIGateway.V2.Response(with: output, statusCode: .ok)
return context.eventLoop.makeSucceededFuture(apigatewayOutput)
}
func rekFunction(bucket: String, key: String) {
let safeKey = key.replacingOccurrences(of: "%3A", with: ":")
print("Currently processing the following image")
print("Bucket:", bucket, " key name:", safeKey)
var objectsDetected: [String] = []
var imageLabels = [ "image": safeKey ]
let s3Client = S3(client: awsClient, region: .euwest1)
let s3Object = Rekognition.S3Object(bucket: bucket, name: safeKey)
let image = Rekognition.Image(s3Object: s3Object)
let rekognitionClient = Rekognition(client: awsClient)
let detectLabelsRequest = Rekognition.DetectLabelsRequest(image: image, maxLabels: 10, minConfidence: minConfidence)
rekognitionClient.detectLabels(detectLabelsRequest)
.flatMap { detectLabelsResponse -> EventLoopFuture<Void> in
if let labels = detectLabelsResponse.labels {
// Add all of our labels into imageLabels by iterating over response['Labels']
for label in labels {
if let name = label.name {
objectsDetected.append(name)
let itemAtt = "object\(objectsDetected.count)"
// We now have our shiny new item ready to put into DynamoDB
imageLabels[itemAtt] = name
// Instantiate a table resource object of our environment variable
let imageLabelsTable = // Environment("TABLE") How can I read env vars?
let table = SotoDynamoDB.getTable(imageLabelsTable) // python: table = dynamodb.Table(imageLabelsTable)
// python: table.put_item(Item=imageLabels)
}
}
}
return ???
}
}
}
Lambda.run { MyHandler(context: $0) }
There are a number of issues to cover here.
MyLambda.In
typealias should be S3.Event
and given there isn't anything waiting on the results MyLambda.Out
should be Void
.Lambda.env("TABLE")
.struct RekEntry: Codable {
let key: String
let labels: [String]
}
let entry = RekEntry(
key: "\(bucket)/\(key)"
labels: labels
)
let putRequest = DynamoDB.PutItemCodableInput(
item: entry,
tableName: Lambda.env("TABLE")
)
return dynamoDB.putItem(putRequest)
rekFunction
needs to return an EventLoopFuture<Void>
as this is what you are returning from the Lambda handler. So you would map the result of the dynamoDB.putItem
in the previous point to Void.func rekFunction(bucket: String, key: String) -> EventLoopFuture<Void> {
...
return rekognitionClient.detectLabels(detectLabelsRequest)
.flatMap {
...
return dynamoDB.putItem(putRequest)
}
.map { _ in }
}
I hope that covers everything