Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adopt Sendable throughout API to support -strict-concurrency=complete #22

Merged
merged 17 commits into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions Sources/OpenAPIRuntime/Base/OpenAPIValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public struct OpenAPIValueContainer: Codable, Equatable, Hashable, Sendable {
/// - Parameter unvalidatedValue: A value of a JSON-compatible type,
/// such as `String`, `[Any]`, and `[String: Any]`.
/// - Throws: When the value is not supported.
public init(unvalidatedValue: Any? = nil) throws {
public init(unvalidatedValue: (any Sendable)? = nil) throws {
try self.init(validatedValue: Self.tryCast(unvalidatedValue))
}

Expand All @@ -62,14 +62,14 @@ 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?) throws -> Sendable? {
static func tryCast(_ value: (any Sendable)?) throws -> Sendable? {
guard let value = value else {
return nil
}
if let array = value as? [Any?] {
if let array = value as? [(any Sendable)?] {
return try array.map(tryCast(_:))
}
if let dictionary = value as? [String: Any?] {
if let dictionary = value as? [String: (any Sendable)?] {
return try dictionary.mapValues(tryCast(_:))
}
if let value = tryCastPrimitiveType(value) {
Expand All @@ -87,7 +87,7 @@ public struct OpenAPIValueContainer: Codable, Equatable, Hashable, Sendable {
/// Returns the specified value cast to a supported primitive type.
/// - Parameter value: An untyped value.
/// - Returns: A cast value if supported, nil otherwise.
static func tryCastPrimitiveType(_ value: Any) -> Sendable? {
static func tryCastPrimitiveType(_ value: any Sendable) -> (any Sendable)? {
switch value {
case is String, is Int, is Bool, is Double:
return value
Expand Down Expand Up @@ -327,7 +327,7 @@ public struct OpenAPIObjectContainer: Codable, Equatable, Hashable, Sendable {

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value.mapValues(OpenAPIValueContainer.init))
try container.encode(value.mapValues(OpenAPIValueContainer.init(validatedValue:)))
}

// MARK: Equatable
Expand Down Expand Up @@ -430,7 +430,7 @@ public struct OpenAPIArrayContainer: Codable, Equatable, Hashable, Sendable {

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value.map(OpenAPIValueContainer.init))
try container.encode(value.map(OpenAPIValueContainer.init(validatedValue:)))
}

// MARK: Equatable
Expand Down
1 change: 1 addition & 0 deletions Sources/OpenAPIRuntime/Conversion/Converter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#if canImport(Darwin)
import Foundation
#else
// `@preconcrrency` is for `JSONDecoder`/`JSONEncoder`.
@preconcurrency import Foundation
#endif

Expand Down
9 changes: 7 additions & 2 deletions Sources/OpenAPIRuntime/Errors/ClientError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
#if canImport(Darwin)
import Foundation
#else
// `@preconcrrency` is for `URL`.
@preconcurrency import Foundation
MahdiBM marked this conversation as resolved.
Show resolved Hide resolved
#endif

/// An error thrown by a client performing an OpenAPI operation.
///
Expand All @@ -24,7 +29,7 @@ public struct ClientError: Error {
public var operationID: String

/// The operation-specific Input value.
public var operationInput: Any
public var operationInput: any Sendable

/// The HTTP request created during the operation.
///
Expand Down Expand Up @@ -56,7 +61,7 @@ public struct ClientError: Error {
/// - underlyingError: The underlying error that caused the operation to fail.
public init(
operationID: String,
operationInput: Any,
operationInput: any Sendable,
request: Request? = nil,
baseURL: URL? = nil,
response: Response? = nil,
Expand Down
8 changes: 4 additions & 4 deletions Sources/OpenAPIRuntime/Errors/ServerError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ public struct ServerError: Error {
/// Operation-specific Input value.
///
/// Is nil if error was thrown during request -> Input conversion.
public var operationInput: Any?
public var operationInput: (any Sendable)?

/// Operation-specific Output value.
///
/// Is nil if error was thrown before/during Output -> response conversion.
public var operationOutput: Any?
public var operationOutput: (any Sendable)?

/// The underlying error that caused the operation to fail.
public var underlyingError: Error
Expand All @@ -50,8 +50,8 @@ public struct ServerError: Error {
operationID: String,
request: Request,
requestMetadata: ServerRequestMetadata,
operationInput: Any? = nil,
operationOutput: Any? = nil,
operationInput: (any Sendable)? = nil,
operationOutput: (any Sendable)? = nil,
underlyingError: Error
) {
self.operationID = operationID
Expand Down
2 changes: 1 addition & 1 deletion Sources/OpenAPIRuntime/Interface/ClientTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,6 @@ public protocol ClientMiddleware: Sendable {
_ request: Request,
baseURL: URL,
operationID: String,
next: (Request, URL) async throws -> Response
next: @Sendable (Request, URL) async throws -> Response
) async throws -> Response
}
56 changes: 36 additions & 20 deletions Sources/OpenAPIRuntime/Interface/CurrencyTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,42 @@
#if canImport(Darwin)
import Foundation
#else
// `@preconcrrency` is for `Data`/`URLQueryItem`.
@preconcurrency import Foundation
#endif

/// A protected-by-locks storage for ``redactedHeaderFields``.
private class RedactedHeadersStorage: @unchecked Sendable {
/// The underlying storage of ``redactedHeaderFields``,
/// protected by a lock.
private var _locked_redactedHeaderFields: Set<String> = HeaderField.defaultRedactedHeaderFields

/// The header fields to be redacted.
var redactedHeaderFields: Set<String> {
get {
lock.lock()
defer {
lock.unlock()
}
return _locked_redactedHeaderFields
}
set {
lock.lock()
defer {
lock.unlock()
}
_locked_redactedHeaderFields = newValue
}
}

/// The lock used for protecting access to `_locked_redactedHeaderFields`.
private let lock: NSLock = {
let lock = NSLock()
lock.name = "com.apple.swift-openapi-runtime.lock.redactedHeaderFields"
return lock
}()
}

/// A header field used in an HTTP request or response.
public struct HeaderField: Equatable, Hashable, Sendable {

Expand Down Expand Up @@ -47,20 +80,12 @@ extension HeaderField {
/// Use this to avoid leaking sensitive tokens into application logs.
public static var redactedHeaderFields: Set<String> {
simonjbeaumont marked this conversation as resolved.
Show resolved Hide resolved
set {
_lock_redactedHeaderFields.lock()
defer {
_lock_redactedHeaderFields.unlock()
}
// Save lowercased versions of the header field names to make
// membership checking O(1).
_locked_redactedHeaderFields = Set(newValue.map { $0.lowercased() })
redactedHeadersStorage.redactedHeaderFields = Set(newValue.map { $0.lowercased() })
}
get {
_lock_redactedHeaderFields.lock()
defer {
_lock_redactedHeaderFields.unlock()
}
return _locked_redactedHeaderFields
return redactedHeadersStorage.redactedHeaderFields
}
}

Expand All @@ -71,16 +96,7 @@ extension HeaderField {
"set-cookie",
]

/// The lock used for protecting access to `_locked_redactedHeaderFields`.
private static let _lock_redactedHeaderFields: NSLock = {
let lock = NSLock()
lock.name = "com.apple.swift-openapi-runtime.lock.redactedHeaderFields"
return lock
}()

/// The underlying storage of ``HeaderField/redactedHeaderFields``,
/// protected by a lock.
private static var _locked_redactedHeaderFields: Set<String> = defaultRedactedHeaderFields
private static let redactedHeadersStorage = RedactedHeadersStorage()
}

/// Describes the HTTP method used in an OpenAPI operation.
Expand Down
6 changes: 2 additions & 4 deletions Sources/OpenAPIRuntime/Interface/ServerTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,7 @@ public protocol ServerTransport {
/// - queryItemNames: The names of query items to be extracted
/// from the request URL that matches the provided HTTP operation.
func register(
_ handler: @Sendable @escaping (
Request, ServerRequestMetadata
) async throws -> Response,
_ handler: @Sendable @escaping (Request, ServerRequestMetadata) async throws -> Response,
method: HTTPMethod,
path: [RouterPathComponent],
queryItemNames: Set<String>
Expand Down Expand Up @@ -219,6 +217,6 @@ public protocol ServerMiddleware: Sendable {
_ request: Request,
metadata: ServerRequestMetadata,
operationID: String,
next: (Request, ServerRequestMetadata) async throws -> Response
next: @Sendable (Request, ServerRequestMetadata) async throws -> Response
) async throws -> Response
}
10 changes: 6 additions & 4 deletions Sources/OpenAPIRuntime/Interface/UniversalClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#if canImport(Darwin)
import Foundation
#else
// `@preconcrrency` is for `URL`.
@preconcurrency import Foundation
#endif

Expand Down Expand Up @@ -86,9 +87,10 @@ public struct UniversalClient: Sendable {
public func send<OperationInput, OperationOutput>(
input: OperationInput,
forOperation operationID: String,
serializer: (OperationInput) throws -> Request,
deserializer: (Response) throws -> OperationOutput
) async throws -> OperationOutput {
serializer: @Sendable (OperationInput) throws -> Request,
deserializer: @Sendable (Response) throws -> OperationOutput
) async throws -> OperationOutput where OperationInput: Sendable, OperationOutput: Sendable {
@Sendable
func wrappingErrors<R>(
work: () async throws -> R,
mapError: (Error) -> Error
Expand Down Expand Up @@ -121,7 +123,7 @@ public struct UniversalClient: Sendable {
makeError(error: error)
}
let response: Response = try await wrappingErrors {
var next: (Request, URL) async throws -> Response = { (_request, _url) in
var next: @Sendable (Request, URL) async throws -> Response = { (_request, _url) in
try await wrappingErrors {
try await transport.send(
_request,
Expand Down
12 changes: 7 additions & 5 deletions Sources/OpenAPIRuntime/Interface/UniversalServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,11 @@ public struct UniversalServer<APIHandler: Sendable>: Sendable {
request: Request,
with metadata: ServerRequestMetadata,
forOperation operationID: String,
using handlerMethod: @escaping (APIHandler) -> ((OperationInput) async throws -> OperationOutput),
deserializer: @escaping (Request, ServerRequestMetadata) throws -> OperationInput,
serializer: @escaping (OperationOutput, Request) throws -> Response
) async throws -> Response {
using handlerMethod: @Sendable @escaping (APIHandler) -> ((OperationInput) async throws -> OperationOutput),
deserializer: @Sendable @escaping (Request, ServerRequestMetadata) throws -> OperationInput,
serializer: @Sendable @escaping (OperationOutput, Request) throws -> Response
) async throws -> Response where OperationInput: Sendable, OperationOutput: Sendable {
@Sendable
func wrappingErrors<R>(
work: () async throws -> R,
mapError: (Error) -> Error
Expand All @@ -102,6 +103,7 @@ public struct UniversalServer<APIHandler: Sendable>: Sendable {
throw mapError(error)
}
}
@Sendable
func makeError(
input: OperationInput? = nil,
output: OperationOutput? = nil,
Expand All @@ -116,7 +118,7 @@ public struct UniversalServer<APIHandler: Sendable>: Sendable {
underlyingError: error
)
}
var next: (Request, ServerRequestMetadata) async throws -> Response = { _request, _metadata in
var next: @Sendable (Request, ServerRequestMetadata) async throws -> Response = { _request, _metadata in
let input: OperationInput = try await wrappingErrors {
try deserializer(_request, _metadata)
} mapError: { error in
Expand Down
1 change: 1 addition & 0 deletions docker/docker-compose.2204.58.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ services:
environment:
- WARN_AS_ERROR_ARG=-Xswiftc -warnings-as-errors
- IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error
- STRICT_CONCURRENCY_ARG=-Xswiftc -strict-concurrency=complete
MahdiBM marked this conversation as resolved.
Show resolved Hide resolved

shell:
image: *image
1 change: 1 addition & 0 deletions docker/docker-compose.2204.59.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ services:
environment:
- WARN_AS_ERROR_ARG=-Xswiftc -warnings-as-errors
- IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error
- STRICT_CONCURRENCY_ARG=-Xswiftc -strict-concurrency=complete

shell:
image: *image
1 change: 1 addition & 0 deletions docker/docker-compose.2204.main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ services:
environment:
- WARN_AS_ERROR_ARG=-Xswiftc -warnings-as-errors
- IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error
- STRICT_CONCURRENCY_ARG=-Xswiftc -strict-concurrency=complete

shell:
image: *image
2 changes: 1 addition & 1 deletion docker/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ services:

test:
<<: *common
command: /bin/bash -xcl "swift $${SWIFT_TEST_VERB-test} $${WARN_AS_ERROR_ARG-} $${SANITIZER_ARG-} $${IMPORT_CHECK_ARG-}"
command: /bin/bash -xcl "swift $${SWIFT_TEST_VERB-test} $${WARN_AS_ERROR_ARG-} $${SANITIZER_ARG-} $${IMPORT_CHECK_ARG-} $${STRICT_CONCURRENCY_ARG-}"

shell:
<<: *common
Expand Down
2 changes: 1 addition & 1 deletion scripts/run-integration-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,6 @@ swift package --package-path "${INTEGRATION_TEST_PACKAGE_PATH}" \
edit "${PACKAGE_NAME}" --path "${PACKAGE_PATH}"

log "Building integration test package: ${INTEGRATION_TEST_PACKAGE_PATH}"
swift build --package-path "${INTEGRATION_TEST_PACKAGE_PATH}"
swift build --package-path "${INTEGRATION_TEST_PACKAGE_PATH}" -Xswiftc -strict-concurrency=complete

log "✅ Successfully built integration test package."