From 5ee4ae949edad13bb6e06a2d1f93c2557ba50c91 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Tue, 23 Sep 2025 20:06:44 +0000 Subject: [PATCH 1/4] Fixed file permissions --- .../using-lambda-runtime/Sources/lambda.swift | 180 +++++++----------- 1 file changed, 70 insertions(+), 110 deletions(-) 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 98087226074..4ee32315352 100644 --- a/swift/example_code/lambda/using-lambda-runtime/Sources/lambda.swift +++ b/swift/example_code/lambda/using-lambda-runtime/Sources/lambda.swift @@ -4,7 +4,7 @@ // snippet-start:[lambda.swift.function.imports] import Foundation import AWSLambdaRuntime -import AWSS3 +@preconcurrency import AWSS3 import protocol AWSClientRuntime.AWSServiceError import enum Smithy.ByteStream @@ -38,125 +38,85 @@ 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] -// 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" )" - ) - - // 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( +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. + + _ = try await s3Client.putObject( + input: PutObjectInput( body: inputStream, bucket: bucketName, key: objectName ) - let _ = try await client.putObject(input: putObjectRequest) + ) + + // Return the name of the file + + 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 - // Return the name of the file. - return objectName + 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") } - // 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) + + 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.handle] + + return Response(req_id: context.requestID, body: responseMessage) } -// snippet-end:[lambda.swift.function.handler] +// 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] From ed8b1c4410bee4a64a7779c9d968a25d8fdf7802 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Tue, 23 Sep 2025 20:17:00 +0000 Subject: [PATCH 2/4] Swift Step Functions Hello and Scenario --- swift/example_code/sfn/README.md | 122 ++++++ swift/example_code/sfn/hello/Package.swift | 47 +++ .../sfn/hello/Sources/entry.swift | 74 ++++ swift/example_code/sfn/scenario/Package.swift | 48 +++ .../sfn/scenario/Sources/Activity.swift | 137 +++++++ .../sfn/scenario/Sources/Example.swift | 252 ++++++++++++ .../sfn/scenario/Sources/StateMachine.swift | 371 ++++++++++++++++++ .../sfn/scenario/Sources/entry.swift | 62 +++ 8 files changed, 1113 insertions(+) create mode 100644 swift/example_code/sfn/README.md create mode 100644 swift/example_code/sfn/hello/Package.swift create mode 100644 swift/example_code/sfn/hello/Sources/entry.swift create mode 100644 swift/example_code/sfn/scenario/Package.swift create mode 100644 swift/example_code/sfn/scenario/Sources/Activity.swift create mode 100644 swift/example_code/sfn/scenario/Sources/Example.swift create mode 100644 swift/example_code/sfn/scenario/Sources/StateMachine.swift create mode 100644 swift/example_code/sfn/scenario/Sources/entry.swift diff --git a/swift/example_code/sfn/README.md b/swift/example_code/sfn/README.md new file mode 100644 index 00000000000..ecdac41c071 --- /dev/null +++ b/swift/example_code/sfn/README.md @@ -0,0 +1,122 @@ +# Step Functions code examples for the SDK for Swift + +## Overview + +Shows how to use the AWS SDK for Swift to work with AWS Step Functions. + + + + +_Step Functions makes it easy to coordinate the components of distributed applications as a series of steps in a visual workflow._ + +## ⚠ Important + +* Running this code might result in charges to your AWS account. For more details, see [AWS Pricing](https://aws.amazon.com/pricing/) and [Free Tier](https://aws.amazon.com/free/). +* Running the tests might result in charges to your AWS account. +* We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege). +* This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services). + + + + +## Code examples + +### Prerequisites + +For prerequisites, see the [README](../../README.md#Prerequisites) in the `swift` folder. + + + + + +### Basics + +Code examples that show you how to perform the essential operations within a service. + +- [Learn the basics](scenario/Sources/entry.swift) + + +### Single actions + +Code excerpts that show you how to call individual service functions. + +- [CreateActivity](scenario/Sources/Activity.swift#L42) +- [CreateStateMachine](scenario/Sources/StateMachine.swift#L133) +- [DeleteActivity](scenario/Sources/Activity.swift#L102) +- [DeleteStateMachine](scenario/Sources/StateMachine.swift#L308) +- [DescribeExecution](scenario/Sources/StateMachine.swift#L263) +- [DescribeStateMachine](scenario/Sources/StateMachine.swift#L180) +- [GetActivityTask](scenario/Sources/StateMachine.swift#L228) +- [ListActivities](scenario/Sources/Activity.swift#L42) +- [ListStateMachines](scenario/Sources/StateMachine.swift#L105) +- [SendTaskSuccess](scenario/Sources/Activity.swift#L115) +- [StartExecution](scenario/Sources/StateMachine.swift#L205) + + + + + +## Run the examples + +### Instructions + +To build any of these examples from a terminal window, navigate into its +directory, then use the following command: + +``` +$ swift build +``` + +To build one of these examples in Xcode, navigate to the example's directory +(such as the `ListUsers` directory, to build that example). Then type `xed.` +to open the example directory in Xcode. You can then use standard Xcode build +and run commands. + + + + + +#### Learn the basics + +This example shows you how to do the following: + +- Create an activity. +- Create a state machine from an Amazon States Language definition that contains the previously created activity as a step. +- Run the state machine and respond to the activity with user input. +- Get the final status and output after the run completes, then clean up resources. + + + + + + + + + +### Tests + +⚠ Running tests might result in charges to your AWS account. + + +To find instructions for running these tests, see the [README](../../README.md#Tests) +in the `swift` folder. + + + + + + +## Additional resources + +- [Step Functions Developer Guide](https://docs.aws.amazon.com/step-functions/latest/dg/welcome.html) +- [Step Functions API Reference](https://docs.aws.amazon.com/step-functions/latest/apireference/Welcome.html) +- [SDK for Swift Step Functions reference](https://sdk.amazonaws.com/swift/api/awssfn/latest/documentation/awssfn) + + + + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 diff --git a/swift/example_code/sfn/hello/Package.swift b/swift/example_code/sfn/hello/Package.swift new file mode 100644 index 00000000000..c2013d449a3 --- /dev/null +++ b/swift/example_code/sfn/hello/Package.swift @@ -0,0 +1,47 @@ +// swift-tools-version: 5.9 +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// (swift-tools-version has two lines here because it needs to be the first +// line in the file, but it should also appear in the snippet below) +// +// snippet-start:[swift.sfn.hello.package] +// swift-tools-version: 5.9 +// +// The swift-tools-version declares the minimum version of Swift required to +// build this package. + +import PackageDescription + +let package = Package( + name: "hello-sfn", + // Let Xcode know the minimum Apple platforms supported. + platforms: [ + .macOS(.v13), + .iOS(.v15) + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package( + url: "https://github.com/awslabs/aws-sdk-swift", + from: "1.0.0"), + .package( + url: "https://github.com/apple/swift-argument-parser.git", + branch: "main" + ) + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products + // from dependencies. + .executableTarget( + name: "hello-sfn", + dependencies: [ + .product(name: "AWSSFN", package: "aws-sdk-swift"), + .product(name: "ArgumentParser", package: "swift-argument-parser") + ], + path: "Sources") + + ] +) +// snippet-end:[swift.sfn.hello.package] diff --git a/swift/example_code/sfn/hello/Sources/entry.swift b/swift/example_code/sfn/hello/Sources/entry.swift new file mode 100644 index 00000000000..c753654c391 --- /dev/null +++ b/swift/example_code/sfn/hello/Sources/entry.swift @@ -0,0 +1,74 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// snippet-start:[swift.sfn.hello] +// An example that shows how to use the AWS SDK for Swift to perform a simple +// operation using Amazon Elastic Compute Cloud (EC2). +// + +import ArgumentParser +import Foundation + +// snippet-start:[swift.sfn.import] +import AWSSFN +// snippet-end:[swift.sfn.import] + +struct ExampleCommand: ParsableCommand { + @Option(help: "The AWS Region to run AWS API calls in.") + var awsRegion = "us-east-1" + + static var configuration = CommandConfiguration( + commandName: "hello-sfn", + abstract: """ + Demonstrates a simple operation using AWS Step Functions. + """, + discussion: """ + An example showing how to make a call to AWS Step Functions using the + AWS SDK for Swift. + """ + ) + + /// Called by ``main()`` to run the bulk of the example. + func runAsync() async throws { + let sfnConfig = try await SFNClient.SFNClientConfiguration(region: awsRegion) + let sfnClient = SFNClient(config: sfnConfig) + + // snippet-start:[swift.sfn.hello.ListStateMachines] + do { + let output = try await sfnClient.listStateMachines( + input: ListStateMachinesInput( + maxResults: 10 + ) + ) + + guard let stateMachines = output.stateMachines else { + print("*** No state machines found.") + return + } + + print("Found \(stateMachines.count) state machines (capped to 10)...") + for machine in stateMachines { + print(" \(machine.name ?? ""): \(machine.stateMachineArn ?? "")") + } + } catch { + print("*** Error fetching state machine list: \(error.localizedDescription)") + } + // snippet-end:[swift.sfn.hello.ListStateMachines] + } +} + +/// The program's asynchronous entry point. +@main +struct Main { + static func main() async { + let args = Array(CommandLine.arguments.dropFirst()) + + do { + let command = try ExampleCommand.parse(args) + try await command.runAsync() + } catch { + ExampleCommand.exit(withError: error) + } + } +} +// snippet-end:[swift.sfn.hello] diff --git a/swift/example_code/sfn/scenario/Package.swift b/swift/example_code/sfn/scenario/Package.swift new file mode 100644 index 00000000000..ef21eeb8a90 --- /dev/null +++ b/swift/example_code/sfn/scenario/Package.swift @@ -0,0 +1,48 @@ +// swift-tools-version: 5.9 +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// (swift-tools-version has two lines here because it needs to be the first +// line in the file, but it should also appear in the snippet below) +// +// snippet-start:[swift.sfn.hello.package] +// swift-tools-version: 5.9 +// +// The swift-tools-version declares the minimum version of Swift required to +// build this package. + +import PackageDescription + +let package = Package( + name: "sfn-scenario", + // Let Xcode know the minimum Apple platforms supported. + platforms: [ + .macOS(.v13), + .iOS(.v15) + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package( + url: "https://github.com/awslabs/aws-sdk-swift", + from: "1.0.0"), + .package( + url: "https://github.com/apple/swift-argument-parser.git", + branch: "main" + ) + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products + // from dependencies. + .executableTarget( + name: "sfn-scenario", + dependencies: [ + .product(name: "AWSIAM", package: "aws-sdk-swift"), + .product(name: "AWSSFN", package: "aws-sdk-swift"), + .product(name: "ArgumentParser", package: "swift-argument-parser") + ], + path: "Sources") + + ] +) +// snippet-end:[swift.sfn.hello.package] diff --git a/swift/example_code/sfn/scenario/Sources/Activity.swift b/swift/example_code/sfn/scenario/Sources/Activity.swift new file mode 100644 index 00000000000..375a0b0100c --- /dev/null +++ b/swift/example_code/sfn/scenario/Sources/Activity.swift @@ -0,0 +1,137 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// snippet-start:[swift.sfn.scenario.activity] +import Foundation +import AWSSFN + +/// Describes errors that occur on Step Functions activities. +enum ActivityError: Error { + /// The ARN is missing from the returned activity. + case missingArnError + /// The activity list is missing from the response. + case missingActivityListError + /// No matching activity was found. + case activityNotFoundError + + var errorDescription: String { + switch self { + case .missingArnError: + return "The ARN is missing from the returned activity" + case .missingActivityListError: + return "The activity list is missing from the response" + case .activityNotFoundError: + return "No activity with the specified name was found" + } + } +} + +/// Manage a Step Functions activity. +class Activity { + let sfnClient: SFNClient + let activityName: String + var activityArn = "" + + init(client: SFNClient, name: String) async throws { + sfnClient = client + self.activityName = name + + try await self.findOrCreateActivity() + } + + // snippet-start:[swift.sfn.CreateActivity] + /// Create a new Step Functions activity. + /// + /// - Throws: `ActivityError` and appropriate AWS errors. + private func createActivity() async throws { + let output = try await sfnClient.createActivity( + input: CreateActivityInput(name: activityName) + ) + + guard let arn = output.activityArn else { + throw ActivityError.missingArnError + } + + activityArn = arn + } + // snippet-end:[swift.sfn.CreateActivity] + + // snippet-start:[swift.sfn.ListActivitiesPaginated] + // snippet-start:[swift.sfn.ListActivities] + /// Find an activity with the name specified when initializing the + /// `Activity` object. + /// + /// - Throws: `ActivityError` and appropriate AWS errors. + private func findActivity() async throws { + let pages = sfnClient.listActivitiesPaginated( + input: ListActivitiesInput() + ) + + for try await page in pages { + guard let activities = page.activities else { + throw ActivityError.missingActivityListError + } + + for activity in activities { + if activity.name == activityName { + guard let arn = activity.activityArn else { + throw ActivityError.missingArnError + } + self.activityArn = arn + } + } + } + + throw ActivityError.activityNotFoundError + } + // snippet-end:[swift.sfn.ListActivities] + // snippet-end:[swift.sfn.ListActivitiesPaginated] + + /// Finds an existing activity with the name given when initializing + /// the `Activity`. If one isn't found, a new one is created. + /// + /// - Throws: `ActivityError` and appropriate AWS errors. + private func findOrCreateActivity() async throws { + do { + try await findActivity() + } catch { + try await createActivity() + } + } + + // snippet-start:[swift.sfn.DeleteActivity] + /// Delete the activity described by this object. + public func delete() async { + do { + _ = try await sfnClient.deleteActivity( + input: DeleteActivityInput(activityArn: activityArn) + ) + } catch { + print("*** Error deleting the activity: \(error.localizedDescription)") + } + } + // snippet-end:[swift.sfn.DeleteActivity] + + // snippet-start:[swift.sfn.SendTaskSuccess] + /// Sends a task success notification to the activity. + /// + /// - Parameters: + /// - taskToken: The task's token. + /// - response: The task response. + /// + /// - Returns: `ActivityError` and appropriate AWS errors. + public func sendTaskSuccess(taskToken: String, response: String) async -> Bool { + do { + _ = try await sfnClient.sendTaskSuccess( + input: SendTaskSuccessInput(output: response, taskToken: taskToken) + ) + + return true + } catch { + print("*** Error sending task success: \(error.localizedDescription)") + return false + } + } + // snippet-end:[swift.sfn.SendTaskSuccess] +} +// snippet-end:[swift.sfn.scenario.activity] diff --git a/swift/example_code/sfn/scenario/Sources/Example.swift b/swift/example_code/sfn/scenario/Sources/Example.swift new file mode 100644 index 00000000000..43113be53e1 --- /dev/null +++ b/swift/example_code/sfn/scenario/Sources/Example.swift @@ -0,0 +1,252 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import AWSIAM +import AWSSFN + +class Example { + let sfnClient: SFNClient + let iamClient: IAMClient + + let username: String + let activityName: String + var activity: Activity? + var stateMachineName: String + var stateMachinei: StateMachine? + var stateMachine: StateMachine? + var definitionPath: String + var runArn: String? = nil + var iamRole: IAMClientTypes.Role? + + init(region: String, username: String, activityName: String, stateMachineName: String, + definitionPath: String) async throws { + let sfnConfig = try await SFNClient.SFNClientConfiguration(region: region) + sfnClient = SFNClient(config: sfnConfig) + + let iamConfig = try await IAMClient.IAMClientConfiguration(region: region) + iamClient = IAMClient(config: iamConfig) + + self.username = username + self.activityName = activityName + self.stateMachineName = stateMachineName + self.definitionPath = definitionPath + } + + /// Clean up artifacts created by the program. + func cleanUp() async { + if iamRole != nil { + print("Deleting the IAM role: \(iamRole?.roleName ?? "")...") + do { + _ = try await iamClient.deleteRole( + input: DeleteRoleInput(roleName: iamRole?.roleName) + ) + } catch { + print("*** Unable to delete the IAM role: \(error.localizedDescription)") + } + } + + if activity != nil { + await activity?.delete() + } + + if stateMachine != nil { + print("Deleting the State Machine...") + await stateMachine?.delete() + } + } + + /// Create a new IAM role. + /// + /// - Returns: The `IAMClientTypes.Role` that was created, or `nil` if it + /// couldn't be created. + func createIAMRole() async -> IAMClientTypes.Role? { + let trustPolicy = """ + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "", + "Effect": "Allow", + "Principal": {"Service": "states.amazonaws.com"}, + "Action": "sts:AssumeRole" + } + ] + } + """ + + do { + let output = try await iamClient.createRole( + input: CreateRoleInput( + assumeRolePolicyDocument: trustPolicy, + roleName: tempName(prefix: "state-machine-demo-role") + ) + ) + + return output.role + } catch { + print("*** Error creating the IAM role: \(error.localizedDescription)") + return nil + } + } + + /// Delete the IAM role. + /// + /// - Throws: The AWS error, if any. + func deleteIAMRole() async throws { + guard let iamRole = self.iamRole else { + return + } + + print("Deleting the IAM role: \(iamRole.roleName ?? "")") + + _ = try await iamClient.deleteRole( + input: DeleteRoleInput(roleName: iamRole.roleName) + ) + } + + /// Generate and return a unique file name that begins with the specified + /// string. + /// + /// - Parameters: + /// - prefix: Text to use at the beginning of the returned name. + /// + /// - Returns: A string containing a unique filename that begins with the + /// specified `prefix`. + /// + /// The returned name uses a random number between 1 million and 1 billion to + /// provide reasonable certainty of uniqueness for the purposes of this + /// example. + func tempName(prefix: String) -> String { + return "\(prefix)-\(Int.random(in: 1000000..<1000000000))" + } + + /// Run the example. + func run() async { + print("Creating the IAM role...") + iamRole = await createIAMRole() + + if iamRole == nil { + print("Unable to create the IAM role. Exiting.") + return + } + + print("Created role: \(iamRole?.roleName ?? "")") + + // Find or create a Step Functions activity. + + print("Finding or creating a Step Functions activity...") + + do { + activity = try await Activity(client: sfnClient, name: activityName) + } catch let error as ActivityError { + print("Unable to create the activity. \(error.errorDescription)") + await cleanUp() + return + } catch { + print("An AWS error occurred: \(error.localizedDescription)") + await cleanUp() + return + } + + guard let activity = activity else { + print("No activity available.") + await cleanUp() + return + } + + print("Created Step Functions activity with ARN \(activity.activityArn).") + + // Find or create a State Machine. + + print("Finding or creating a State Machine...") + do { + stateMachine = try await StateMachine( + sfnClient: sfnClient, + name: stateMachineName, + iamRole: iamRole!, + definitionPath: definitionPath, + activity: activity + ) + } catch let error as StateMachineError { + print("Unable to create the state machine: \(error.errorDescription)") + await cleanUp() + return + } catch { + print("An AWS error occurred while creating the state machine: \(error.localizedDescription)") + await cleanUp() + return + } + + guard let stateMachine = stateMachine else { + print("No state machine available.") + await cleanUp() + return + } + + // Display information about the State Machine. + + do { + try await stateMachine.describe() + } catch let error as StateMachineError { + print("Unable to describe the state machine: \(error.errorDescription)") + await cleanUp() + return + } catch { + print("An AWS error occurred getting state machine details: \(error.localizedDescription)") + await cleanUp() + return + } + + // Run the state machine. + + do { + runArn = try await stateMachine.start(username: username) + } catch let error as StateMachineError { + print("Unable to start the state machine: \(error.errorDescription)") + await cleanUp() + return + } catch { + print("An AWS error occurred while starting the state machine: \(error.localizedDescription)") + await cleanUp() + return + } + + guard let runArn else { + print("Unable to run the state machine. Exiting.") + await cleanUp() + return + } + + // Step through the state machine. This function runs until the state + // machine enters its "done" state. + + do { + try await stateMachine.execute() + } catch let error as StateMachineError { + print("Error executing the state machine: \(error.errorDescription)") + await cleanUp() + return + } catch { + print("AWS error while executing the state machine: \(error.localizedDescription)") + await cleanUp() + return + } + + // Finish running the state machine. + + do { + try await stateMachine.finishExecution(arn: runArn) + } catch let error as StateMachineError { + print("Error while stopping the state machine: \(error.errorDescription)") + await cleanUp() + return + } catch { + print("AWS error while stopping the state machine: \(error.localizedDescription)") + await cleanUp() + return + } + + await cleanUp() + } +} diff --git a/swift/example_code/sfn/scenario/Sources/StateMachine.swift b/swift/example_code/sfn/scenario/Sources/StateMachine.swift new file mode 100644 index 00000000000..b18bf61c2ec --- /dev/null +++ b/swift/example_code/sfn/scenario/Sources/StateMachine.swift @@ -0,0 +1,371 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// snippet-start:[swift.sfn.scenario.statemachine] +import Foundation +import AWSSFN +import AWSIAM + +/// Describes an error the occurred while managing the state machine. +enum StateMachineError: Error { + /// No matching role was found. + case roleNotFoundError + /// The ARN is missing from the returned state machine. + case missingArnError + /// The state machine list is missing from the response. + case missingStateMachineListError + /// No matching state machine was found. + case stateMachineNotFoundError + /// Unable to read the state machine definition file. + case definitionFileReadError + /// A state machine's details are missing + case stateMachineDetailsMissingError + /// The task token is missing from the activity task. + case taskTokenMissingError + /// The input is missing from the activity task. + case inputMissingError + /// The state machine's output is missing. + case outputMissingError + /// The state machine's execution has been aborted. + case executionAborted + /// The state machine's execution failed. + case executionFailed + /// The state machine timed out. + case executionTimedOut + /// The state machine's status is unrecognized. + case executionStatusUnknown + + var errorDescription: String { + switch self { + case .roleNotFoundError: + return "The specified role was not found or could not be created" + case .missingArnError: + return "The ARN is missing from the returned activity" + case .missingStateMachineListError: + return "The state machine list is missing from the response" + case .stateMachineNotFoundError: + return "No state machine with the specified name was found" + case .definitionFileReadError: + return "Unable to read the state machine definition file" + case .stateMachineDetailsMissingError: + return "The state machine's details are missing" + case .taskTokenMissingError: + return "The task token is missing from the activity task." + case .inputMissingError: + return "The input is missing from the activity task." + case .outputMissingError: + return "The state machine's output is missing." + case .executionAborted: + return "The state machine's execution was aborted." + case .executionFailed: + return "The state machine's execution failed." + case .executionTimedOut: + return "The state machine's execution timed out." + case .executionStatusUnknown: + return "The state machine has entered an unknown status." + } + } +} + +/// Describes a message and a list of actions that can be taken in response +/// to that message. +struct ActionList: Decodable { + let message: String + let actions: [String] +} + +/// Describes a message returned by an action. +struct Output: Decodable { + let message: String +} + +/// Encapsulates an AWS Step Functions state machine. +class StateMachine { + let sfnClient: SFNClient + let iamRole: IAMClientTypes.Role + let activity: Activity + let stateMachineName: String + let definitionPath: String + var stateMachineArn = "" + + init(sfnClient: SFNClient, name: String, + iamRole: IAMClientTypes.Role, definitionPath: String, + activity: Activity) async throws { + + self.sfnClient = sfnClient + self.iamRole = iamRole + self.stateMachineName = name + self.definitionPath = definitionPath + self.activity = activity + + try await findOrCreateStateMachine() + } + + // snippet-start:[swift.sfn.ListStateMachinesPaginated] + // snippet-start:[swift.sfn.ListStateMachines] + /// Finds a state machine matching the name specified when initializing the `StateMachine`. + /// - Throws: `StateMachineError` and appropriate AWS errors. + private func findStateMachine() async throws { + let pages = sfnClient.listStateMachinesPaginated( + input: ListStateMachinesInput() + ) + + for try await page in pages { + guard let stateMachines = page.stateMachines else { + throw StateMachineError.missingStateMachineListError + } + + for stateMachine in stateMachines { + if stateMachine.name == stateMachineName { + guard let arn = stateMachine.stateMachineArn else { + throw StateMachineError.missingArnError + } + stateMachineArn = arn + } + } + } + + throw StateMachineError.stateMachineNotFoundError + } + // snippet-end:[swift.sfn.ListStateMachines] + // snippet-end:[swift.sfn.ListStateMachinesPaginated] + + // snippet-start:[swift.sfn.CreateStateMachine] + /// Create a new state machine with the name given when initializing the + /// `StateMachine` object. + /// + /// - Throws: `StateMachineError` and appropriate AWS errors. + private func createStateMachine() async throws { + var definition: String + + print("Reading the state machine file from \(definitionPath)...") + do { + definition = try String(contentsOfFile: definitionPath, encoding: .utf8) + } catch { + throw StateMachineError.definitionFileReadError + } + + // Swap in the activity's ARN into the definition string. + + definition.replace("{{DOC_EXAMPLE_ACTIVITY_ARN}}", with: activity.activityArn) + + let output = try await sfnClient.createStateMachine( + input: CreateStateMachineInput( + definition: definition, + name: stateMachineName, + roleArn: iamRole.arn + ) + ) + + guard let arn = output.stateMachineArn else { + throw StateMachineError.missingArnError + } + + stateMachineArn = arn + } + // snippet-end:[swift.sfn.CreateStateMachine] + + /// Finds a state machine matching the name given when initializing the + /// `StateMachine` object. If it doesn't exist, a new one is created. + /// + /// - Throws: `StateMachineError` and appropriate AWS errors. + private func findOrCreateStateMachine() async throws { + do { + try await findStateMachine() + } catch { + try await createStateMachine() + } + } + + // snippet-start:[swift.sfn.DescribeStateMachine] + /// Outputs a description of the state machine. + /// + /// - Throws: `StateMachineError` and appropriate AWS errors. + func describe() async throws { + let output = try await sfnClient.describeStateMachine( + input: DescribeStateMachineInput( + stateMachineArn: stateMachineArn + ) + ) + + guard let name = output.name, + let status = output.status else { + throw StateMachineError.stateMachineDetailsMissingError + } + + print() + print("State machine details: ") + print(" Name: \(name)") + print(" ARN: \(stateMachineArn)") + print(" Status: \(status)") + print() + } + // snippet-end:[swift.sfn.DescribeStateMachine] + + // snippet-start:[swift.sfn.StartExecution] + /// Start up the state machine. + /// + /// - Parameter username: The username to use for the conversation. + /// + /// - Throws: `StateMachineError` and appropriate AWS errors. + /// - Returns: The execution ARN of the running state machine. + func start(username: String) async throws -> String? { + let runInput = """ + { "name": "\(username)" } + """ + + let output = try await sfnClient.startExecution( + input: StartExecutionInput( + input: runInput, + stateMachineArn: stateMachineArn + ) + ) + + return output.executionArn + } + // snippet-end:[swift.sfn.StartExecution] + + // snippet-start:[swift.sfn.GetActivityTask] + /// Execute the steps of the state machine until it exits. + /// + /// - Throws: `StateMachineError` and appropriate AWS errors. + func execute() async throws { + var action: String = "" + + while action != "done" { + let getTaskOutput = try await sfnClient.getActivityTask( + input: GetActivityTaskInput( + activityArn: activity.activityArn + ) + ) + + guard let token = getTaskOutput.taskToken else { + throw StateMachineError.taskTokenMissingError + } + guard let input = getTaskOutput.input else { + throw StateMachineError.inputMissingError + } + + let inputData = input.data(using: .utf8)! + let inputObject = try! JSONDecoder().decode(ActionList.self, from: inputData) + + print("Task message: \(inputObject.message)") + + action = menuRequest(prompt: "Choose an action:", options: inputObject.actions) + _ = await activity.sendTaskSuccess(taskToken: token, response: """ + { "action": "\(action)" } + """ + ) + } + } + // snippet-end:[swift.sfn.GetActivityTask] + + // snippet-start:[swift.sfn.DescribeExecution] + /// Wait for the execution to end, then output its final message. + /// + /// - Parameter arn: The execution ARN to finish. + /// + /// - Throws: `StateMachineError` and appropriate AWS errors. + func finishExecution(arn: String) async throws { + var status: SFNClientTypes.ExecutionStatus = .running + + while status == .running { + let output = try await sfnClient.describeExecution( + input: DescribeExecutionInput( + executionArn: arn + ) + ) + + status = output.status ?? .aborted + + switch status { + case .running: + print("The state machine is still running. Waiting for it to finish.") + await sleep(forSeconds: 1) + case .succeeded: + guard let outputString = output.output else { + throw StateMachineError.outputMissingError + } + + let outputData = outputString.data(using: .utf8)! + let outputObject = try! JSONDecoder().decode(Output.self, from: outputData) + print(""" + Execution completed with final message: \(outputObject.message) + """) + case .aborted: + throw StateMachineError.executionAborted + case .failed: + throw StateMachineError.executionFailed + case .timedOut: + throw StateMachineError.executionTimedOut + default: + throw StateMachineError.executionStatusUnknown + } + } + } + // snippet-end:[swift.sfn.DescribeExecution] + + // snippet-start:[swift.sfn.DeleteStateMachine] + /// Delete the state machine. + func delete() async { + do { + _ = try await sfnClient.deleteStateMachine( + input: DeleteStateMachineInput(stateMachineArn: stateMachineArn) + ) + } catch { + print("*** Error deleting the state machine: \(error.localizedDescription)") + } + } + // snippet-end:[swift.sfn.DeleteStateMachine] + + /// Sleep for the specified number of seconds. + /// + /// - Parameter seconds: The number of seconds to sleep, as a floating + /// point value. + func sleep(forSeconds seconds: Double) async { + do { + try await Task.sleep(for: .seconds(seconds)) + } catch { + return + } + + } + + /// Display a menu of options then request a selection. + /// + /// - Parameters: + /// - prompt: A prompt string to display before the menu. + /// - options: An array of strings giving the menu options. + /// + /// - Returns: The string value of the selected option. + func menuRequest(prompt: String, options: [String]) -> String { + let numOptions = options.count + + if numOptions == 0 { + return "done" + } + + print(prompt) + + for (index, value) in options.enumerated() { + print("(\(index+1)) \(value)") + } + + repeat { + print("Enter your selection (1 - \(numOptions)): ", terminator: "") + if let answer = readLine() { + guard let answer = Int(answer) else { + print("Please enter the number matching your selection.") + continue + } + + if answer > 0 && answer <= numOptions { + return options[answer-1] + } else { + print("Please enter the number matching your selection.") + } + } + } while true + } +} +// snippet-end:[swift.sfn.scenario.statemachine] diff --git a/swift/example_code/sfn/scenario/Sources/entry.swift b/swift/example_code/sfn/scenario/Sources/entry.swift new file mode 100644 index 00000000000..c9ce063ae97 --- /dev/null +++ b/swift/example_code/sfn/scenario/Sources/entry.swift @@ -0,0 +1,62 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// snippet-start:[swift.sfn.scenario] +// An example that shows how to use the AWS SDK for Swift to perform a simple +// operation using Amazon Elastic Compute Cloud (EC2). +// + +import ArgumentParser +import Foundation + +struct ExampleCommand: ParsableCommand { + @Option(help: "The AWS Region to run AWS API calls in.") + var awsRegion = "us-east-1" + + @Option(help: "The user's name.") + var username = "Johanna Doe" + + @Option(help: "The name of the activity to find or create.") + var activityName = "scenario-example-activity" + + @Option(help: "The name of the state machine to find or create.") + var stateMachineName = "scenario-example-state-machine" + + @Option(help: "Path of the State Machine definition file.") + var definitionPath: String + + static var configuration = CommandConfiguration( + commandName: "sfn-scenario", + abstract: """ + Demonstrates a variety of AWS Step Function features. + """, + discussion: """ + """ + ) + + /// Called by ``main()`` to run the bulk of the example. + func runAsync() async throws { + let example = try await Example(region: awsRegion, username: username, + activityName: activityName, + stateMachineName: stateMachineName, + definitionPath: definitionPath) + + await example.run() + } +} + +/// The program's asynchronous entry point. +@main +struct Main { + static func main() async { + let args = Array(CommandLine.arguments.dropFirst()) + + do { + let command = try ExampleCommand.parse(args) + try await command.runAsync() + } catch { + ExampleCommand.exit(withError: error) + } + } +} +// snippet-end:[swift.sfn.scenario] From f3abaa4b07337585313658b8618a427937e229ef Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Tue, 30 Sep 2025 13:06:58 -0400 Subject: [PATCH 3/4] Restore lambda.swift to previous state Undo changes made to the Swift lambda runtime example by mistake. This file should have been part of another PR. --- .../using-lambda-runtime/Sources/lambda.swift | 181 +++++++++++------- 1 file changed, 109 insertions(+), 72 deletions(-) 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..3efe85ac45f 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.handler] // snippet-end:[lambda.swift.function.complete] From 5c95ebe37a48313acb30bd9e86e4483a51c6ab03 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Tue, 30 Sep 2025 13:10:09 -0400 Subject: [PATCH 4/4] Update lambda.swift Remove stray newlines --- .../lambda/using-lambda-runtime/Sources/lambda.swift | 3 +++ 1 file changed, 3 insertions(+) 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 3efe85ac45f..98087226074 100644 --- a/swift/example_code/lambda/using-lambda-runtime/Sources/lambda.swift +++ b/swift/example_code/lambda/using-lambda-runtime/Sources/lambda.swift @@ -9,6 +9,7 @@ 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. @@ -19,6 +20,7 @@ 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`. @@ -29,6 +31,7 @@ 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 {