diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2890a51..8a10bd7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,11 +14,11 @@ on: jobs: test: name: Run tests - runs-on: macos-11 + runs-on: macos-12 steps: - name: Checkout uses: actions/checkout@v2 - name: Change Xcode - run: sudo xcode-select -s /Applications/Xcode_13.2.1.app + run: sudo xcode-select -s /Applications/Xcode_13.4.1.app - name: Build and test run: swift test --enable-code-coverage --disable-automatic-resolution \ No newline at end of file diff --git a/Package.resolved b/Package.resolved index dfc45eb..cda9880 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/Alamofire/Alamofire.git", "state": { "branch": null, - "revision": "becd9a729a37bdbef5bc39dc3c702b99f9e3d046", - "version": "5.2.2" + "revision": "8dd85aee02e39dd280c75eef88ffdb86eed4b07b", + "version": "5.6.2" } } ] diff --git a/README.md b/README.md index cfc78c0..804865a 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,20 @@ client.request(endpoint) { (result: Result) The separation into `Client` and `Endpoint` allows you to separate the asynchronous code in `Client` from the synchronous code in `Endpoint`. Thus, the side effects are isolated in `Client`, and the pure functions in the non-mutable `Endpoint`. +### CombineClient + +`CombineClient` - protocol that wrap up network request in `Combine`. + +### ConcurrencyClient + +`ConcurrencyClient` - protocol that wrap up network request in `Async/Await`. + +* By default, new methods implemented as extensions of `Client`'s methods. +* `ApexyAlamofire` use built in implementation of `Async/Await` in `Alamofire` +* For `URLSession` new `Async/Await` methods was implemented using `URLSession`'s `AsyncAwait` extended implementation for iOS 14 and below. (look into `URLSession+Concurrency.swift` for more details) + +`Client`, `CombineClient` and `ConcurrenyClient` are separated protocols. You can specify method that you are using by using specific protocol. + ## Getting Started Since most requests will receive JSON, it is necessary to make basic protocols at the module level. They will contain common requests logic for a specific API. diff --git a/README.ru.md b/README.ru.md index 19a50c0..e87256b 100644 --- a/README.ru.md +++ b/README.ru.md @@ -111,6 +111,20 @@ client.request(endpoint) { (result: Result) Разделение на `Client` и `Endpoint` позволяет разделить асинхронный код в `Client` от синхронного кода в `Endpoint`. Таким образом сайд эффекты изолированы в одном месте `Client`, а чистые функции в немутабельных `Endpoint`. +### CombineClient + +`CombineClient` - отдельный протокол, который содержит релизацию сетевый вызовов через Combine. + +### ConcurrencyClient + +`ConcurrencyClient` - отдельный протокол, который содержит релизацию сетевый вызовов через Async/Await. + +* По умолчанию, новые методы релизованы как надстройки над существующими методами с замыканиями. +* Для `ApexyAlamofire` методы уже реализованы через методы `Alamofire`. +* Для `URLSession` добавлены через реализацию системных методов через Async/Await для версий ниже iOS 15. + +`Client`, `CombineClient` и `ConcurrenyClient` - независимые протоколы. В зависимости от удобного для вас способа работы асинхронностью, вы можете выбрать конкретный протокол. + ## Getting Started Так как большинство запросов будут получать JSON, то на уровне модуля нужно сделать базовые протоколы. Они будут содержать в себе общую логику запросов для конкретной API. diff --git a/Sources/Apexy/APIResult.swift b/Sources/Apexy/APIResult.swift new file mode 100644 index 0000000..5a03a1b --- /dev/null +++ b/Sources/Apexy/APIResult.swift @@ -0,0 +1,21 @@ +// +// APIResult.swift +// +// +// Created by Aleksei Tiurnin on 17.08.2022. +// + +import Foundation + +public typealias APIResult = Swift.Result + +public extension APIResult { + var error: Error? { + switch self { + case .failure(let error): + return error + default: + return nil + } + } +} diff --git a/Sources/Apexy/Client.swift b/Sources/Apexy/Client.swift index 4e3aa51..92f0408 100644 --- a/Sources/Apexy/Client.swift +++ b/Sources/Apexy/Client.swift @@ -1,7 +1,5 @@ import Foundation -public typealias APIResult = Swift.Result - public protocol Client: AnyObject { /// Send request to specified endpoint. @@ -25,31 +23,4 @@ public protocol Client: AnyObject { _ endpoint: T, completionHandler: @escaping (APIResult) -> Void ) -> Progress where T: UploadEndpoint - - /// Send request to specified endpoint. - /// - Returns: response data from the server for the request. - @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) - func request(_ endpoint: T) async throws -> T.Content where T: Endpoint - - /// Upload data to specified endpoint. - /// - Returns: response data from the server for the upload. - @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) - func upload(_ endpoint: T) async throws -> T.Content where T: UploadEndpoint - -} - -@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) -public extension Client { - - func request(_ endpoint: T) async throws -> T.Content where T: Endpoint { - try await AsyncAwaitHelper.adaptToAsync(dataTaskClosure: { continuation in - request(endpoint, completionHandler: continuation.resume) - }) - } - - func upload(_ endpoint: T) async throws -> T.Content where T: UploadEndpoint { - try await AsyncAwaitHelper.adaptToAsync(dataTaskClosure: { continuation in - upload(endpoint, completionHandler: continuation.resume) - }) - } } diff --git a/Sources/Apexy/Client+Combine.swift b/Sources/Apexy/Clients/CombineClient.swift similarity index 65% rename from Sources/Apexy/Client+Combine.swift rename to Sources/Apexy/Clients/CombineClient.swift index fcc97af..da06ca3 100644 --- a/Sources/Apexy/Client+Combine.swift +++ b/Sources/Apexy/Clients/CombineClient.swift @@ -1,13 +1,18 @@ #if canImport(Combine) import Combine -/// Wrapper for Combine framework -public extension Client { +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) +public protocol CombineClient: AnyObject { + + /// Send request to specified endpoint. + /// - Parameters: + /// - endpoint: endpoint of remote content. + /// - Returns: Publisher which you can subscribe to + func request(_ endpoint: T) -> AnyPublisher where T: Endpoint +} - @available(iOS 13.0, *) - @available(macOS 10.15, *) - @available(tvOS 13.0, *) - @available(watchOS 6.0, *) +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) +public extension Client where Self: CombineClient { func request(_ endpoint: T) -> AnyPublisher where T: Endpoint { Deferred> { let subject = PassthroughSubject() @@ -30,4 +35,5 @@ public extension Client { .eraseToAnyPublisher() } } + #endif diff --git a/Sources/Apexy/Clients/ConcurrencyClient.swift b/Sources/Apexy/Clients/ConcurrencyClient.swift new file mode 100644 index 0000000..83d673f --- /dev/null +++ b/Sources/Apexy/Clients/ConcurrencyClient.swift @@ -0,0 +1,24 @@ +// +// ConcurrencyClient.swift +// +// +// Created by Aleksei Tiurnin on 16.08.2022. +// + +import Foundation + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) +public protocol ConcurrencyClient: AnyObject { + /// Send request to specified endpoint. + /// - Parameters: + /// - endpoint: endpoint of remote content. + /// - Returns: response data from the server for the request. + func request(_ endpoint: T) async throws -> T.Content where T: Endpoint + + /// Upload data to specified endpoint. + /// - Parameters: + /// - endpoint: endpoint of remote content. + /// - Returns: response data from the server for the upload. + func upload(_ endpoint: T) async throws -> T.Content where T: UploadEndpoint + +} diff --git a/Sources/Apexy/Endpoint.swift b/Sources/Apexy/Endpoint.swift index 7c300a5..4048c9f 100755 --- a/Sources/Apexy/Endpoint.swift +++ b/Sources/Apexy/Endpoint.swift @@ -41,5 +41,5 @@ public protocol Endpoint { } public extension Endpoint { - func validate(_ request: URLRequest?, response: HTTPURLResponse, data: Data?) { } + func validate(_ request: URLRequest?, response: HTTPURLResponse, data: Data?) throws { } } diff --git a/Sources/Apexy/ProgressWrapper.swift b/Sources/Apexy/ProgressWrapper.swift deleted file mode 100644 index 9f31ba1..0000000 --- a/Sources/Apexy/ProgressWrapper.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// ProgressWrapper.swift -// ApexyURLSession -// -// Created by Aleksei Tiurnin on 20.01.2022. -// - -import Foundation - -@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) -public final class ProgressWrapper { - - public var progress: Progress? { - get { - lock.lock() - defer { lock.unlock() } - return _progress - } - set { - lock.lock() - defer { lock.unlock() } - _progress = newValue - } - } - - private var _progress: Progress? - private let lock = NSLock() - - public init(_progress: Progress? = nil) { - self._progress = _progress - } - - public func cancel() { - progress?.cancel() - } -} diff --git a/Sources/Apexy/ResponseObserver.swift b/Sources/Apexy/ResponseObserver.swift new file mode 100644 index 0000000..67aecde --- /dev/null +++ b/Sources/Apexy/ResponseObserver.swift @@ -0,0 +1,10 @@ +// +// ResponseObserver.swift +// +// +// Created by Aleksei Tiurnin on 31.08.2022. +// + +import Foundation + +public typealias ResponseObserver = (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Void diff --git a/Sources/ApexyAlamofire/AlamofireClient+Concurrency.swift b/Sources/ApexyAlamofire/AlamofireClient+Concurrency.swift new file mode 100644 index 0000000..73b36be --- /dev/null +++ b/Sources/ApexyAlamofire/AlamofireClient+Concurrency.swift @@ -0,0 +1,82 @@ +// +// AlamofireClient+Concurrency.swift +// +// +// Created by Aleksei Tiurnin on 15.08.2022. +// + +import Alamofire +import Apexy +import Foundation + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) +extension AlamofireClient: ConcurrencyClient { + + func observeResponse( + dataResponse: DataResponse, + error: Error?) { + self.responseObserver?( + dataResponse.request, + dataResponse.response, + dataResponse.data, + error) + } + + open func request(_ endpoint: T) async throws -> T.Content where T : Endpoint { + + let anyRequest = AnyRequest(create: endpoint.makeRequest) + let request = sessionManager.request(anyRequest) + .validate { request, response, data in + Result(catching: { try endpoint.validate(request, response: response, data: data) }) + } + + let dataResponse = await request.serializingData().response + let result = APIResult(catching: { () throws -> T.Content in + do { + let data = try dataResponse.result.get() + return try endpoint.content(from: dataResponse.response, with: data) + } catch { + throw error.unwrapAlamofireValidationError() + } + }) + + Task.detached { [weak self, dataResponse, result] in + self?.observeResponse(dataResponse: dataResponse, error: result.error) + } + + return try result.get() + } + + open func upload(_ endpoint: T) async throws -> T.Content where T : UploadEndpoint { + + let urlRequest: URLRequest + let body: UploadEndpointBody + (urlRequest, body) = try endpoint.makeRequest() + + let request: UploadRequest + switch body { + case .data(let data): + request = sessionManager.upload(data, with: urlRequest) + case .file(let url): + request = sessionManager.upload(url, with: urlRequest) + case .stream(let stream): + request = sessionManager.upload(stream, with: urlRequest) + } + + let dataResponse = await request.serializingData().response + let result = APIResult(catching: { () throws -> T.Content in + do { + let data = try dataResponse.result.get() + return try endpoint.content(from: dataResponse.response, with: data) + } catch { + throw error.unwrapAlamofireValidationError() + } + }) + + Task.detached { [weak self, dataResponse, result] in + self?.observeResponse(dataResponse: dataResponse, error: result.error) + } + + return try result.get() + } +} diff --git a/Sources/ApexyAlamofire/AlamofireClient.swift b/Sources/ApexyAlamofire/AlamofireClient.swift index 4833f72..9ad3d56 100755 --- a/Sources/ApexyAlamofire/AlamofireClient.swift +++ b/Sources/ApexyAlamofire/AlamofireClient.swift @@ -5,32 +5,29 @@ // Copyright © 2019 RedMadRobot. All rights reserved. // +import Alamofire import Apexy import Foundation -import Alamofire /// API Client. -open class AlamofireClient: Client { - - /// A closure used to observe result of every response from the server. - public typealias ResponseObserver = (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Void +open class AlamofireClient: Client, CombineClient { /// Session network manager. - private let sessionManager: Alamofire.Session + let sessionManager: Alamofire.Session /// The queue on which the network response handler is dispatched. - private let responseQueue = DispatchQueue( + let responseQueue = DispatchQueue( label: "Apexy.responseQueue", qos: .utility) /// The queue on which the completion handler is dispatched. - private let completionQueue: DispatchQueue + let completionQueue: DispatchQueue /// This closure to be called after each response from the server for the request. - private let responseObserver: ResponseObserver? + let responseObserver: ResponseObserver? /// Look more at Alamofire.RequestInterceptor. - public let requestInterceptor: RequestInterceptor + let requestInterceptor: RequestInterceptor /// Creates new 'AlamofireClient' instance. /// @@ -187,7 +184,7 @@ open class AlamofireClient: Client { // MARK: - Helper /// Wrapper for `URLRequestConvertible` from `Alamofire`. -private struct AnyRequest: Alamofire.URLRequestConvertible { +struct AnyRequest: Alamofire.URLRequestConvertible { let create: () throws -> URLRequest func asURLRequest() throws -> URLRequest { @@ -195,17 +192,6 @@ private struct AnyRequest: Alamofire.URLRequestConvertible { } } -private extension APIResult { - var error: Error? { - switch self { - case .failure(let error): - return error - default: - return nil - } - } -} - public extension Error { func unwrapAlamofireValidationError() -> Error { guard let afError = asAFError else { return self } diff --git a/Sources/Apexy/AsyncAwaitHelper.swift b/Sources/ApexyURLSession/Helpers/AsyncAwaitHelper.swift similarity index 69% rename from Sources/Apexy/AsyncAwaitHelper.swift rename to Sources/ApexyURLSession/Helpers/AsyncAwaitHelper.swift index d7a8333..71211ba 100644 --- a/Sources/Apexy/AsyncAwaitHelper.swift +++ b/Sources/ApexyURLSession/Helpers/AsyncAwaitHelper.swift @@ -1,14 +1,12 @@ -// -// AsyncAwaitHelper.swift -// -// -// Created by Aleksei Tiurnin on 31.01.2022. -// - import Foundation @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) -public enum AsyncAwaitHelper { +enum AsyncAwaitHelper { + + enum AsyncError: Error, Equatable { + case cancelledBeforeStart + } + public typealias ContentContinuation = CheckedContinuation public static func adaptToAsync(dataTaskClosure: (ContentContinuation) -> Progress) async throws -> T { @@ -16,7 +14,8 @@ public enum AsyncAwaitHelper { return try await withTaskCancellationHandler(handler: { progressWrapper.cancel() }, operation: { - try await withCheckedThrowingContinuation { (continuation: ContentContinuation) in + try Task.checkCancellation() + return try await withCheckedThrowingContinuation { (continuation: ContentContinuation) in let progress = dataTaskClosure(continuation) progressWrapper.progress = progress } diff --git a/Sources/ApexyURLSession/Helpers/ProgressWrapper.swift b/Sources/ApexyURLSession/Helpers/ProgressWrapper.swift new file mode 100644 index 0000000..bcf8b12 --- /dev/null +++ b/Sources/ApexyURLSession/Helpers/ProgressWrapper.swift @@ -0,0 +1,39 @@ +import Foundation + +@propertyWrapper struct Locked { + + var wrappedValue: T { + get { + lock.lock() + defer { lock.unlock() } + return _value + } + set { + lock.lock() + defer { lock.unlock() } + _value = newValue + } + } + + private var _value: T + private let lock = NSLock() + + init(wrappedValue: T) { + self._value = wrappedValue + } +} + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) +final class ProgressWrapper { + + @Locked + var progress: Progress? + + init(_progress: Progress? = nil) { + self.progress = progress + } + + func cancel() { + progress?.cancel() + } +} diff --git a/Sources/ApexyURLSession/URLSession+AsyncAwait13.swift b/Sources/ApexyURLSession/Helpers/URLSession+Concurrency.swift similarity index 71% rename from Sources/ApexyURLSession/URLSession+AsyncAwait13.swift rename to Sources/ApexyURLSession/Helpers/URLSession+Concurrency.swift index c69fc09..fc4ee8a 100644 --- a/Sources/ApexyURLSession/URLSession+AsyncAwait13.swift +++ b/Sources/ApexyURLSession/Helpers/URLSession+Concurrency.swift @@ -1,5 +1,5 @@ // -// URLSession+AsyncAwait13.swift +// URLSession+Concurrency.swift // ApexyURLSession // // Created by Aleksei Tiurnin on 20.01.2022. @@ -14,6 +14,11 @@ import Foundation @available(tvOS, introduced: 13, deprecated: 15, message: "Extension is no longer necessary. Use API built into SDK") extension URLSession { + /// Send request + /// - Parameters: + /// - request: A URL request object that provides the URL, cache policy, request type, and so on. The body stream and body data in this request object are ignored. + /// - delegate: Delegate to get events about request (NOT WORKING💀) + /// - Returns: Tuple with Data and URLResponse public func data( for request: URLRequest, delegate: URLSessionTaskDelegate? = nil) async throws -> (Data, URLResponse) { @@ -30,6 +35,12 @@ extension URLSession { }) } + /// Send request + /// - Parameters: + /// - request: A URL request object that provides the URL, cache policy, request type, and so on. The body stream and body data in this request object are ignored. + /// - fromFile: The URL of the file to upload. + /// - delegate: Delegate to get events about request (NOT WORKING💀) + /// - Returns: Tuple with Data and URLResponse public func upload( for request: URLRequest, fromFile fileURL: URL, @@ -47,6 +58,12 @@ extension URLSession { }) } + /// Send request + /// - Parameters: + /// - request: A URL request object that provides the URL, cache policy, request type, and so on. The body stream and body data in this request object are ignored. + /// - bodyData: The body data for the request. + /// - delegate: Delegate to get events about request (NOT WORKING💀) + /// - Returns: Tuple with Data and URLResponse public func upload( for request: URLRequest, from bodyData: Data, diff --git a/Sources/ApexyURLSession/URLSessionClient+Concurrency.swift b/Sources/ApexyURLSession/URLSessionClient+Concurrency.swift new file mode 100644 index 0000000..547ad45 --- /dev/null +++ b/Sources/ApexyURLSession/URLSessionClient+Concurrency.swift @@ -0,0 +1,90 @@ +// +// URLSessionClient+Concurrency.swift +// +// +// Created by Aleksei Tiurnin on 15.08.2022. +// + +import Apexy +import Foundation + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) +extension URLSessionClient: ConcurrencyClient { + + func observeResponse( + request: URLRequest?, + responseResult: Result<(data: Data, response: URLResponse), Error>) { + let tuple = try? responseResult.get() + self.responseObserver?( + request, + tuple?.response as? HTTPURLResponse, + tuple?.data, + responseResult.error) + } + + open func request(_ endpoint: T) async throws -> T.Content where T : Endpoint { + + var request = try endpoint.makeRequest() + request = try requestAdapter.adapt(request) + var responseResult: Result<(data: Data, response: URLResponse), Error> + + do { + let response: (data: Data, response: URLResponse) = try await session.data(for: request) + + if let httpResponse = response.response as? HTTPURLResponse { + try endpoint.validate(request, response: httpResponse, data: response.data) + } + + responseResult = .success(response) + } catch let someError { + responseResult = .failure(someError) + } + + Task.detached { [weak self, request, responseResult] in + self?.observeResponse(request: request, responseResult: responseResult) + } + + return try responseResult.flatMap { tuple in + do { + return .success(try endpoint.content(from: tuple.response, with: tuple.data)) + } catch { + return .failure(error) + } + }.get() + } + + open func upload(_ endpoint: T) async throws -> T.Content where T : UploadEndpoint { + + var request: (request: URLRequest, body: UploadEndpointBody) = try endpoint.makeRequest() + request.request = try requestAdapter.adapt(request.request) + var responseResult: Result<(data: Data, response: URLResponse), Error> + + do { + let response: (data: Data, response: URLResponse) + switch request { + case (_, .data(let data)): + response = try await session.upload(for: request.request, from: data) + case (_, .file(let url)): + response = try await session.upload(for: request.request, fromFile: url) + case (_, .stream): + throw URLSessionClientError.uploadStreamUnimplemented + } + + responseResult = .success(response) + } catch let someError { + responseResult = .failure(someError) + } + + Task.detached { [weak self, request, responseResult] in + self?.observeResponse(request: request.request, responseResult: responseResult) + } + + return try responseResult.flatMap { tuple in + do { + return .success(try endpoint.content(from: tuple.response, with: tuple.data)) + } catch { + return .failure(error) + } + }.get() + } +} diff --git a/Sources/ApexyURLSession/URLSessionClient.swift b/Sources/ApexyURLSession/URLSessionClient.swift index b612962..9490a34 100644 --- a/Sources/ApexyURLSession/URLSessionClient.swift +++ b/Sources/ApexyURLSession/URLSessionClient.swift @@ -1,20 +1,17 @@ import Apexy import Foundation -open class URLSessionClient: Client { - - /// A closure used to observe result of every response from the server. - public typealias ResponseObserver = (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Void +open class URLSessionClient: Client, CombineClient { - private let session: URLSession + let session: URLSession - private let requestAdapter: RequestAdapter + let requestAdapter: RequestAdapter /// The queue on which the completion handler is dispatched. - private let completionQueue: DispatchQueue + let completionQueue: DispatchQueue /// This closure to be called after each response from the server for the request. - private let responseObserver: ResponseObserver? + let responseObserver: ResponseObserver? /// Creates new 'URLSessionClient' instance. /// @@ -127,66 +124,6 @@ open class URLSessionClient: Client { return task.progress } - - @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) - open func request(_ endpoint: T) async throws -> T.Content where T : Endpoint { - var request = try endpoint.makeRequest() - request = try requestAdapter.adapt(request) - var response: (data: Data, response: URLResponse)? - var error: Error? - - defer { - completionQueue.async { [request, response, error] in - self.responseObserver?(request, response?.response as? HTTPURLResponse, response?.data, error) - } - } - - do { - response = try await session.data(for: request) - - if let httpResponse = response?.response as? HTTPURLResponse { - try endpoint.validate(request, response: httpResponse, data: response?.data) - } - - let data = response?.data ?? Data() - return try endpoint.content(from: response?.response, with: data) - } catch let someError { - error = someError - throw someError - } - } - - @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) - open func upload(_ endpoint: T) async throws -> T.Content where T : UploadEndpoint { - var request: (request: URLRequest, body: UploadEndpointBody) = try endpoint.makeRequest() - request.request = try requestAdapter.adapt(request.request) - var response: (data: Data, response: URLResponse)? - var error: Error? - - defer { - completionQueue.async { [request, response, error] in - self.responseObserver?(request.request, response?.response as? HTTPURLResponse, response?.data, error) - } - } - - do { - switch request { - case (_, .data(let data)): - response = try await session.upload(for: request.request, from: data) - case (_, .file(let url)): - response = try await session.upload(for: request.request, fromFile: url) - case (_, .stream): - throw URLSessionClientError.uploadStreamUnimplemented - } - - let data = response?.data ?? Data() - return try endpoint.content(from: response?.response, with: data) - } catch let someError { - error = someError - throw someError - } - } - } enum URLSessionClientError: LocalizedError { diff --git a/Tests/ApexyURLSessionTests/AsyncAwaitHelperTests.swift b/Tests/ApexyURLSessionTests/AsyncAwaitHelperTests.swift new file mode 100644 index 0000000..0682178 --- /dev/null +++ b/Tests/ApexyURLSessionTests/AsyncAwaitHelperTests.swift @@ -0,0 +1,40 @@ +// +// AsyncAwitHelperTests.swift +// +// +// Created by Aleksei Tiurnin on 31.10.2022. +// + +import XCTest +@testable import ApexyURLSession + +final class AsyncAwaitHelperTests: XCTestCase { + + func testExample() async throws { + let task = Task(priority: .background) { + try await AsyncAwaitHelper.adaptToAsync(dataTaskClosure: { continuation in + continuation.resume(returning: "123") + return Progress() + }) + } + let value = try await task.value + XCTAssertEqual(value, "123") + } + + func testCancelExample() async throws { + let task = Task(priority: .background) { + try await AsyncAwaitHelper.adaptToAsync(dataTaskClosure: { continuation in + continuation.resume(returning: "123") + return Progress() + }) + } + task.cancel() + do { + _ = try await task.value + XCTFail("Task has been cancelled") + } catch { + XCTAssert(error is CancellationError) + } + } + +}