diff --git a/swift/example_code/lambda/using-lambda-runtime/Sources/lambda.swift b/swift/example_code/lambda/using-lambda-runtime/Sources/lambda.swift index 4ee32315352..3d4a5a800b1 100644 --- a/swift/example_code/lambda/using-lambda-runtime/Sources/lambda.swift +++ b/swift/example_code/lambda/using-lambda-runtime/Sources/lambda.swift @@ -4,12 +4,11 @@ // snippet-start:[lambda.swift.function.imports] import Foundation import AWSLambdaRuntime -@preconcurrency import AWSS3 +import AWSS3 import protocol AWSClientRuntime.AWSServiceError import enum Smithy.ByteStream // snippet-end:[lambda.swift.function.imports] - // snippet-start:[lambda.swift.function.types] // snippet-start:[lambda.swift.function.struct.request] /// Represents the contents of the requests being received from the client. @@ -20,7 +19,6 @@ struct Request: Decodable, Sendable { let body: String } // snippet-end:[lambda.swift.function.struct.request] - // snippet-start:[lambda.swift.function.struct.response] /// The contents of the response sent back to the client. This must be /// `Encodable`. @@ -31,92 +29,131 @@ struct Response: Encodable, Sendable { let body: String } // snippet-end:[lambda.swift.function.struct.response] - // snippet-start:[lambda.swift.function.errors] /// The errors that the Lambda function can return. enum S3ExampleLambdaErrors: Error { /// A required environment variable is missing. The missing variable is /// specified. case noEnvironmentVariable(String) + /// The Amazon Simple Storage Service (S3) client couldn't be created. + case noS3Client } // snippet-end:[lambda.swift.function.errors] // snippet-end:[lambda.swift.function.types] -let currentRegion = ProcessInfo.processInfo.environment["AWS_REGION"] ?? "us-east-1" -let s3Client = try S3Client(region: currentRegion) - -// snippet-start:[lambda.swift.function.putobject] -/// Create a new object on Amazon S3 whose name is based on the current -/// timestamp, containing the text specified. -/// -/// - Parameters: -/// - body: The text to store in the new S3 object. -/// - bucketName: The name of the Amazon S3 bucket to put the new object -/// into. -/// -/// - Throws: Errors from `PutObject`. -/// -/// - Returns: The name of the new Amazon S3 object that contains the -/// specified body text. -func putObject(body: String, bucketName: String) async throws -> String { - // Generate an almost certainly unique object name based on the current - // timestamp. - - let objectName = "\(Int(Date().timeIntervalSince1970*1_000_000)).txt" - - // Create a Smithy `ByteStream` that represents the string to write into - // the bucket. - - let inputStream = Smithy.ByteStream.data(body.data(using: .utf8)) - - // Store the text into an object in the Amazon S3 bucket. +// snippet-start:[lambda.swift.function.handler] +/// A Swift AWS Lambda Runtime `LambdaHandler` lets you both perform needed +/// initialization and handle AWS Lambda requests. There are other handler +/// protocols available for other use cases. +@main +struct S3ExampleLambda: LambdaHandler { + let s3Client: S3Client? + + // snippet-start:[lambda.swift.function.handler.init] + /// Initialize the AWS Lambda runtime. + /// + /// ^ The logger is a standard Swift logger. You can control the verbosity + /// by setting the `LOG_LEVEL` environment variable. + init(context: LambdaInitializationContext) async throws { + // Display the `LOG_LEVEL` configuration for this process. + context.logger.info( + "Log Level env var : \(ProcessInfo.processInfo.environment["LOG_LEVEL"] ?? "info" )" + ) - _ = try await s3Client.putObject( - input: PutObjectInput( + // Initialize the Amazon S3 client. This single client is used for every + // request. + let currentRegion = ProcessInfo.processInfo.environment["AWS_REGION"] ?? "us-east-1" + self.s3Client = try? S3Client(region: currentRegion) + } + // snippet-end:[lambda.swift.function.handler.init] + + // snippet-start:[lambda.swift.function.handler.putobject] + /// Write the specified text into a given Amazon S3 bucket. The object's + /// name is based on the current time. + /// + /// - Parameters: + /// - s3Client: The `S3Client` to use when sending the object to the + /// bucket. + /// - bucketName: The name of the Amazon S3 bucket to put the object + /// into. + /// - body: The string to write into the new object. + /// + /// - Returns: A string indicating the name of the file created in the AWS + /// S3 bucket. + private func putObject(client: S3Client, + bucketName: String, + body: String) async throws -> String { + // Generate an almost certainly unique object name based on the current + // timestamp. + let objectName = "\(Int(Date().timeIntervalSince1970*1_000_000)).txt" + + // Create a Smithy `ByteStream` that represents the string to write into + // the bucket. + let inputStream = Smithy.ByteStream.data(body.data(using: .utf8)) + + // Store the text into an object in the Amazon S3 bucket. + let putObjectRequest = PutObjectInput( body: inputStream, bucket: bucketName, key: objectName ) - ) - - // Return the name of the file + let _ = try await client.putObject(input: putObjectRequest) - return objectName -} -// snippet-end:[lambda.swift.function.putobject] - -// snippet-start:[lambda.swift.function.runtime] -let runtime = LambdaRuntime { - (event: Request, context: LambdaContext) async throws -> Response in - - var responseMessage: String - - // Get the name of the bucket to write the new object into from the - // environment variable `BUCKET_NAME`. - guard let bucketName = ProcessInfo.processInfo.environment["BUCKET_NAME"] else { - context.logger.error("Set the environment variable BUCKET_NAME to the name of the S3 bucket to write files to.") - throw S3ExampleLambdaErrors.noEnvironmentVariable("BUCKET_NAME") + // Return the name of the file. + return objectName } - - do { - let filename = try await putObject(body: event.body, bucketName: bucketName) - - // Generate the response text and update the log. - responseMessage = "The Lambda function has successfully stored your data in S3 with name '\(filename)'" - context.logger.info("Data successfully stored in S3.") - } catch let error as AWSServiceError { - // Generate the error message and update the log. - responseMessage = "The Lambda function encountered an error and your data was not saved. Root cause: \(error.errorCode ?? "") - \(error.message ?? "")" - context.logger.error("Failed to upload data to Amazon S3.") + // snippet-end:[lambda.swift.function.handler.putobject] + + // snippet-start:[lambda.swift.function.handler.handle] + /// The Lambda function's entry point. Called by the Lambda runtime. + /// + /// - Parameters: + /// - event: The `Request` describing the request made by the + /// client. + /// - context: A `LambdaContext` describing the context in + /// which the lambda function is running. + /// + /// - Returns: A `Response` object that will be encoded to JSON and sent + /// to the client by the Lambda runtime. + func handle(_ event: Request, context: LambdaContext) async throws -> Response { + // Get the bucket name from the environment. + guard let bucketName = ProcessInfo.processInfo.environment["BUCKET_NAME"] else { + throw S3ExampleLambdaErrors.noEnvironmentVariable("BUCKET_NAME") + } + + // Make sure the `S3Client` is valid. + guard let s3Client else { + throw S3ExampleLambdaErrors.noS3Client + } + + // Call the `putObject` function to store the object on Amazon S3. + var responseMessage: String + do { + let filename = try await putObject( + client: s3Client, + bucketName: bucketName, + body: event.body) + + // Generate the response text. + responseMessage = "The Lambda function has successfully stored your data in S3 with name \(filename)'" + + // Send the success notification to the logger. + context.logger.info("Data successfully stored in S3.") + } catch let error as AWSServiceError { + // Generate the error message. + responseMessage = "The Lambda function encountered an error and your data was not saved. Root cause: \(error.errorCode ?? "") - \(error.message ?? "")" + + // Send the error message to the logger. + context.logger.error("Failed to upload data to Amazon S3.") + } + + // Return the response message. The AWS Lambda runtime will send it to the + // client. + return Response( + req_id: context.requestID, + body: responseMessage) } - - return Response(req_id: context.requestID, body: responseMessage) + // snippet-end:[lambda.swift.function.handler.handle] } -// snippet-end:[lambda.swift.function.runtime] - -// Start up the runtime. - -// snippet-start:[lambda.swift.function.start] -try await runtime.run() -// snippet-end:[lambda.swift.function.start] -// snippet-end:[lambda.swift.function.complete] +// snippet-end:[lambda.swift.function.handler] +// snippet-end:[lambda.swift.function.complete] \ No newline at end of file