Skip to content

Commit

Permalink
Swift 6 language mode (#199)
Browse files Browse the repository at this point in the history
* Enable Swift 6 language mode

* Print when saving file

* Add Sendable to Error types

* Add Sendable to options

* Add Error to ErrorResponse

* Add commented out test

* Await Printer init on pre-Swift 6.0

* Workaround missing @retroactive attribute in pre-Swift 6

* Workaround missing @retroactive attribute in pre-Swift 6

* More test coverage
  • Loading branch information
MortenGregersen authored Sep 29, 2024
1 parent d6e618e commit e31637f
Show file tree
Hide file tree
Showing 60 changed files with 831 additions and 480 deletions.
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,5 @@ let package = Package(
.testTarget(name: "BagbutikDocsCollectorTests", dependencies: ["BagbutikDocsCollector"]),
.testTarget(name: "BagbutikSpecDecoderTests", dependencies: ["BagbutikSpecDecoder"]),
.testTarget(name: "BagbutikStringExtensionsTests", dependencies: ["BagbutikStringExtensions"]),
])
],
swiftLanguageVersions: [.v5, .version("6")])
9 changes: 0 additions & 9 deletions Sources/Bagbutik-Core/Internal/DateFactory.swift

This file was deleted.

8 changes: 4 additions & 4 deletions Sources/Bagbutik-Core/Models/ErrorLinks.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

public struct ErrorLinks: Codable {
public struct ErrorLinks: Codable, Sendable {
public var about: String?
public var associated: Associated?

Expand All @@ -23,7 +23,7 @@ public struct ErrorLinks: Codable {
try container.encodeIfPresent(associated, forKey: "associated")
}

public enum Associated: Codable {
public enum Associated: Codable, Sendable {
case properties(Properties)
case string(String)

Expand All @@ -47,7 +47,7 @@ public struct ErrorLinks: Codable {
}
}

public struct Properties: Codable {
public struct Properties: Codable, Sendable {
public var href: String?
public var meta: Meta?

Expand All @@ -70,7 +70,7 @@ public struct ErrorLinks: Codable {
try container.encodeIfPresent(meta, forKey: "meta")
}

public struct Meta: Codable {
public struct Meta: Codable, Sendable {
public var source: String?

public init(source: String? = nil) {
Expand Down
8 changes: 4 additions & 4 deletions Sources/Bagbutik-Core/Models/ErrorResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Foundation
Full documentation:
<https://developer.apple.com/documentation/appstoreconnectapi/errorresponse>
*/
public struct ErrorResponse: Codable {
public struct ErrorResponse: Codable, Error {
/// An array of one or more errors.
public var errors: [Errors]?

Expand All @@ -34,7 +34,7 @@ public struct ErrorResponse: Codable {
Full documentation:
<https://developer.apple.com/documentation/appstoreconnectapi/errorresponse/errors>
*/
public struct Errors: Codable, Identifiable {
public struct Errors: Codable, Identifiable, Sendable {
/// A machine-readable code indicating the type of error. The code is a hierarchical value with levels of specificity separated by the '`.`' character. This value is parseable for programmatic error handling in code.
public let code: String
/// A detailed explanation of the error. Do not use this field for programmatic error handling.
Expand Down Expand Up @@ -93,7 +93,7 @@ public struct ErrorResponse: Codable {
try container.encode(title, forKey: "title")
}

public struct Meta: Codable {
public struct Meta: Codable, Sendable {
public var additionalProperties: [String: String]?
public var associatedErrors: [String: [Errors]]?

Expand All @@ -117,7 +117,7 @@ public struct ErrorResponse: Codable {
}
}

public enum Source: Codable {
public enum Source: Codable, Sendable {
case jsonPointer(JsonPointer)
case parameter(Parameter)

Expand Down
2 changes: 1 addition & 1 deletion Sources/Bagbutik-Core/Models/JsonPointer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation
Full documentation:
<https://developer.apple.com/documentation/appstoreconnectapi/jsonpointer>
*/
public struct JsonPointer: Codable {
public struct JsonPointer: Codable, Sendable {
/// A JSON pointer that indicates the location in the request entity where the error originates.
public let pointer: String

Expand Down
2 changes: 1 addition & 1 deletion Sources/Bagbutik-Core/Models/Parameter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Foundation
Full documentation:
<https://developer.apple.com/documentation/appstoreconnectapi/parameter>
*/
public struct Parameter: Codable {
public struct Parameter: Codable, Sendable {
/// The query parameter that produced the error.
public let parameter: String

Expand Down
5 changes: 2 additions & 3 deletions Sources/Bagbutik-Core/Service/BagbutikService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public typealias FetchData = (_ request: URLRequest, _ delegate: URLSessionTaskD
If the JWT has expired, it will be renewed before the request is performed.
*/
public class BagbutikService {
internal private(set) var jwt: JWT
internal var jwt: JWT
private let fetchData: FetchData

/**
Expand All @@ -47,13 +47,12 @@ public class BagbutikService {

private static let jsonDecoder: JSONDecoder = {
let decoder = JSONDecoder()
let isoDateFormatter = ISO8601DateFormatter()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
decoder.dateDecodingStrategy = .custom { decoder in
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
if let date = isoDateFormatter.date(from: dateString) {
if let date = ISO8601DateFormatter().date(from: dateString) {
return date
} else if let date = dateFormatter.date(from: dateString) {
return date
Expand Down
47 changes: 33 additions & 14 deletions Sources/Bagbutik-Core/Service/JWT.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public struct JWT {
private var payload: Payload
private let privateKey: String

internal var dateFactory: (TimeInterval) -> Date {
didSet { payload.dateFactory = dateFactory }
}

/**
Create a new JWT.
Expand All @@ -34,10 +38,7 @@ public struct JWT {
- privateKey: The contents of your private key from App Store Connect. Starting with `-----BEGIN PRIVATE KEY-----`.
*/
public init(keyId: String, issuerId: String, privateKey: String) throws {
header = Header(kid: keyId)
payload = Payload(iss: issuerId)
self.privateKey = privateKey
encodedSignature = try Self.createEncodedSignature(header: header, payload: payload, privateKey: privateKey)
try self.init(keyId: keyId, issuerId: issuerId, privateKey: privateKey, dateFactory: Date.init(timeIntervalSinceNow:))
}

/**
Expand All @@ -56,7 +57,15 @@ public struct JWT {
try self.init(keyId: keyId, issuerId: issuerId, privateKey: privateKey)
}

internal mutating func renewEncodedSignature() throws {
init(keyId: String, issuerId: String, privateKey: String, dateFactory: @escaping (TimeInterval) -> Date) throws {
header = Header(kid: keyId)
payload = Payload(iss: issuerId, dateFactory: dateFactory)
self.privateKey = privateKey
encodedSignature = try Self.createEncodedSignature(header: header, payload: payload, privateKey: privateKey)
self.dateFactory = dateFactory
}

mutating func renewEncodedSignature() throws {
payload.renewExp()
encodedSignature = try Self.createEncodedSignature(header: header, payload: payload, privateKey: privateKey)
}
Expand All @@ -79,22 +88,32 @@ public struct JWT {
}

private struct Payload: Encodable {
var iss: String
var exp: Int
let iss: String
private(set) var exp: Int
let aud = "appstoreconnect-v1"
var isExpired: Bool { Date(timeIntervalSince1970: TimeInterval(exp)) < DateFactory.fromTimeIntervalSinceNow(0) }
var dateFactory: (TimeInterval) -> Date
var isExpired: Bool { Date(timeIntervalSince1970: TimeInterval(exp)) < Date(timeIntervalSinceNow: 0) }

init(iss: String) {
init(iss: String, dateFactory: @escaping (TimeInterval) -> Date) {
self.iss = iss
exp = Self.createExp()
self.dateFactory = dateFactory
exp = 0
renewExp()
}

mutating func renewExp() {
exp = Self.createExp()
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(iss, forKey: .iss)
try container.encode(exp, forKey: .exp)
try container.encode(aud, forKey: .aud)
}

enum CodingKeys: String, CodingKey {
case iss, exp, aud
}

private static func createExp() -> Int {
Int(DateFactory.fromTimeIntervalSinceNow(20 * 60).timeIntervalSince1970)
mutating func renewExp() {
exp = Int(dateFactory(20 * 60).timeIntervalSince1970)
}
}
}

This file was deleted.

10 changes: 5 additions & 5 deletions Sources/BagbutikCLI/BagbutikCLI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import FoundationNetworking

@main
struct BagbutikCLI: AsyncParsableCommand {
static var configuration = CommandConfiguration(
static let configuration = CommandConfiguration(
abstract: "A utility for downloading spec and generating models and endpoints.",
subcommands: [Generate.self, CheckFeedback.self, DownloadNewestDocs.self, DownloadNewestSpec.self],
defaultSubcommand: Generate.self)

struct Generate: AsyncParsableCommand {
static var configuration = CommandConfiguration(abstract: "Generate models and endpoints from spec. If the path to a spec is omitted, the newest version is downloaded from Apple.")
static let configuration = CommandConfiguration(abstract: "Generate models and endpoints from spec. If the path to a spec is omitted, the newest version is downloaded from Apple.")

@Option(name: .shortAndLong, help: "Path to the App Store Connect OpenAPI Spec")
var specPath: String?
Expand All @@ -42,7 +42,7 @@ struct BagbutikCLI: AsyncParsableCommand {
}

struct CheckFeedback: AsyncParsableCommand {
static var configuration = CommandConfiguration(abstract: "Check if the manual applied patched to the specs are still needed. If the path to a spec is omitted, the newest version is downloaded from Apple.")
static let configuration = CommandConfiguration(abstract: "Check if the manual applied patched to the specs are still needed. If the path to a spec is omitted, the newest version is downloaded from Apple.")

@Option(name: .shortAndLong, help: "Path to the App Store Connect OpenAPI Spec")
var specPath: String?
Expand All @@ -64,7 +64,7 @@ struct BagbutikCLI: AsyncParsableCommand {
}

struct DownloadNewestDocs: AsyncParsableCommand {
static var configuration = CommandConfiguration(abstract: "Download the newest documentation.")
static let configuration = CommandConfiguration(abstract: "Download the newest documentation.")

@Option(name: .shortAndLong, help: "Path to the App Store Connect OpenAPI Spec")
var specPath: String?
Expand All @@ -86,7 +86,7 @@ struct BagbutikCLI: AsyncParsableCommand {
}

struct DownloadNewestSpec: AsyncParsableCommand {
static var configuration = CommandConfiguration(abstract: "Download the newest spec.")
static let configuration = CommandConfiguration(abstract: "Download the newest spec.")

mutating func run() async throws {
try await downloadNewestSpec()
Expand Down
2 changes: 1 addition & 1 deletion Sources/BagbutikDocsCollector/DocsFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public enum DocsFetcherError: Error {
case couldNotCreateFile

/// The type of the file URL
public enum FileURLType {
public enum FileURLType: Sendable {
/// The URL for the spec file
case specFileURL
/// The URL for the output directory
Expand Down
16 changes: 8 additions & 8 deletions Sources/BagbutikDocsCollector/DocsLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,27 @@ public enum DocsLoaderError: Error, Equatable {
case couldNotResolvePackageName(id: String, paths: [[String]])
}

public class DocsLoader {
private let loadFile: (URL) throws -> Data
public actor DocsLoader {
private let loadFile: @MainActor (URL) throws -> Data
var operationDocumentationById: [String: OperationDocumentation]?
var identifierBySchemaName: [String: String]?
var schemaDocumentationById: [String: Documentation]?

public convenience init() {
public init() {
self.init(operationDocumentationById: nil) // A parameter is needed for it to call the internal initializer
}

init(loadFile: @escaping (URL) throws -> Data = { url in try Data(contentsOf: url) }, operationDocumentationById: [String: OperationDocumentation]? = nil, identifierBySchemaName: [String: String]? = nil, schemaDocumentationById: [String: Documentation]? = nil) {
init(loadFile: @escaping @MainActor (URL) throws -> Data = { url in try Data(contentsOf: url) }, operationDocumentationById: [String: OperationDocumentation]? = nil, identifierBySchemaName: [String: String]? = nil, schemaDocumentationById: [String: Documentation]? = nil) {
self.loadFile = loadFile
self.operationDocumentationById = operationDocumentationById
self.identifierBySchemaName = identifierBySchemaName
self.schemaDocumentationById = schemaDocumentationById
}

public func loadDocs(documentationDirURL: URL) throws {
let operationDocumentationByIdData = try loadFile(documentationDirURL.appendingPathComponent(DocsFilename.operationDocumentation.filename))
let identifierBySchemaNameData = try loadFile(documentationDirURL.appendingPathComponent(DocsFilename.schemaMapping.filename))
let schemaDocumentationByIdData = try loadFile(documentationDirURL.appendingPathComponent(DocsFilename.schemaDocumentation.filename))
public func loadDocs(documentationDirURL: URL) async throws {
let operationDocumentationByIdData = try await loadFile(documentationDirURL.appendingPathComponent(DocsFilename.operationDocumentation.filename))
let identifierBySchemaNameData = try await loadFile(documentationDirURL.appendingPathComponent(DocsFilename.schemaMapping.filename))
let schemaDocumentationByIdData = try await loadFile(documentationDirURL.appendingPathComponent(DocsFilename.schemaDocumentation.filename))
let jsonDecoder = JSONDecoder()
self.operationDocumentationById = try jsonDecoder.decode([String: Documentation].self, from: operationDocumentationByIdData).mapValues { documentation in
guard case .operation(let operationDocumentation) = documentation else { throw DocsLoaderError.wrongTypeOfDocumentation }
Expand Down
8 changes: 4 additions & 4 deletions Sources/BagbutikDocsCollector/Models/Documentation.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import BagbutikSpecDecoder
import Foundation

public enum Documentation: Codable, Equatable {
public enum Documentation: Codable, Equatable, Sendable {
case `enum`(EnumDocumentation)
case object(ObjectDocumentation)
case operation(OperationDocumentation)
Expand Down Expand Up @@ -133,7 +133,7 @@ public enum Documentation: Codable, Equatable {
discussion: discussion,
cases: values))
} else if metadata.symbolKind == "dict" /* Object */ {
let properties: [Property] = contentSections.compactMap { contentSection in
let properties: [Property] = contentSections.compactMap { (contentSection: ContentSection) -> [Property]? in
guard case .properties(let properties) = contentSection else { return nil }
return properties
}.first ?? []
Expand Down Expand Up @@ -170,7 +170,7 @@ public enum Documentation: Codable, Equatable {
.filter { $0.lengthOfBytes(using: .utf8) > 0 }
.joined(separator: "\n")
}.first
let responses: [Response] = contentSections.compactMap { contentSection in
let responses: [Response] = contentSections.compactMap { (contentSection: ContentSection) -> [Response]? in
guard case .restResponses(let responses) = contentSection else { return nil }
return responses
}.first ?? []
Expand Down Expand Up @@ -260,7 +260,7 @@ public enum Documentation: Codable, Equatable {
let text: String
}

public struct Hierarchy: Codable, Equatable {
public struct Hierarchy: Codable, Equatable, Sendable {
let paths: [[String]]

public init(paths: [[String]]) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
public struct EnumDocumentation: Equatable {
public struct EnumDocumentation: Equatable, Sendable {
public let id: String
public let hierarchy: Documentation.Hierarchy
public let title: String
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
public struct ObjectDocumentation: Codable, Equatable {
public struct ObjectDocumentation: Codable, Equatable, Sendable {
public let id: String
public let hierarchy: Documentation.Hierarchy
public let title: String
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
public struct OperationDocumentation: Codable, Equatable {
public struct OperationDocumentation: Codable, Equatable, Sendable {
public let id: String
public let hierarchy: Documentation.Hierarchy
public let title: String
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
public struct PropertyDocumentation: Codable, Equatable {
public struct PropertyDocumentation: Codable, Equatable, Sendable {
public let required: Bool
public let description: String?

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
public struct ResponseDocumentation: Codable, Equatable {
public struct ResponseDocumentation: Codable, Equatable, Sendable {
public let status: Int
public let reason: String?
public let description: String?
Expand Down
Loading

0 comments on commit e31637f

Please sign in to comment.