diff --git a/Sources/OpenAPIURLSession/URLSessionTransport.swift b/Sources/OpenAPIURLSession/URLSessionTransport.swift index d3780dc..7f73ab4 100644 --- a/Sources/OpenAPIURLSession/URLSessionTransport.swift +++ b/Sources/OpenAPIURLSession/URLSessionTransport.swift @@ -12,9 +12,19 @@ // //===----------------------------------------------------------------------===// import OpenAPIRuntime +#if canImport(Darwin) import Foundation +#else +@preconcurrency import struct Foundation.URL +@preconcurrency import struct Foundation.URLComponents +@preconcurrency import struct Foundation.Data +@preconcurrency import protocol Foundation.LocalizedError +#endif #if canImport(FoundationNetworking) -@preconcurrency import FoundationNetworking +@preconcurrency import struct FoundationNetworking.URLRequest +@preconcurrency import class FoundationNetworking.URLSession +@preconcurrency import class FoundationNetworking.URLResponse +@preconcurrency import class FoundationNetworking.HTTPURLResponse #endif /// A client transport that performs HTTP operations using the URLSession type diff --git a/Tests/OpenAPIURLSessionTests/Locking.swift b/Tests/OpenAPIURLSessionTests/Locking.swift new file mode 100644 index 0000000..1d86fe4 --- /dev/null +++ b/Tests/OpenAPIURLSessionTests/Locking.swift @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftOpenAPIGenerator open source project +// +// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation + +/// A wrapper providing locked access to a value. +/// +/// Marked as @unchecked Sendable due to the synchronization being +/// performed manually using locks. +/// +/// Note: Use the `package` access modifier once min Swift version is increased. +@_spi(Locking) +public final class LockedValueBox: @unchecked Sendable { + private let lock: NSLock = { + let lock = NSLock() + lock.name = "com.apple.swift-openapi-urlsession.lock.LockedValueBox" + return lock + }() + private var value: Value + public init(_ value: Value) { + self.value = value + } + public func withValue(_ work: (inout Value) throws -> R) rethrows -> R { + lock.lock() + defer { + lock.unlock() + } + return try work(&value) + } +} diff --git a/Tests/OpenAPIURLSessionTests/URLSessionTransportTests.swift b/Tests/OpenAPIURLSessionTests/URLSessionTransportTests.swift index 9baec4a..6e9d02b 100644 --- a/Tests/OpenAPIURLSessionTests/URLSessionTransportTests.swift +++ b/Tests/OpenAPIURLSessionTests/URLSessionTransportTests.swift @@ -13,9 +13,18 @@ //===----------------------------------------------------------------------===// import XCTest import OpenAPIRuntime +#if canImport(Darwin) import Foundation +#else +@preconcurrency import struct Foundation.URL +#endif #if canImport(FoundationNetworking) -import FoundationNetworking +@preconcurrency import struct FoundationNetworking.URLRequest +@preconcurrency import class FoundationNetworking.URLProtocol +@preconcurrency import class FoundationNetworking.URLSession +@preconcurrency import class FoundationNetworking.HTTPURLResponse +@preconcurrency import class FoundationNetworking.URLResponse +@preconcurrency import class FoundationNetworking.URLSessionConfiguration #endif @testable import OpenAPIURLSession @@ -53,12 +62,14 @@ class URLSessionTransportTests: XCTestCase { func testSend() async throws { let endpointURL = URL(string: "http://example.com/api/hello/Maria?greeting=Howdy")! - MockURLProtocol.mockHTTPResponses[endpointURL] = .success( - ( - HTTPURLResponse(url: endpointURL, statusCode: 201, httpVersion: nil, headerFields: [:])!, - body: Data("👋".utf8) + MockURLProtocol.mockHTTPResponses.withValue { map in + map[endpointURL] = .success( + ( + HTTPURLResponse(url: endpointURL, statusCode: 201, httpVersion: nil, headerFields: [:])!, + body: Data("👋".utf8) + ) ) - ) + } let transport: any ClientTransport = URLSessionTransport( configuration: .init(session: MockURLProtocol.mockURLSession) ) @@ -81,9 +92,10 @@ class URLSessionTransportTests: XCTestCase { } class MockURLProtocol: URLProtocol { - static var mockHTTPResponses: [URL: Result<(response: HTTPURLResponse, body: Data?), any Error>] = [:] + typealias MockHTTPResponseMap = [URL: Result<(response: HTTPURLResponse, body: Data?), any Error>] + static let mockHTTPResponses = LockedValueBox([:]) - static var recordedHTTPRequests: [URLRequest] = [] + static let recordedHTTPRequests = LockedValueBox<[URLRequest]>([]) override class func canInit(with request: URLRequest) -> Bool { true } @@ -92,9 +104,11 @@ class MockURLProtocol: URLProtocol { override func stopLoading() {} override func startLoading() { - Self.recordedHTTPRequests.append(self.request) + Self.recordedHTTPRequests.withValue { $0.append(self.request) } guard let url = self.request.url else { return } - guard let response = Self.mockHTTPResponses[url] else { return } + guard let response = Self.mockHTTPResponses.withValue({ $0[url] }) else { + return + } switch response { case .success(let mockResponse): client?.urlProtocol(self, didReceive: mockResponse.response, cacheStoragePolicy: .notAllowed) diff --git a/docker/docker-compose.2204.58.yaml b/docker/docker-compose.2204.58.yaml index 738fed2..987f3d2 100644 --- a/docker/docker-compose.2204.58.yaml +++ b/docker/docker-compose.2204.58.yaml @@ -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 diff --git a/docker/docker-compose.2204.59.yaml b/docker/docker-compose.2204.59.yaml index ec87e09..47a3c43 100644 --- a/docker/docker-compose.2204.59.yaml +++ b/docker/docker-compose.2204.59.yaml @@ -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 diff --git a/docker/docker-compose.2204.main.yaml b/docker/docker-compose.2204.main.yaml index d182bf4..ef0e43e 100644 --- a/docker/docker-compose.2204.main.yaml +++ b/docker/docker-compose.2204.main.yaml @@ -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 diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 41ed6e1..d1ea158 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -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