From e13c1d6388bb77335c2c5c79ffed8e81ba3d5ffb Mon Sep 17 00:00:00 2001 From: Mathew Gacy Date: Tue, 22 Oct 2024 12:48:48 -0700 Subject: [PATCH] Strict Concurrency Support (#10) * Declare Sendable conformances * Enable strict concurrency checking * Address Sendable warnings * Fix warning * Upgrade AWS SDK * Replace use of ISO8601DateFormatter * Replace SwiftLint plugin with Danger Swift * SwiftLint fixes * Remove macOS directive * Update toolchain * Replace @retroactive with fully qualified names --- .github/workflows/danger.yml | 26 ++++ Dangerfile.swift | 15 +++ Package.resolved | 111 +----------------- Package.swift | 15 +-- README.md | 2 +- Sources/EmailSender/EmailSender.swift | 4 +- Sources/Persistence/Persistence.swift | 6 +- Sources/Persistence/TimestampProvider.swift | 25 ++-- Sources/Secrets/SecretValue.swift | 16 +++ Sources/Secrets/Secrets.swift | 2 +- Tests/PersistenceTests/PersistenceTests.swift | 4 +- .../TimestampProviderTests.swift | 4 +- 12 files changed, 95 insertions(+), 135 deletions(-) create mode 100644 .github/workflows/danger.yml create mode 100644 Dangerfile.swift diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml new file mode 100644 index 0000000..3e2018a --- /dev/null +++ b/.github/workflows/danger.yml @@ -0,0 +1,26 @@ +name: Run Danger +on: + workflow_dispatch: + pull_request: + types: + - opened + - reopened + - synchronize + - ready_for_review + +jobs: + build: + if: github.event.pull_request.draft == false + name: Run Danger + runs-on: ubuntu-latest + permissions: + contents: write + issues: write + pull-requests: write + steps: + - name: Git checkout + uses: actions/checkout@v4 + - name: Danger + uses: 417-72KI/danger-swiftlint@v5.9 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Dangerfile.swift b/Dangerfile.swift new file mode 100644 index 0000000..803fffc --- /dev/null +++ b/Dangerfile.swift @@ -0,0 +1,15 @@ +import Danger + +extension String: Error {} + +let danger = Danger() + +if danger.github.pullRequest.body == nil { + danger.fail("Please add a description to this Pull Request") +} + +SwiftLint + .lint( + .all(directory: nil), + configFile: ".swiftlint.yml" + ) diff --git a/Package.resolved b/Package.resolved index 15d894b..e4ff60f 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/awslabs/aws-crt-swift", "state" : { - "revision" : "fd1756b6e5c9fd1a906edfb743f7cb64c2c98639", - "version" : "0.17.0" + "revision" : "7b42e0343f28b3451aab20840dc670abd12790bd", + "version" : "0.36.0" } }, { @@ -14,26 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/awslabs/aws-sdk-swift", "state" : { - "revision" : "3d2cfde16273c8fabd02416647d88e2824bded90", - "version" : "0.32.0" - } - }, - { - "identity" : "collectionconcurrencykit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/JohnSundell/CollectionConcurrencyKit.git", - "state" : { - "revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95", - "version" : "0.2.0" - } - }, - { - "identity" : "cryptoswift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", - "state" : { - "revision" : "db51c407d3be4a051484a141bf0bff36c43d3b1e", - "version" : "1.8.0" + "revision" : "57b74dba32d24e52d07953a2ce5f9d3d27cbc975", + "version" : "1.0.24" } }, { @@ -41,35 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/smithy-lang/smithy-swift", "state" : { - "revision" : "ca28bf2c2b44ceb8c60b1e72b08f63aebc9b68b6", - "version" : "0.36.0" - } - }, - { - "identity" : "sourcekitten", - "kind" : "remoteSourceControl", - "location" : "https://github.com/jpsim/SourceKitten.git", - "state" : { - "revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56", - "version" : "0.34.1" - } - }, - { - "identity" : "swift-argument-parser", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-argument-parser.git", - "state" : { - "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531", - "version" : "1.2.3" - } - }, - { - "identity" : "swift-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-collections", - "state" : { - "revision" : "a902f1823a7ff3c9ab2fba0f992396b948eda307", - "version" : "1.0.5" + "revision" : "041be5fd2755309fb3132fffc43b12a26c8bf6a8", + "version" : "0.82.0" } }, { @@ -80,60 +35,6 @@ "revision" : "532d8b529501fb73a2455b179e0bbb6d49b652ed", "version" : "1.5.3" } - }, - { - "identity" : "swift-syntax", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-syntax.git", - "state" : { - "revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036", - "version" : "509.0.2" - } - }, - { - "identity" : "swiftlint", - "kind" : "remoteSourceControl", - "location" : "https://github.com/realm/SwiftLint.git", - "state" : { - "revision" : "f17a4f9dfb6a6afb0408426354e4180daaf49cee", - "version" : "0.54.0" - } - }, - { - "identity" : "swiftytexttable", - "kind" : "remoteSourceControl", - "location" : "https://github.com/scottrhoyt/SwiftyTextTable.git", - "state" : { - "revision" : "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3", - "version" : "0.9.0" - } - }, - { - "identity" : "swxmlhash", - "kind" : "remoteSourceControl", - "location" : "https://github.com/drmohundro/SWXMLHash.git", - "state" : { - "revision" : "a853604c9e9a83ad9954c7e3d2a565273982471f", - "version" : "7.0.2" - } - }, - { - "identity" : "xmlcoder", - "kind" : "remoteSourceControl", - "location" : "https://github.com/MaxDesiatov/XMLCoder.git", - "state" : { - "revision" : "80b4a1646399b8e4e0ce80711653476a85bd5e37", - "version" : "0.17.0" - } - }, - { - "identity" : "yams", - "kind" : "remoteSourceControl", - "location" : "https://github.com/jpsim/Yams.git", - "state" : { - "revision" : "0d9ee7ea8c4ebd4a489ad7a73d5c6cad55d6fed3", - "version" : "5.0.6" - } } ], "version" : 2 diff --git a/Package.swift b/Package.swift index da5d3a2..afc537a 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.7 +// swift-tools-version: 5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -12,7 +12,7 @@ let package = Package( .library(name: "Secrets", targets: ["Secrets"]) ], dependencies: [ - .package(url: "https://github.com/awslabs/aws-sdk-swift.git", from: "0.19.0") + .package(url: "https://github.com/awslabs/aws-sdk-swift.git", from: "1.0.0") ], targets: [ .testTarget( @@ -30,7 +30,7 @@ let package = Package( ] ) -let genericTargets: [Target] = [ +let regularTargets: [Target] = [ .target( name: "EmailSender", dependencies: [ @@ -51,11 +51,8 @@ let genericTargets: [Target] = [ ) ] -#if os(macOS) -package.dependencies.append(.package(url: "https://github.com/realm/SwiftLint.git", exact: "0.54.0")) -for target in genericTargets { - target.plugins = [.plugin(name: "SwiftLintPlugin", package: "SwiftLint")] +for target in regularTargets { + target.swiftSettings = [.enableExperimentalFeature("StrictConcurrency")] } -#endif -package.targets.append(contentsOf: genericTargets) +package.targets.append(contentsOf: regularTargets) diff --git a/README.md b/README.md index 8ac7090..8526f72 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Swifty helpers for working with the Swift [AWS SDK](https://github.com/awslabs/a ## 📱 Requirements -Swift 5.7 toolchain with Swift Package Manager. +Swift 5.9 toolchain with Swift Package Manager. ## 🖥 Installation diff --git a/Sources/EmailSender/EmailSender.swift b/Sources/EmailSender/EmailSender.swift index 8e1d0d5..fc754ea 100644 --- a/Sources/EmailSender/EmailSender.swift +++ b/Sources/EmailSender/EmailSender.swift @@ -5,11 +5,11 @@ // Created by Mathew Gacy on 12/8/23. // -import AWSSES +@preconcurrency import AWSSES import Foundation /// A type that sends emails. -public struct EmailSender { +public struct EmailSender: Sendable { /// A closure returning a message ID after sending an email. public var send: @Sendable (Recipients, Sender, Subject, Body) async throws -> MessageID? diff --git a/Sources/Persistence/Persistence.swift b/Sources/Persistence/Persistence.swift index dc0fac6..885be52 100644 --- a/Sources/Persistence/Persistence.swift +++ b/Sources/Persistence/Persistence.swift @@ -5,7 +5,7 @@ // Created by Mathew Gacy on 12/8/23. // -import AWSDynamoDB +@preconcurrency import AWSDynamoDB import Foundation /// Represents the data for an attribute. Each attribute value is described as a name-value pair. @@ -15,7 +15,7 @@ import Foundation public typealias AttributeValue = DynamoDBClientTypes.AttributeValue /// A type that persists collections of attributes. -public struct Persistence { +public struct Persistence: Sendable { /// A closure to modify the attributes of persisted values. /// /// Use this to add additional attributes like a timestamp or to perform validation of all @@ -67,7 +67,7 @@ public extension Persistence { } /// A type that creates ``Persistence`` instances. -public struct PersistenceFactory { +public struct PersistenceFactory: Sendable { /// The region where the table is located. public typealias Region = String diff --git a/Sources/Persistence/TimestampProvider.swift b/Sources/Persistence/TimestampProvider.swift index 8fa22a6..c67c376 100644 --- a/Sources/Persistence/TimestampProvider.swift +++ b/Sources/Persistence/TimestampProvider.swift @@ -8,7 +8,7 @@ import Foundation /// A class of types providing user-readable representations of dates. -public protocol DateFormatting { +public protocol DateFormatting: Sendable { /// Returns a string representation of the specified date. /// /// - Parameter date: The date to be represented. @@ -16,13 +16,20 @@ public protocol DateFormatting { func string(from date: Date) -> String } -extension DateFormatter: DateFormatting {} - -extension ISO8601DateFormatter: DateFormatting {} +extension DateFormatter: DateFormatting { + public static let iso8601: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" + formatter.calendar = Calendar(identifier: .iso8601) + formatter.timeZone = TimeZone(secondsFromGMT: 0) + formatter.locale = Locale(identifier: "en_US_POSIX") + return formatter + }() +} /// A type to provide timestamps. -public struct TimestampProvider { - private let dateProvider: () -> Date +public struct TimestampProvider: Sendable { + private let dateProvider: @Sendable () -> Date private let formatter: any DateFormatting /// Creates an instance. @@ -31,7 +38,7 @@ public struct TimestampProvider { /// - dateProvider: A closure returning the current date. /// - formatter: The date formatter to use to format the current date. public init( - dateProvider: @escaping () -> Date, + dateProvider: @escaping @Sendable () -> Date, formatter: DateFormatting ) { self.dateProvider = dateProvider @@ -48,7 +55,7 @@ public extension TimestampProvider { /// A live implementation. static var live: Self { .init( - dateProvider: Date.init, - formatter: ISO8601DateFormatter()) + dateProvider: { Date() }, + formatter: DateFormatter.iso8601) } } diff --git a/Sources/Secrets/SecretValue.swift b/Sources/Secrets/SecretValue.swift index 415cdf3..ff72c01 100644 --- a/Sources/Secrets/SecretValue.swift +++ b/Sources/Secrets/SecretValue.swift @@ -32,6 +32,22 @@ protocol SecretValue: Equatable { var secretString: String? { get } } +extension AWSSecretsManager.GetSecretValueOutput: Swift.Equatable { + public static func == (lhs: GetSecretValueOutput, rhs: GetSecretValueOutput) -> Bool { + lhs.arn == rhs.arn + && lhs.name == rhs.name + && lhs.secretBinary == rhs.secretBinary + && lhs.secretString == rhs.secretString + } +} extension GetSecretValueOutput: SecretValue {} +extension AWSSecretsManager.SecretsManagerClientTypes.SecretValueEntry: Swift.Equatable { + public static func == (lhs: SecretsManagerClientTypes.SecretValueEntry, rhs: SecretsManagerClientTypes.SecretValueEntry) -> Bool { + lhs.arn == rhs.arn + && lhs.name == rhs.name + && lhs.secretBinary == rhs.secretBinary + && lhs.secretString == rhs.secretString + } +} extension SecretsManagerClientTypes.SecretValueEntry: SecretValue {} diff --git a/Sources/Secrets/Secrets.swift b/Sources/Secrets/Secrets.swift index 477588a..3c4153f 100644 --- a/Sources/Secrets/Secrets.swift +++ b/Sources/Secrets/Secrets.swift @@ -5,7 +5,7 @@ // Created by Mathew Gacy on 12/22/23. // -import AWSSecretsManager +@preconcurrency import AWSSecretsManager import Foundation /// A secret stored by AWS Secrets Manager. diff --git a/Tests/PersistenceTests/PersistenceTests.swift b/Tests/PersistenceTests/PersistenceTests.swift index 4e7d5bb..8627dfb 100644 --- a/Tests/PersistenceTests/PersistenceTests.swift +++ b/Tests/PersistenceTests/PersistenceTests.swift @@ -24,7 +24,7 @@ final class PersistenceTests: XCTestCase { } } - let timeoutInterval: TimeInterval = 0.1 + let timeoutInterval: TimeInterval = 0.1 func testPersistence() async throws { let expected: [String: AttributeValue] = [ @@ -35,7 +35,7 @@ final class PersistenceTests: XCTestCase { let timestampProvider = TimestampProvider( dateProvider: { Date(timeIntervalSince1970: 0) }, - formatter: ISO8601DateFormatter()) + formatter: DateFormatter.iso8601) let expectation = expectation(description: "Model persisted") let sut = Persistence.addingTimestamp(named: "CreatedAt", from: timestampProvider) { actual in diff --git a/Tests/PersistenceTests/TimestampProviderTests.swift b/Tests/PersistenceTests/TimestampProviderTests.swift index 5f3c238..1528ee1 100644 --- a/Tests/PersistenceTests/TimestampProviderTests.swift +++ b/Tests/PersistenceTests/TimestampProviderTests.swift @@ -5,8 +5,6 @@ // Created by Mathew Gacy on 12/8/23. // -import Foundation - @testable import Persistence import Foundation import XCTest @@ -17,7 +15,7 @@ final class TimestampProviderTests: XCTestCase { let sut = TimestampProvider( dateProvider: { Date(timeIntervalSince1970: 0) }, - formatter: ISO8601DateFormatter()) + formatter: DateFormatter.iso8601) let actual = sut.timestamp() XCTAssertEqual(actual, expected) }