From cd3ca5a6f4fc0e6bd6261a11a32f87265da4114d Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Fri, 22 Aug 2025 17:33:14 +0000 Subject: [PATCH 01/10] Added 'Hello' example - needs testing --- .../example_code/support/hello/Package.swift | 47 ++++++++++ .../support/hello/Sources/entry.swift | 91 +++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 swift/example_code/support/hello/Package.swift create mode 100644 swift/example_code/support/hello/Sources/entry.swift diff --git a/swift/example_code/support/hello/Package.swift b/swift/example_code/support/hello/Package.swift new file mode 100644 index 00000000000..5341e578b63 --- /dev/null +++ b/swift/example_code/support/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.support.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-support", + // 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-support", + dependencies: [ + .product(name: "AWSSupport", package: "aws-sdk-swift"), + .product(name: "ArgumentParser", package: "swift-argument-parser") + ], + path: "Sources") + + ] +) +// snippet-end:[swift.support.hello.package] diff --git a/swift/example_code/support/hello/Sources/entry.swift b/swift/example_code/support/hello/Sources/entry.swift new file mode 100644 index 00000000000..d4546aec47e --- /dev/null +++ b/swift/example_code/support/hello/Sources/entry.swift @@ -0,0 +1,91 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// snippet-start:[swift.support.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 AWSClientRuntime +import Foundation + +// snippet-start:[swift.support.import] +import AWSSupport +// snippet-end:[swift.support.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-support", + abstract: """ + Demonstrates a simple operation using Amazon Support. + """, + discussion: """ + An example showing how to make a call to Amazon Support using the AWS + SDK for Swift. + """ + ) + + /// Return an array of the user's services. + /// + /// - Parameter supportClient: The `SupportClient` to use when calling + /// `describeServices()`. + /// + /// - Returns: An array of services. + func getSupportServices(supportClient: SupportClient) async -> [SupportClientTypes.Service] { + do { + let output = try await supportClient.describeServices( + input: DescribeServicesInput() + ) + + guard let services = output.services else { + return [] + } + + return services + } catch let error as AWSServiceError { + // SubscriptionRequiredException isn't a modeled error, so we + // have to catch AWSServiceError and then look at its errorCode to + // see if it's SubscriptionRequiredException. + if error.errorCode == "SubscriptionRequiredException" { + print("*** You need a subscription to use AWS Support.") + return [] + } else { + print("*** An unknown error occurred getting support information.") + return [] + } + } catch { + print("*** Error getting service information: \(error.localizedDescription)") + return [] + } + } + + /// Called by ``main()`` to run the bulk of the example. + func runAsync() async throws { + let supportConfig = try await SupportClient.SupportClientConfiguration(region: awsRegion) + let supportClient = SupportClient(config: supportConfig) + + let services = await getSupportServices(supportClient: supportClient) + + print("Found \(services.count) security services") + } +} + +/// 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.support.hello] From 60409db16ef650400701299fa313a6b072c36dbf Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Tue, 9 Sep 2025 14:27:24 +0000 Subject: [PATCH 02/10] Add scenario and readme --- .doc_gen/metadata/support_metadata.yaml | 106 +++ swift/example_code/support/README.md | 132 ++++ .../support/scenario/Package.swift | 47 ++ .../support/scenario/Sources/Scenario.swift | 668 ++++++++++++++++++ .../support/scenario/Sources/entry.swift | 48 ++ 5 files changed, 1001 insertions(+) create mode 100644 swift/example_code/support/README.md create mode 100644 swift/example_code/support/scenario/Package.swift create mode 100644 swift/example_code/support/scenario/Sources/Scenario.swift create mode 100644 swift/example_code/support/scenario/Sources/entry.swift diff --git a/.doc_gen/metadata/support_metadata.yaml b/.doc_gen/metadata/support_metadata.yaml index 323774d5274..35ec7cb1a51 100644 --- a/.doc_gen/metadata/support_metadata.yaml +++ b/.doc_gen/metadata/support_metadata.yaml @@ -48,6 +48,17 @@ support_Hello: - description: snippet_tags: - python.example_code.support.Hello + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/support + excerpts: + - description: The Package.swift file. + snippet_tags: + - swift.support.hello.package + - description: The entry.swift file. + snippet_tags: + - swift.support.hello services: support: {DescribeServices} support_AddAttachmentsToSet: @@ -97,6 +108,15 @@ support_AddAttachmentsToSet: snippet_tags: - python.example_code.support.SupportWrapper_decl - python.example_code.support.AddAttachmentToSet + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.support.import + - swift.support.AddAttachmentsToSet services: support: {AddAttachmentsToSet} support_AddCommunicationToCase: @@ -146,6 +166,15 @@ support_AddCommunicationToCase: snippet_tags: - python.example_code.support.SupportWrapper_decl - python.example_code.support.AddCommunicationToCase + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.support.import + - swift.support.AddCommunicationToCase services: support: {AddCommunicationToCase} support_CreateCase: @@ -195,6 +224,15 @@ support_CreateCase: snippet_tags: - python.example_code.support.SupportWrapper_decl - python.example_code.support.CreateCase + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.support.import + - swift.support.CreateCase services: support: {CreateCase} support_DescribeAttachment: @@ -244,6 +282,15 @@ support_DescribeAttachment: snippet_tags: - python.example_code.support.SupportWrapper_decl - python.example_code.support.DescribeAttachment + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.support.import + - swift.support.DescribeAttachment services: support: {DescribeAttachment} support_DescribeCases: @@ -293,6 +340,15 @@ support_DescribeCases: snippet_tags: - python.example_code.support.SupportWrapper_decl - python.example_code.support.DescribeCases + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.support.import + - swift.support.DescribeCases services: support: {DescribeCases} support_DescribeCommunications: @@ -342,6 +398,15 @@ support_DescribeCommunications: snippet_tags: - python.example_code.support.SupportWrapper_decl - python.example_code.support.DescribeCommunications + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.support.import + - swift.support.DescribeCommunications services: support: {DescribeCommunications} support_DescribeServices: @@ -382,6 +447,15 @@ support_DescribeServices: snippet_tags: - python.example_code.support.SupportWrapper_decl - python.example_code.support.DescribeServices + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.support.import + - swift.support.DescribeServices services: support: {DescribeServices} support_DescribeSeverityLevels: @@ -430,6 +504,15 @@ support_DescribeSeverityLevels: snippet_tags: - python.example_code.support.SupportWrapper_decl - python.example_code.support.DescribeSeverityLevels + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.support.import + - swift.support.DescribeSeverityLevels services: support: {DescribeSeverityLevels} support_ResolveCase: @@ -479,6 +562,15 @@ support_ResolveCase: snippet_tags: - python.example_code.support.SupportWrapper_decl - python.example_code.support.ResolveCase + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.support.import + - swift.support.ResolveCase services: support: {ResolveCase} support_Scenario_GetStartedSupportCases: @@ -543,6 +635,20 @@ support_Scenario_GetStartedSupportCases: - description: Define a class that wraps support client actions. snippet_tags: - python.example_code.support.SupportWrapper_full + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/support + excerpts: + - description: The Package.swift file. + snippet_tags: + - swift.support.scenario.package + - description: The entry.swift file. + snippet_tags: + - swift.support.scenario.entry + - description: The Scenario.swift file. + snippet_tags: + - swift.support.scenario.scenario services: support: {AddAttachmentsToSet, AddCommunicationToCase, CreateCase, DescribeAttachment, DescribeCases, DescribeCommunications, DescribeServices, DescribeSeverityLevels, ResolveCase} diff --git a/swift/example_code/support/README.md b/swift/example_code/support/README.md new file mode 100644 index 00000000000..61affc4a584 --- /dev/null +++ b/swift/example_code/support/README.md @@ -0,0 +1,132 @@ +# Support code examples for the SDK for Swift + +## Overview + +Shows how to use the AWS SDK for Swift to work with AWS Support. + + + + +_Support provides support for users of Amazon Web Services._ + +## ⚠ 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. + + + + + +### Get started + +- [Hello Support](hello/Package.swift#L8) (`DescribeServices`) + + +### Basics + +Code examples that show you how to perform the essential operations within a service. + +- [Learn the basics](scenario/Package.swift) + + +### Single actions + +Code excerpts that show you how to call individual service functions. + +- [AddAttachmentsToSet](scenario/Sources/Scenario.swift#L456) +- [AddCommunicationToCase](scenario/Sources/Scenario.swift#L505) +- [CreateCase](scenario/Sources/Scenario.swift#L354) +- [DescribeAttachment](scenario/Sources/Scenario.swift#L593) +- [DescribeCases](scenario/Sources/Scenario.swift#L403) +- [DescribeCommunications](scenario/Sources/Scenario.swift#L555) +- [DescribeServices](scenario/Sources/Scenario.swift#L279) +- [DescribeSeverityLevels](scenario/Sources/Scenario.swift#L315) +- [ResolveCase](scenario/Sources/Scenario.swift#L633) + + + + + +## 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. + + + + +#### Hello Support + +This example shows you how to get started using Support. + + +#### Learn the basics + +This example shows you how to do the following: + +- Get and display available services and severity levels for cases. +- Create a support case using a selected service, category, and severity level. +- Get and display a list of open cases for the current day. +- Add an attachment set and a communication to the new case. +- Describe the new attachment and communication for the case. +- Resolve the case. +- Get and display a list of resolved cases for the current day. + + + + + + + + + +### 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 + +- [Support User Guide](https://docs.aws.amazon.com/awssupport/latest/user/getting-started.html) +- [Support API Reference](https://docs.aws.amazon.com/awssupport/latest/APIReference/welcome.html) +- [SDK for Swift Support reference](https://sdk.amazonaws.com/swift/api/awssupport/latest/documentation/awssupport) + + + + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 diff --git a/swift/example_code/support/scenario/Package.swift b/swift/example_code/support/scenario/Package.swift new file mode 100644 index 00000000000..a11dff49b11 --- /dev/null +++ b/swift/example_code/support/scenario/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.support.scenario.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-support", + // 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-support", + dependencies: [ + .product(name: "AWSSupport", package: "aws-sdk-swift"), + .product(name: "ArgumentParser", package: "swift-argument-parser") + ], + path: "Sources") + + ] +) +// snippet-end:[swift.support.scenario.package] diff --git a/swift/example_code/support/scenario/Sources/Scenario.swift b/swift/example_code/support/scenario/Sources/Scenario.swift new file mode 100644 index 00000000000..6ebb3ecb1e8 --- /dev/null +++ b/swift/example_code/support/scenario/Sources/Scenario.swift @@ -0,0 +1,668 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// snippet-start:[swift.support.scenario.scenario] +import AWSClientRuntime +import AWSSupport +import Foundation + +/// The implementation of the scenario example's primary functionality. +class Scenario { + let region: String + let supportClient: SupportClient + + init(region: String) async throws { + self.region = region + + let supportConfig = try await SupportClient.SupportClientConfiguration(region: region) + supportClient = SupportClient(config: supportConfig) + } + + /// Ask the user to enter an integer at the keyboard. + /// + /// - Parameters: + /// - min: The minimum value to allow; default: 0. + /// - max: The maximum value to allow. + /// + /// - Returns: The integer entered by the user. + /// + /// This function keeps asking until the user enters a valid integer in + /// the specified range. + func inputInteger(min: Int, max: Int) -> Int { + if max < min { + return -1 + } + if min < 0 { + return -1 + } + + repeat { + print("Enter your selection (\(min) - \(max)): ", terminator: "") + if let answer = readLine() { + guard let answer = Int(answer) else { + print("Please enter the number matching your selection.") + continue + } + + if answer >= min && answer <= max { + return answer + } else { + print("Please enter a number between \(min) and \(max).") + } + } + } while true + } + + /// Runs the example. + /// + /// - Throws: Throws exceptions received from AWS + func run() async throws { + //====================================================================== + // 1. Get and display a list of available services, using + // DescribeServices. + //====================================================================== + + let services = await getServices().sorted { $0.name ?? "" < $1.name ?? "" } + print("Select a service:") + for (index, service) in services.enumerated() { + let numberPart = String(format: "%*d", 3, index+1) + print(" \(numberPart)) \(service.name ?? "")") + } + + let selectedService = services[inputInteger(min: 1, max: services.count) - 1] + let selectedServiceName = selectedService.name ?? "" + + //====================================================================== + // 2. Display categories for the selected service, and let the user + // select a category. + //====================================================================== + + guard let categories = selectedService.categories else { + print("The selected service has no categories listed!") + return + } + print("The selected service (\(selectedServiceName)) has \(categories.count) categories:") + for (index, category) in categories.enumerated() { + let numberPart = String(format: "%*d", 3, index+1) + print(" \(numberPart)) \(category.name ?? "")") + } + + let selectedCategory = categories[inputInteger(min: 1, max: categories.count) - 1] + + //====================================================================== + // 3. Get and display severity levels and select one from the list + // (DescribeSeverityLevels). + //====================================================================== + + let severityLevels = await getSeverityLevels() + print("Select a severity level:") + for (index, severityLevel) in severityLevels.enumerated() { + let numberPart = String(format: "%*d", 3, index+1) + print(" \(numberPart)) \(severityLevel.name ?? "")") + } + + let selectedSeverityLevel = severityLevels[inputInteger(min: 1, max: severityLevels.count) - 1] + + //====================================================================== + // 4. Create a support case using the selected service, category, and + // severity level. Set the subject to "Test case - please ignore". + // Get the new caseId (CreateCase). + //====================================================================== + + let caseID = await createCase( + service: selectedService, + category: selectedCategory, + severity: selectedSeverityLevel, + subject: "Test case - please ignore", + body: "Please ignore this test case created by the AWS Support scenario example for the AWS SDK for Swift." + ) + + guard let caseID else { + print("An error occurred while creating the case.") + return + } + print("Created a case with ID ", caseID) + + print("Waiting for the change to propagate...") + try await Task.sleep(nanoseconds: 10_000_000_000) + + //====================================================================== + // 5. Get a list of open cases for the current day. The list should + // contain the new case (DescribeCases). + //====================================================================== + + print("Getting today's open cases...") + let today = Calendar.current.startOfDay(for: .now) + let isoDateFormatter = ISO8601DateFormatter() + + let openCases = await getCases( + afterTime: isoDateFormatter.string(from: today), + includeResolved: false + ) + + for currentCase in openCases { + print(" Case: \(currentCase.caseId ?? ""): current status is \(currentCase.status ?? "")") + } + + //====================================================================== + // 6. Generate a file and add it to the case using an attachment set + // (AddAttachmentsToSet). + //====================================================================== + + print("Creating an attachment and attachment set...") + + let attachText = "Example file attachment text. Please ignore" + let attachName = "\(UUID()).txt" + + let attachmentSetID = await createAttachment(name: attachName, body: Data(attachText.utf8)) + guard let attachmentSetID else { + print("Attachment set couldn't be created.") + return + } + print("Created attachment set with ID \(attachmentSetID)") + + //====================================================================== + // 7. Add communication with the attachment to the support case + // (AddCommunicationToCase). + //====================================================================== + + print("Adding a communication with the attachment to the case...") + if !(await addCommunicationToCase(caseId: caseID, + body: "Please see the attachment for details.", + attachmentSet: attachmentSetID)) { + print("Unable to attach the attachment to the case.") + return + } + print("Added communication to the case.") + + print("Waiting for the change to propagate...") + try await Task.sleep(nanoseconds: 10_000_000_000) + + //====================================================================== + // 8. List the support case's communications (DescribeCommunications). + // One communication will be added when the case is created, and a + // second is created in step 7. Get the attachment ID of the step 7 + // communication. It will be needed in the next step. + //====================================================================== + + var attachmentIDList: [String] = [] + + print("Listing the case's communications...") + let communications = await getCommunications(caseId: caseID) + guard let communications else { + return + } + + print("Found \(communications.count) communications on the case:") + + for communication in communications { + print(" \(communication.submittedBy ?? "") at \(communication.timeCreated ?? "")") + + guard let attachmentList = communication.attachmentSet else { + print(" Attachment list is missing.") + continue + } + + if attachmentList.count > 0 { + print(" \\--> \(attachmentList.count) attachments") + for attachment in attachmentList { + guard let id = attachment.attachmentId else { + print(" Attachment ID missing.") + continue + } + attachmentIDList.append(id) + } + } + } + + //====================================================================== + // 9. Describe the attachment set included with the communication + // (DescribeAttachment). + //====================================================================== + + print("Describing all attachments to the case...") + for attachmentID in attachmentIDList { + guard let attachment = await getAttachment(id: attachmentID) else { + print("Attachment details not obtained successfully.") + return + } + + print("Filename: \(attachment.fileName ?? "")") + print("Contents:") + + guard let bodyData = attachment.data else { + print("") + continue + } + + guard let body = String(data: bodyData, encoding: .utf8) else { + print("") + continue + } + + print(body) + print(String(repeating: "=", count: 78)) + } + + //====================================================================== + // 10. Resolve the support case (ResolveCase). + //====================================================================== + + print("Resolving the case...") + guard let status = await resolveCase(id: caseID) else { + print("Unable to resolve the case.") + return + } + print("Resolved the case. Status was previously \(status.previousStatus); now \(status.finalStatus).") + + //====================================================================== + // 11. Get a list of resolved cases for the current day after waiting + // for it to be resolved. This should now include the + // just-resolved case (DescribeCases). + //====================================================================== + + print("Getting today's resolved cases...") + let allCases = await getCases( + afterTime: isoDateFormatter.string(from: today), + includeResolved: true + ) + + for currentCase in allCases { + if currentCase.status == "resolved" { + print(" Case: \(currentCase.caseId ?? ""): final status is \(currentCase.status ?? "")") + } + } + + print("End of example.") + } + + // snippet-start:[swift.support.DescribeServices] + /// Return an array of the user's services. + /// + /// - Parameter supportClient: The `SupportClient` to use when calling + /// `describeServices()`. + /// + /// - Returns: An array of services. + func getServices() async -> [SupportClientTypes.Service] { + do { + let output = try await supportClient.describeServices( + input: DescribeServicesInput() + ) + + guard let services = output.services else { + return [] + } + + return services + } catch let error as AWSServiceError { + // SubscriptionRequiredException isn't a modeled error, so we + // have to catch AWSServiceError and then look at its errorCode to + // see if it's SubscriptionRequiredException. + if error.errorCode == "SubscriptionRequiredException" { + print("*** You need a subscription to use AWS Support.") + return [] + } else { + print("*** An unknown error occurred getting support information.") + return [] + } + } catch { + print("*** Error getting service information: \(error.localizedDescription)") + return [] + } + } + // snippet-end:[swift.support.DescribeServices] + + // snippet-start:[swift.support.DescribeSeverityLevels] + /// Returns the severity levels that can be applied to cases. + /// + /// - Returns: An array of `SupportClientTypes.SeverityLevel` objects + /// describing the available severity levels. + /// + /// The returned array is empty if there are either no available severity + /// levels, or if an error occurs. + func getSeverityLevels() async -> [SupportClientTypes.SeverityLevel] { + do { + let output = try await supportClient.describeSeverityLevels( + input: DescribeSeverityLevelsInput( + language: "en" + ) + ) + + guard let severityLevels = output.severityLevels else { + return [] + } + + return severityLevels + } catch let error as AWSServiceError { + // SubscriptionRequiredException isn't a modeled error, so we + // have to catch AWSServiceError and then look at its errorCode to + // see if it's SubscriptionRequiredException. + if error.errorCode == "SubscriptionRequiredException" { + print("*** You need a subscription to use AWS Support.") + return [] + } else { + print("*** An unknown error occurred getting the category list.") + return [] + } + } catch { + print("*** Error getting available severity levels: \(error.localizedDescription)") + return [] + } + } + // snippet-end:[swift.support.DescribeSeverityLevels] + + // snippet-start:[swift.support.CreateCase] + /// Create a new AWS Support case. + /// + /// - Parameters: + /// - service: The AWS service for which to create a case. + /// - category: The category under which to file the case. + /// - severity: The severity to apply to the case. + /// - subject: A brief description of the case. + /// - body: A more detailed description of the case. + /// + /// - Returns: A string containing the new case's ID, or `nil` if unable + /// to create the case. + func createCase(service: SupportClientTypes.Service, category: SupportClientTypes.Category, + severity: SupportClientTypes.SeverityLevel, subject: String, + body: String) async -> String? { + do { + let output = try await supportClient.createCase( + input: CreateCaseInput( + categoryCode: category.code, + communicationBody: body, + language: "en", + serviceCode: service.code, + severityCode: severity.code, + subject: subject + ) + ) + + return output.caseId + } catch let error as AWSServiceError { + // SubscriptionRequiredException isn't a modeled error, so we + // have to catch AWSServiceError and then look at its errorCode to + // see if it's SubscriptionRequiredException. + if error.errorCode == "SubscriptionRequiredException" { + print("*** You need a subscription to use AWS Support.") + return nil + } else { + print("*** An unknown error occurred creating the new AWS Support case.") + return nil + } + } catch is CaseCreationLimitExceeded { + print("*** Unable to create a new case because you have exceeded your case creation limit.") + return nil + } catch { + print("*** Error getting available severity levels: \(error.localizedDescription)") + return nil + } + } + // snippet-end:[swift.support.CreateCase] + + // snippet-start:[swift.support.DescribeCases] + /// Get a list of cases and their details, optionally after a particular + /// starting date and time. + /// + /// - Parameters: + /// - afterTime: The timestamp of the earliest cases to return, or nil + /// to include all cases. + /// - includeResolved: A Bool indicating whether or not to include cases + /// whose status is `resolved`. + /// + /// - Returns: An array of `SupportClientTypes.CaseDetails` objects + /// describing all matching cases. + func getCases(afterTime: String? = nil, includeResolved: Bool = false) async -> [SupportClientTypes.CaseDetails] { + do { + let pages = supportClient.describeCasesPaginated( + input: DescribeCasesInput( + afterTime: afterTime, + includeResolvedCases: includeResolved + ) + ) + + var allCases: [SupportClientTypes.CaseDetails] = [] + + for try await page in pages { + guard let caseList = page.cases else { + print("No cases returned.") + continue + } + + for aCase in caseList { + allCases.append(aCase) + } + } + + return allCases + } catch let error as AWSServiceError { + // SubscriptionRequiredException isn't a modeled error, so we + // have to catch AWSServiceError and then look at its errorCode to + // see if it's SubscriptionRequiredException. + if error.errorCode == "SubscriptionRequiredException" { + print("*** You need a subscription to use AWS Support.") + return [] + } else { + print("*** An unknown error occurred getting the AWS Support case list.") + return [] + } + } catch { + print("*** Error getting the list of cases: \(error.localizedDescription)") + return [] + } + } + // snippet-end:[swift.support.DescribeCases] + + // snippet-start:[swift.support.AddAttachmentsToSet] + /// Create a new AWS support case attachment set with a single attachment. + /// + /// - Parameters: + /// - name: The attachment's filename. + /// - body: The body of the attachment, as a `Data` object. + /// + /// - Returns: A string containing the new attachment set's ID. + func createAttachment(name: String, body: Data) async -> String? { + do { + let output = try await supportClient.addAttachmentsToSet( + input: AddAttachmentsToSetInput( + attachments: [ + SupportClientTypes.Attachment(data: body, fileName: name) + ] + ) + ) + + guard let attachmentSetID = output.attachmentSetId else { + print("No attachment set ID returned.") + return nil + } + + return attachmentSetID + } catch let error as AWSServiceError { + // SubscriptionRequiredException isn't a modeled error, so we + // have to catch AWSServiceError and then look at its errorCode to + // see if it's SubscriptionRequiredException. + if error.errorCode == "SubscriptionRequiredException" { + print("*** You need a subscription to use AWS Support.") + return nil + } else { + print("*** An unknown error occurred creating the attachment set.") + return nil + } + } catch is AttachmentLimitExceeded { + print("*** Too many attachment sets have been created in too short a time. Try again later.") + return nil + } catch is AttachmentSetSizeLimitExceeded { + print("*** You have exceeded the limit on the maximum number of attachments (3)") + print("or attachment size (5 MB per attachment).") + return nil + } catch { + print("*** Error creating the attachment set: \(error.localizedDescription)") + return nil + } + } + // snippet-end:[swift.support.AddAttachmentsToSet] + + // snippet-start:[swift.support.AddCommunicationToCase] + /// Add a communication to an AWS Support case, including an optional + /// attachment set. + /// + /// - Parameters: + /// - caseId: The ID of the case to add the communication to. + /// - body: The body text of the communication. + /// - attachmentSet: The attachment ID of an attachment set to add to + /// the communication, or `nil` if no attachment is desired. + /// + /// - Returns: A `Bool` indicating whether or not the communication was + /// successfully attached to the case. + func addCommunicationToCase(caseId: String, body: String, attachmentSet: String?) async -> Bool { + do { + let output = try await supportClient.addCommunicationToCase( + input: AddCommunicationToCaseInput( + attachmentSetId: attachmentSet, + caseId: caseId, + ccEmailAddresses: [], + communicationBody: body + ) + ) + + return output.result + } catch let error as AWSServiceError { + // SubscriptionRequiredException isn't a modeled error, so we + // have to catch AWSServiceError and then look at its errorCode to + // see if it's SubscriptionRequiredException. + if error.errorCode == "SubscriptionRequiredException" { + print("*** You need a subscription to use AWS Support.") + } else { + print("*** An unknown error occurred creating the communication.") + } + return false + } catch is AttachmentSetExpired { + print("*** The specified attachment set is expired.") + return false + } catch is CaseIdNotFound { + print("*** The specified case ID doesn't exist.") + return false + } catch is AttachmentSetIdNotFound { + print("The specified attachment set ID doesn't exist.") + return false + } catch { + print("*** Error creating the communication: \(error.localizedDescription)") + return false + } + } + // snippet-end:[swift.support.AddCommunicationToCase] + + // snippet-start:[swift.support.DescribeCommunications] + /// Get a list of the communications associated with a specific case. + /// + /// - Parameter caseId: The ID of the case for which to get + /// communications. + /// + /// - Returns: An array of `SupportClientTypes.Communication` records, + /// each describing one communication on the case. Returns `nil` if an + /// error occurs. + func getCommunications(caseId: String) async -> [SupportClientTypes.Communication]? { + do { + let output = try await supportClient.describeCommunications( + input: DescribeCommunicationsInput( + caseId: caseId + ) + ) + + return output.communications + } catch let error as AWSServiceError { + // SubscriptionRequiredException isn't a modeled error, so we + // have to catch AWSServiceError and then look at its errorCode to + // see if it's SubscriptionRequiredException. + if error.errorCode == "SubscriptionRequiredException" { + print("*** You need a subscription to use AWS Support.") + } else { + print("*** An unknown error occurred creating the communication.") + } + return nil + } catch is CaseIdNotFound { + print("*** The specified case ID doesn't exist.") + return nil + } catch { + print("*** Error getting the communications list: \(error.localizedDescription)") + return nil + } + } + // snippet-end:[swift.support.DescribeCommunications] + + // snippet-start:[swift.support.DescribeAttachment] + /// Return information about a specific attachment, given its ID. + /// + /// - Parameter id: The attachment's ID. + /// + /// - Returns: A `SupportClientTypes.Attachment` object describing the + /// specified attachment, or `nil` if no matching attachment is found or + /// an error occurs. + func getAttachment(id: String) async -> SupportClientTypes.Attachment? { + do { + let output = try await supportClient.describeAttachment( + input: DescribeAttachmentInput( + attachmentId: id + ) + ) + + return output.attachment + } catch let error as AWSServiceError { + // SubscriptionRequiredException isn't a modeled error, so we + // have to catch AWSServiceError and then look at its errorCode to + // see if it's SubscriptionRequiredException. + if error.errorCode == "SubscriptionRequiredException" { + print("*** You need a subscription to use AWS Support.") + } else { + print("*** An unknown error occurred retrieving the attachment.") + } + return nil + } catch is AttachmentIdNotFound { + print("*** The specified attachment ID doesn't exist.") + return nil + } catch is DescribeAttachmentLimitExceeded { + print("*** Too many attachment description requests in too short a period. Try again later.") + return nil + } catch { + print("*** Error getting the communications list: \(error.localizedDescription)") + return nil + } + } + // snippet-end:[swift.support.DescribeAttachment] + + // snippet-start:[swift.support.ResolveCase] + /// Resolve the specified AWS Support case. + /// + /// - Parameter id: The ID of the case to resolve. + /// + /// - Returns: A tuple containing the case's state prior to being + /// resolved, as well as its state after being resolved. Returns `nil` + /// if an error occurs resolving the case. + func resolveCase(id: String) async -> (previousStatus: String, finalStatus: String)? { + do { + let output = try await supportClient.resolveCase( + input: ResolveCaseInput(caseId: id) + ) + + return (output.initialCaseStatus ?? "", output.finalCaseStatus ?? "") + } catch let error as AWSServiceError { + // SubscriptionRequiredException isn't a modeled error, so we + // have to catch AWSServiceError and then look at its errorCode to + // see if it's SubscriptionRequiredException. + if error.errorCode == "SubscriptionRequiredException" { + print("*** You need a subscription to use AWS Support.") + } else { + print("*** An unknown error occurred resolving the case.") + } + return nil + } catch is CaseIdNotFound { + print("*** The specified case ID doesn't exist.") + return nil + } catch { + print("*** Error resolving the case: \(error.localizedDescription)") + return nil + } + } + // snippet-end:[swift.support.ResolveCase] +} +// snippet-end:[swift.support.scenario.scenario] diff --git a/swift/example_code/support/scenario/Sources/entry.swift b/swift/example_code/support/scenario/Sources/entry.swift new file mode 100644 index 00000000000..3e1e95d4c2c --- /dev/null +++ b/swift/example_code/support/scenario/Sources/entry.swift @@ -0,0 +1,48 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// snippet-start:[swift.support.scenario.entry] +// 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 AWSClientRuntime +import AWSSupport +import Foundation + +struct ExampleCommand: ParsableCommand { + @Option(help: "The AWS Region to run AWS API calls in.") + var awsRegion = "us-east-1" + + static var configuration = CommandConfiguration( + commandName: "support-scenario", + abstract: """ + Demonstrates various operations using Amazon Support. + """, + discussion: """ + """ + ) + + /// Called by ``main()`` to run the bulk of the example. + func runAsync() async throws { + let scenario = try await Scenario(region: awsRegion) + try await scenario.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.support.scenario.entry] From 443a1ea7d74a43d1d6c20c2ec58c4316d2f2f7b9 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Wed, 10 Sep 2025 15:39:35 +0000 Subject: [PATCH 03/10] Fix comments --- swift/example_code/support/hello/Sources/entry.swift | 2 +- swift/example_code/support/scenario/Sources/entry.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/swift/example_code/support/hello/Sources/entry.swift b/swift/example_code/support/hello/Sources/entry.swift index d4546aec47e..329573f26c1 100644 --- a/swift/example_code/support/hello/Sources/entry.swift +++ b/swift/example_code/support/hello/Sources/entry.swift @@ -3,7 +3,7 @@ // // snippet-start:[swift.support.hello] // An example that shows how to use the AWS SDK for Swift to perform a simple -// operation using Amazon Elastic Compute Cloud (EC2). +// operation using AWS Support. // import ArgumentParser diff --git a/swift/example_code/support/scenario/Sources/entry.swift b/swift/example_code/support/scenario/Sources/entry.swift index 3e1e95d4c2c..bd671b06771 100644 --- a/swift/example_code/support/scenario/Sources/entry.swift +++ b/swift/example_code/support/scenario/Sources/entry.swift @@ -3,7 +3,7 @@ // // snippet-start:[swift.support.scenario.entry] // An example that shows how to use the AWS SDK for Swift to perform a simple -// operation using Amazon Elastic Compute Cloud (EC2). +// operation using AWS Support. // import ArgumentParser From 019745405a94c2a87b8663b8d95bff2dfee6de54 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Fri, 19 Sep 2025 15:09:31 +0000 Subject: [PATCH 04/10] Fix executable name --- swift/example_code/support/scenario/Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/swift/example_code/support/scenario/Package.swift b/swift/example_code/support/scenario/Package.swift index a11dff49b11..79e125b0eb0 100644 --- a/swift/example_code/support/scenario/Package.swift +++ b/swift/example_code/support/scenario/Package.swift @@ -14,7 +14,7 @@ import PackageDescription let package = Package( - name: "hello-support", + name: "support-scenario", // Let Xcode know the minimum Apple platforms supported. platforms: [ .macOS(.v13), @@ -35,7 +35,7 @@ let package = Package( // Targets can depend on other targets in this package and products // from dependencies. .executableTarget( - name: "hello-support", + name: "support-scenario", dependencies: [ .product(name: "AWSSupport", package: "aws-sdk-swift"), .product(name: "ArgumentParser", package: "swift-argument-parser") From de5a42cbe360725437303b96f12570e37a6bfd6a Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Fri, 19 Sep 2025 17:28:27 +0000 Subject: [PATCH 05/10] Abort if no services found --- swift/example_code/support/scenario/Sources/Scenario.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/swift/example_code/support/scenario/Sources/Scenario.swift b/swift/example_code/support/scenario/Sources/Scenario.swift index 6ebb3ecb1e8..9368afb1baa 100644 --- a/swift/example_code/support/scenario/Sources/Scenario.swift +++ b/swift/example_code/support/scenario/Sources/Scenario.swift @@ -63,6 +63,11 @@ class Scenario { //====================================================================== let services = await getServices().sorted { $0.name ?? "" < $1.name ?? "" } + if services.count == 0 { + print("No services found. Exiting.") + return + } + print("Select a service:") for (index, service) in services.enumerated() { let numberPart = String(format: "%*d", 3, index+1) From 7cf8ece35bd13bb69902d67ada5ffa9754f41db1 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Fri, 19 Sep 2025 17:29:46 +0000 Subject: [PATCH 06/10] Add comment about business account needed --- swift/example_code/support/scenario/Sources/entry.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/swift/example_code/support/scenario/Sources/entry.swift b/swift/example_code/support/scenario/Sources/entry.swift index bd671b06771..bfae42b1be8 100644 --- a/swift/example_code/support/scenario/Sources/entry.swift +++ b/swift/example_code/support/scenario/Sources/entry.swift @@ -2,8 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 // // snippet-start:[swift.support.scenario.entry] -// An example that shows how to use the AWS SDK for Swift to perform a simple -// operation using AWS Support. +// An example that shows how to use the AWS SDK for Swift to perform a series +// of operations using AWS Support +// +// NOTE: You must have a business class AWS account to use AWS Support +// features. // import ArgumentParser From b7495237e670385d3219da68eefd3ad84b208d44 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Fri, 19 Sep 2025 17:34:41 +0000 Subject: [PATCH 07/10] Add link to AWS Support plans info --- swift/example_code/support/scenario/Sources/entry.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swift/example_code/support/scenario/Sources/entry.swift b/swift/example_code/support/scenario/Sources/entry.swift index bfae42b1be8..acc69f99fb3 100644 --- a/swift/example_code/support/scenario/Sources/entry.swift +++ b/swift/example_code/support/scenario/Sources/entry.swift @@ -6,7 +6,7 @@ // of operations using AWS Support // // NOTE: You must have a business class AWS account to use AWS Support -// features. +// features. See https://aws.amazon.com/premiumsupport/plans/. // import ArgumentParser From fc9ce6666f508587695da971dc24041902b02cf5 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Fri, 19 Sep 2025 17:37:45 +0000 Subject: [PATCH 08/10] Add enhanced error information and other output --- swift/example_code/support/scenario/Sources/Scenario.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/swift/example_code/support/scenario/Sources/Scenario.swift b/swift/example_code/support/scenario/Sources/Scenario.swift index 9368afb1baa..eac7a3cd00b 100644 --- a/swift/example_code/support/scenario/Sources/Scenario.swift +++ b/swift/example_code/support/scenario/Sources/Scenario.swift @@ -62,6 +62,8 @@ class Scenario { // DescribeServices. //====================================================================== + print("Getting available services...") + let services = await getServices().sorted { $0.name ?? "" < $1.name ?? "" } if services.count == 0 { print("No services found. Exiting.") @@ -307,7 +309,7 @@ class Scenario { print("*** You need a subscription to use AWS Support.") return [] } else { - print("*** An unknown error occurred getting support information.") + print("*** An unanticipated error occurred getting support information: \(error.errorCode ?? "").") return [] } } catch { From a8c502e269370bea57de93ad6e053bbd9bb78335 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Fri, 19 Sep 2025 19:21:24 +0000 Subject: [PATCH 09/10] Enhance error; improve comments. --- swift/example_code/support/scenario/Sources/Scenario.swift | 2 +- swift/example_code/support/scenario/Sources/entry.swift | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/swift/example_code/support/scenario/Sources/Scenario.swift b/swift/example_code/support/scenario/Sources/Scenario.swift index eac7a3cd00b..7be03d9ddf4 100644 --- a/swift/example_code/support/scenario/Sources/Scenario.swift +++ b/swift/example_code/support/scenario/Sources/Scenario.swift @@ -309,7 +309,7 @@ class Scenario { print("*** You need a subscription to use AWS Support.") return [] } else { - print("*** An unanticipated error occurred getting support information: \(error.errorCode ?? "").") + print("*** An unanticipated error occurred getting support information: \(error.message ?? "") (\(error.errorCode ?? "")).") return [] } } catch { diff --git a/swift/example_code/support/scenario/Sources/entry.swift b/swift/example_code/support/scenario/Sources/entry.swift index acc69f99fb3..ee4109e93be 100644 --- a/swift/example_code/support/scenario/Sources/entry.swift +++ b/swift/example_code/support/scenario/Sources/entry.swift @@ -5,9 +5,9 @@ // An example that shows how to use the AWS SDK for Swift to perform a series // of operations using AWS Support // -// NOTE: You must have a business class AWS account to use AWS Support -// features. See https://aws.amazon.com/premiumsupport/plans/. -// +// NOTE: You must have one of the following AWS Support plans to use the AWS +// Support API: Business, Enterprise On-Ramp, or Enterprise. For more +// information, see: https://aws.amazon.com/premiumsupport/plans/. import ArgumentParser import AWSClientRuntime From 4fd3ef6f55d3ff5d259cdc9b2db5400df9b18a41 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Fri, 19 Sep 2025 19:25:39 +0000 Subject: [PATCH 10/10] Update readme --- swift/example_code/support/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/swift/example_code/support/README.md b/swift/example_code/support/README.md index 61affc4a584..0f3c4a84d73 100644 --- a/swift/example_code/support/README.md +++ b/swift/example_code/support/README.md @@ -45,15 +45,15 @@ Code examples that show you how to perform the essential operations within a ser Code excerpts that show you how to call individual service functions. -- [AddAttachmentsToSet](scenario/Sources/Scenario.swift#L456) -- [AddCommunicationToCase](scenario/Sources/Scenario.swift#L505) -- [CreateCase](scenario/Sources/Scenario.swift#L354) -- [DescribeAttachment](scenario/Sources/Scenario.swift#L593) -- [DescribeCases](scenario/Sources/Scenario.swift#L403) -- [DescribeCommunications](scenario/Sources/Scenario.swift#L555) -- [DescribeServices](scenario/Sources/Scenario.swift#L279) -- [DescribeSeverityLevels](scenario/Sources/Scenario.swift#L315) -- [ResolveCase](scenario/Sources/Scenario.swift#L633) +- [AddAttachmentsToSet](scenario/Sources/Scenario.swift#L463) +- [AddCommunicationToCase](scenario/Sources/Scenario.swift#L512) +- [CreateCase](scenario/Sources/Scenario.swift#L361) +- [DescribeAttachment](scenario/Sources/Scenario.swift#L600) +- [DescribeCases](scenario/Sources/Scenario.swift#L410) +- [DescribeCommunications](scenario/Sources/Scenario.swift#L562) +- [DescribeServices](scenario/Sources/Scenario.swift#L286) +- [DescribeSeverityLevels](scenario/Sources/Scenario.swift#L322) +- [ResolveCase](scenario/Sources/Scenario.swift#L640)