From 084572219752690180c53f3ef353e8042e2772a7 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Fri, 14 Jul 2023 15:24:51 +0200 Subject: [PATCH 1/3] Adopt explicit existential any --- Package.swift | 13 ++++++- .../OpenAPIRuntime/Base/OpenAPIValue.swift | 38 +++++++++---------- .../Conversion/CodableExtensions.swift | 2 +- .../Conversion/Configuration.swift | 8 ++-- .../Deprecated/Deprecated.swift | 4 +- .../OpenAPIRuntime/Errors/ClientError.swift | 6 +-- .../OpenAPIRuntime/Errors/RuntimeError.swift | 4 +- .../OpenAPIRuntime/Errors/ServerError.swift | 6 +-- .../Interface/UniversalClient.swift | 18 ++++----- .../Interface/UniversalServer.swift | 12 +++--- .../Conversion/Test_CodableExtensions.swift | 10 ++--- 11 files changed, 65 insertions(+), 56 deletions(-) diff --git a/Package.swift b/Package.swift index 0d2d2ea6..169aef1a 100644 --- a/Package.swift +++ b/Package.swift @@ -14,6 +14,13 @@ //===----------------------------------------------------------------------===// import PackageDescription +// General Swift-settings for all targets. +let swiftSettings: [SwiftSetting] = [ + // https://github.com/apple/swift-evolution/blob/main/proposals/0335-existential-any.md + // Require `any` for existential types. + .enableUpcomingFeature("ExistentialAny") +] + let package = Package( name: "swift-openapi-runtime", platforms: [ @@ -31,11 +38,13 @@ let package = Package( targets: [ .target( name: "OpenAPIRuntime", - dependencies: [] + dependencies: [], + swiftSettings: swiftSettings ), .testTarget( name: "OpenAPIRuntimeTests", - dependencies: ["OpenAPIRuntime"] + dependencies: ["OpenAPIRuntime"], + swiftSettings: swiftSettings ), ] ) diff --git a/Sources/OpenAPIRuntime/Base/OpenAPIValue.swift b/Sources/OpenAPIRuntime/Base/OpenAPIValue.swift index 0c9cd496..4f274274 100644 --- a/Sources/OpenAPIRuntime/Base/OpenAPIValue.swift +++ b/Sources/OpenAPIRuntime/Base/OpenAPIValue.swift @@ -36,12 +36,12 @@ public struct OpenAPIValueContainer: Codable, Equatable, Hashable, Sendable { /// The underlying dynamic value. - public var value: Sendable? + public var value: (any Sendable)? /// Creates a new container with the given validated value. /// - Parameter value: A value of a JSON-compatible type, such as `String`, /// `[Any]`, and `[String: Any]`. - init(validatedValue value: Sendable?) { + init(validatedValue value: (any Sendable)?) { self.value = value } @@ -62,7 +62,7 @@ public struct OpenAPIValueContainer: Codable, Equatable, Hashable, Sendable { /// - Parameter value: An untyped value. /// - Returns: A cast value if supported. /// - Throws: When the value is not supported. - static func tryCast(_ value: (any Sendable)?) throws -> Sendable? { + static func tryCast(_ value: (any Sendable)?) throws -> (any Sendable)? { guard let value = value else { return nil } @@ -98,7 +98,7 @@ public struct OpenAPIValueContainer: Codable, Equatable, Hashable, Sendable { // MARK: Decodable - public init(from decoder: Decoder) throws { + public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() if container.decodeNil() { self.init(validatedValue: nil) @@ -124,7 +124,7 @@ public struct OpenAPIValueContainer: Codable, Equatable, Hashable, Sendable { // MARK: Encodable - public func encode(to encoder: Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.singleValueContainer() guard let value = value else { try container.encodeNil() @@ -171,7 +171,7 @@ public struct OpenAPIValueContainer: Codable, Equatable, Hashable, Sendable { return lhs == rhs case let (lhs as String, rhs as String): return lhs == rhs - case let (lhs as [Sendable?], rhs as [Sendable?]): + case let (lhs as [(any Sendable)?], rhs as [(any Sendable)?]): guard lhs.count == rhs.count else { return false } @@ -179,7 +179,7 @@ public struct OpenAPIValueContainer: Codable, Equatable, Hashable, Sendable { .allSatisfy { lhs, rhs in OpenAPIValueContainer(validatedValue: lhs) == OpenAPIValueContainer(validatedValue: rhs) } - case let (lhs as [String: Sendable?], rhs as [String: Sendable?]): + case let (lhs as [String: (any Sendable)?], rhs as [String: (any Sendable)?]): guard lhs.count == rhs.count else { return false } @@ -211,11 +211,11 @@ public struct OpenAPIValueContainer: Codable, Equatable, Hashable, Sendable { hasher.combine(value) case let value as String: hasher.combine(value) - case let value as [Sendable]: + case let value as [any Sendable]: for item in value { hasher.combine(OpenAPIValueContainer(validatedValue: item)) } - case let value as [String: Sendable]: + case let value as [String: any Sendable]: for (key, itemValue) in value { hasher.combine(key) hasher.combine(OpenAPIValueContainer(validatedValue: itemValue)) @@ -281,11 +281,11 @@ extension OpenAPIValueContainer: ExpressibleByFloatLiteral { public struct OpenAPIObjectContainer: Codable, Equatable, Hashable, Sendable { /// The underlying dynamic dictionary value. - public var value: [String: Sendable?] + public var value: [String: (any Sendable)?] /// Creates a new container with the given validated dictionary. /// - Parameter value: A dictionary value. - init(validatedValue value: [String: Sendable?]) { + init(validatedValue value: [String: (any Sendable)?]) { self.value = value } @@ -311,13 +311,13 @@ public struct OpenAPIObjectContainer: Codable, Equatable, Hashable, Sendable { /// - Parameter value: A dictionary with untyped values. /// - Returns: A cast dictionary if values are supported. /// - Throws: If an unsupported value is found. - static func tryCast(_ value: [String: Any?]) throws -> [String: Sendable?] { + static func tryCast(_ value: [String: Any?]) throws -> [String: (any Sendable)?] { return try value.mapValues(OpenAPIValueContainer.tryCast(_:)) } // MARK: Decodable - public init(from decoder: Decoder) throws { + public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() let item = try container.decode([String: OpenAPIValueContainer].self) self.init(validatedValue: item.mapValues(\.value)) @@ -325,7 +325,7 @@ public struct OpenAPIObjectContainer: Codable, Equatable, Hashable, Sendable { // MARK: Encodable - public func encode(to encoder: Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.singleValueContainer() try container.encode(value.mapValues(OpenAPIValueContainer.init(validatedValue:))) } @@ -385,11 +385,11 @@ public struct OpenAPIObjectContainer: Codable, Equatable, Hashable, Sendable { public struct OpenAPIArrayContainer: Codable, Equatable, Hashable, Sendable { /// The underlying dynamic array value. - public var value: [Sendable?] + public var value: [(any Sendable)?] /// Creates a new container with the given validated array. /// - Parameter value: An array value. - init(validatedValue value: [Sendable?]) { + init(validatedValue value: [(any Sendable)?]) { self.value = value } @@ -414,13 +414,13 @@ public struct OpenAPIArrayContainer: Codable, Equatable, Hashable, Sendable { /// Returns the specified value cast to an array of supported values. /// - Parameter value: An array with untyped values. /// - Returns: A cast value if values are supported, nil otherwise. - static func tryCast(_ value: [Any?]) throws -> [Sendable?] { + static func tryCast(_ value: [Any?]) throws -> [(any Sendable)?] { return try value.map(OpenAPIValueContainer.tryCast(_:)) } // MARK: Decodable - public init(from decoder: Decoder) throws { + public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() let item = try container.decode([OpenAPIValueContainer].self) self.init(validatedValue: item.map(\.value)) @@ -428,7 +428,7 @@ public struct OpenAPIArrayContainer: Codable, Equatable, Hashable, Sendable { // MARK: Encodable - public func encode(to encoder: Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.singleValueContainer() try container.encode(value.map(OpenAPIValueContainer.init(validatedValue:))) } diff --git a/Sources/OpenAPIRuntime/Conversion/CodableExtensions.swift b/Sources/OpenAPIRuntime/Conversion/CodableExtensions.swift index 91935632..8f3496c9 100644 --- a/Sources/OpenAPIRuntime/Conversion/CodableExtensions.swift +++ b/Sources/OpenAPIRuntime/Conversion/CodableExtensions.swift @@ -53,7 +53,7 @@ extension Decoder { guard !unknownKeys.isEmpty else { return .init() } - let keyValuePairs: [(String, Sendable?)] = try unknownKeys.map { key in + let keyValuePairs: [(String, (any Sendable)?)] = try unknownKeys.map { key in ( key.stringValue, try container.decode( diff --git a/Sources/OpenAPIRuntime/Conversion/Configuration.swift b/Sources/OpenAPIRuntime/Conversion/Configuration.swift index b2a68041..45cdcf99 100644 --- a/Sources/OpenAPIRuntime/Conversion/Configuration.swift +++ b/Sources/OpenAPIRuntime/Conversion/Configuration.swift @@ -55,7 +55,7 @@ extension DateTranscoder where Self == ISO8601DateTranscoder { extension JSONEncoder.DateEncodingStrategy { /// Encode the `Date` as a custom value encoded using the given ``DateTranscoder``. - static func from(dateTranscoder: DateTranscoder) -> Self { + static func from(dateTranscoder: any DateTranscoder) -> Self { return .custom { date, encoder in let dateAsString = try dateTranscoder.encode(date) var container = encoder.singleValueContainer() @@ -66,7 +66,7 @@ extension JSONEncoder.DateEncodingStrategy { extension JSONDecoder.DateDecodingStrategy { /// Decode the `Date` as a custom value decoded by the given ``DateTranscoder``. - static func from(dateTranscoder: DateTranscoder) -> Self { + static func from(dateTranscoder: any DateTranscoder) -> Self { return .custom { decoder in let container = try decoder.singleValueContainer() let dateString = try container.decode(String.self) @@ -79,7 +79,7 @@ extension JSONDecoder.DateDecodingStrategy { public struct Configuration: Sendable { /// The transcoder used when converting between date and string values. - public var dateTranscoder: DateTranscoder + public var dateTranscoder: any DateTranscoder /// Creates a new configuration with the specified values. /// @@ -87,7 +87,7 @@ public struct Configuration: Sendable { /// - dateTranscoder: The transcoder to use when converting between date /// and string values. public init( - dateTranscoder: DateTranscoder = .iso8601 + dateTranscoder: any DateTranscoder = .iso8601 ) { self.dateTranscoder = dateTranscoder } diff --git a/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift b/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift index 53f85d74..f702eb2c 100644 --- a/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift +++ b/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift @@ -197,7 +197,7 @@ extension Converter { guard let value else { return } - if let value = value as? _StringConvertible { + if let value = value as? (any _StringConvertible) { headerFields.add(name: name, value: value.description) return } @@ -223,7 +223,7 @@ extension Converter { guard let stringValue = headerFields.firstValue(name: name) else { return nil } - if let myType = T.self as? _StringConvertible.Type { + if let myType = T.self as? any _StringConvertible.Type { return myType.init(stringValue).map { $0 as! T } } let data = Data(stringValue.utf8) diff --git a/Sources/OpenAPIRuntime/Errors/ClientError.swift b/Sources/OpenAPIRuntime/Errors/ClientError.swift index 5d778c74..ce9ff17e 100644 --- a/Sources/OpenAPIRuntime/Errors/ClientError.swift +++ b/Sources/OpenAPIRuntime/Errors/ClientError.swift @@ -49,7 +49,7 @@ public struct ClientError: Error { public var response: Response? /// The underlying error that caused the operation to fail. - public var underlyingError: Error + public var underlyingError: any Error /// Creates a new error. /// - Parameters: @@ -65,7 +65,7 @@ public struct ClientError: Error { request: Request? = nil, baseURL: URL? = nil, response: Response? = nil, - underlyingError: Error + underlyingError: any Error ) { self.operationID = operationID self.operationInput = operationInput @@ -78,7 +78,7 @@ public struct ClientError: Error { // MARK: Private fileprivate var underlyingErrorDescription: String { - guard let prettyError = underlyingError as? PrettyStringConvertible else { + guard let prettyError = underlyingError as? (any PrettyStringConvertible) else { return underlyingError.localizedDescription } return prettyError.prettyDescription diff --git a/Sources/OpenAPIRuntime/Errors/RuntimeError.swift b/Sources/OpenAPIRuntime/Errors/RuntimeError.swift index b6407ddd..1a2143b9 100644 --- a/Sources/OpenAPIRuntime/Errors/RuntimeError.swift +++ b/Sources/OpenAPIRuntime/Errors/RuntimeError.swift @@ -38,8 +38,8 @@ internal enum RuntimeError: Error, CustomStringConvertible, LocalizedError, Pret case missingRequiredRequestBody // Transport/Handler - case transportFailed(Error) - case handlerFailed(Error) + case transportFailed(any Error) + case handlerFailed(any Error) // MARK: CustomStringConvertible diff --git a/Sources/OpenAPIRuntime/Errors/ServerError.swift b/Sources/OpenAPIRuntime/Errors/ServerError.swift index 9fe2a94d..52985d6a 100644 --- a/Sources/OpenAPIRuntime/Errors/ServerError.swift +++ b/Sources/OpenAPIRuntime/Errors/ServerError.swift @@ -35,7 +35,7 @@ public struct ServerError: Error { public var operationOutput: (any Sendable)? /// The underlying error that caused the operation to fail. - public var underlyingError: Error + public var underlyingError: any Error /// Creates a new error. /// - Parameters: @@ -52,7 +52,7 @@ public struct ServerError: Error { requestMetadata: ServerRequestMetadata, operationInput: (any Sendable)? = nil, operationOutput: (any Sendable)? = nil, - underlyingError: Error + underlyingError: (any Error) ) { self.operationID = operationID self.request = request @@ -65,7 +65,7 @@ public struct ServerError: Error { // MARK: Private fileprivate var underlyingErrorDescription: String { - guard let prettyError = underlyingError as? PrettyStringConvertible else { + guard let prettyError = underlyingError as? (any PrettyStringConvertible) else { return underlyingError.localizedDescription } return prettyError.prettyDescription diff --git a/Sources/OpenAPIRuntime/Interface/UniversalClient.swift b/Sources/OpenAPIRuntime/Interface/UniversalClient.swift index 56045d86..9b1a7746 100644 --- a/Sources/OpenAPIRuntime/Interface/UniversalClient.swift +++ b/Sources/OpenAPIRuntime/Interface/UniversalClient.swift @@ -34,17 +34,17 @@ public struct UniversalClient: Sendable { public let converter: Converter /// Type capable of sending HTTP requests and receiving HTTP responses. - public var transport: ClientTransport + public var transport: any ClientTransport /// Middlewares to be invoked before `transport`. - public var middlewares: [ClientMiddleware] + public var middlewares: [any ClientMiddleware] /// Internal initializer that takes an initialized `Converter`. internal init( serverURL: URL, converter: Converter, - transport: ClientTransport, - middlewares: [ClientMiddleware] + transport: any ClientTransport, + middlewares: [any ClientMiddleware] ) { self.serverURL = serverURL self.converter = converter @@ -56,8 +56,8 @@ public struct UniversalClient: Sendable { public init( serverURL: URL = .defaultOpenAPIServerURL, configuration: Configuration = .init(), - transport: ClientTransport, - middlewares: [ClientMiddleware] = [] + transport: any ClientTransport, + middlewares: [any ClientMiddleware] = [] ) { self.init( serverURL: serverURL, @@ -93,7 +93,7 @@ public struct UniversalClient: Sendable { @Sendable func wrappingErrors( work: () async throws -> R, - mapError: (Error) -> Error + mapError: (any Error) -> any Error ) async throws -> R { do { return try await work() @@ -106,8 +106,8 @@ public struct UniversalClient: Sendable { request: Request? = nil, baseURL: URL? = nil, response: Response? = nil, - error: Error - ) -> Error { + error: any Error + ) -> any Error { ClientError( operationID: operationID, operationInput: input, diff --git a/Sources/OpenAPIRuntime/Interface/UniversalServer.swift b/Sources/OpenAPIRuntime/Interface/UniversalServer.swift index ed10e614..7e57f5c4 100644 --- a/Sources/OpenAPIRuntime/Interface/UniversalServer.swift +++ b/Sources/OpenAPIRuntime/Interface/UniversalServer.swift @@ -36,14 +36,14 @@ public struct UniversalServer: Sendable { public var handler: APIHandler /// Middlewares to be invoked before `api` handles the request. - public var middlewares: [ServerMiddleware] + public var middlewares: [any ServerMiddleware] /// Internal initializer that takes an initialized converter. internal init( serverURL: URL, converter: Converter, handler: APIHandler, - middlewares: [ServerMiddleware] + middlewares: [any ServerMiddleware] ) { self.serverURL = serverURL self.converter = converter @@ -56,7 +56,7 @@ public struct UniversalServer: Sendable { serverURL: URL = .defaultOpenAPIServerURL, handler: APIHandler, configuration: Configuration = .init(), - middlewares: [ServerMiddleware] = [] + middlewares: [any ServerMiddleware] = [] ) { self.init( serverURL: serverURL, @@ -95,7 +95,7 @@ public struct UniversalServer: Sendable { @Sendable func wrappingErrors( work: () async throws -> R, - mapError: (Error) -> Error + mapError: (any Error) -> any Error ) async throws -> R { do { return try await work() @@ -107,8 +107,8 @@ public struct UniversalServer: Sendable { func makeError( input: OperationInput? = nil, output: OperationOutput? = nil, - error: Error - ) -> Error { + error: any Error + ) -> any Error { ServerError( operationID: operationID, request: request, diff --git a/Tests/OpenAPIRuntimeTests/Conversion/Test_CodableExtensions.swift b/Tests/OpenAPIRuntimeTests/Conversion/Test_CodableExtensions.swift index f6a752b3..c53a7b15 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_CodableExtensions.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_CodableExtensions.swift @@ -35,7 +35,7 @@ final class Test_CodableExtensions: Test_Runtime { case bar } - init(from decoder: Decoder) throws { + init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.bar = try container.decode(String.self, forKey: .bar) try decoder.ensureNoAdditionalProperties( @@ -91,7 +91,7 @@ final class Test_CodableExtensions: Test_Runtime { case bar } - init(from decoder: Decoder) throws { + init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.bar = try container.decode(String.self, forKey: .bar) self.additionalProperties = @@ -142,7 +142,7 @@ final class Test_CodableExtensions: Test_Runtime { case bar } - init(from decoder: Decoder) throws { + init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.bar = try container.decode(String.self, forKey: .bar) self.additionalProperties = @@ -193,7 +193,7 @@ final class Test_CodableExtensions: Test_Runtime { case bar } - func encode(to encoder: Encoder) throws { + func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(bar, forKey: .bar) try encoder.encodeAdditionalProperties(additionalProperties) @@ -247,7 +247,7 @@ final class Test_CodableExtensions: Test_Runtime { case bar } - func encode(to encoder: Encoder) throws { + func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(bar, forKey: .bar) try encoder.encodeAdditionalProperties(additionalProperties) From eddbff621f0979c0e8c0a7eaccc8ff6f5c5f84fa Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 17 Jul 2023 09:34:58 +0200 Subject: [PATCH 2/3] Disable the ExistentialAny feature temporarily --- Package.swift | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/Package.swift b/Package.swift index 169aef1a..0d2d2ea6 100644 --- a/Package.swift +++ b/Package.swift @@ -14,13 +14,6 @@ //===----------------------------------------------------------------------===// import PackageDescription -// General Swift-settings for all targets. -let swiftSettings: [SwiftSetting] = [ - // https://github.com/apple/swift-evolution/blob/main/proposals/0335-existential-any.md - // Require `any` for existential types. - .enableUpcomingFeature("ExistentialAny") -] - let package = Package( name: "swift-openapi-runtime", platforms: [ @@ -38,13 +31,11 @@ let package = Package( targets: [ .target( name: "OpenAPIRuntime", - dependencies: [], - swiftSettings: swiftSettings + dependencies: [] ), .testTarget( name: "OpenAPIRuntimeTests", - dependencies: ["OpenAPIRuntime"], - swiftSettings: swiftSettings + dependencies: ["OpenAPIRuntime"] ), ] ) From 02112e8cc6cc5a9b134386b983dd316454c635bd Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 17 Jul 2023 11:11:31 +0200 Subject: [PATCH 3/3] PR feedback from Franz, conditionally enable on >=5.9 --- Package.swift | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 0d2d2ea6..ea000944 100644 --- a/Package.swift +++ b/Package.swift @@ -14,6 +14,17 @@ //===----------------------------------------------------------------------===// import PackageDescription +// General Swift-settings for all targets. +var swiftSettings: [SwiftSetting] = [] + +#if swift(>=5.9) +swiftSettings.append( + // https://github.com/apple/swift-evolution/blob/main/proposals/0335-existential-any.md + // Require `any` for existential types. + .enableUpcomingFeature("ExistentialAny") +) +#endif + let package = Package( name: "swift-openapi-runtime", platforms: [ @@ -31,11 +42,13 @@ let package = Package( targets: [ .target( name: "OpenAPIRuntime", - dependencies: [] + dependencies: [], + swiftSettings: swiftSettings ), .testTarget( name: "OpenAPIRuntimeTests", - dependencies: ["OpenAPIRuntime"] + dependencies: ["OpenAPIRuntime"], + swiftSettings: swiftSettings ), ] )