From 560b74bb437f0f0a2eff99048ea96d055551f2e4 Mon Sep 17 00:00:00 2001 From: David Nadoba Date: Thu, 13 Apr 2023 16:38:03 +0200 Subject: [PATCH] Add support for custom cancellation error --- Sources/AsyncHTTPClient/HTTPHandler.swift | 10 ++++++-- Sources/AsyncHTTPClient/RequestBag.swift | 8 +----- .../HTTP1ClientChannelHandlerTests.swift | 2 +- .../HTTP2ClientRequestHandlerTests.swift | 2 +- .../HTTPClientTests+XCTest.swift | 1 + .../HTTPClientTests.swift | 25 +++++++++++++++++++ .../HTTPConnectionPoolTests.swift | 2 +- .../RequestBagTests.swift | 12 ++++----- 8 files changed, 44 insertions(+), 18 deletions(-) diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index 614c55f3a..0c84ef6bc 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -688,7 +688,7 @@ extension URL { } protocol HTTPClientTaskDelegate { - func cancel() + func fail(_ error: Error) } extension HTTPClient { @@ -780,12 +780,18 @@ extension HTTPClient { /// Cancels the request execution. public func cancel() { + self.fail(reason: HTTPClientError.cancelled) + } + + /// Cancels the request execution with a custom `Error`. + /// - Parameter reason: the error that is used to fail the promise + public func fail(reason error: Error) { let taskDelegate = self.lock.withLock { () -> HTTPClientTaskDelegate? in self._isCancelled = true return self._taskDelegate } - taskDelegate?.cancel() + taskDelegate?.fail(error) } func succeed(promise: EventLoopPromise?, diff --git a/Sources/AsyncHTTPClient/RequestBag.swift b/Sources/AsyncHTTPClient/RequestBag.swift index 2b20193b4..c5472fc6f 100644 --- a/Sources/AsyncHTTPClient/RequestBag.swift +++ b/Sources/AsyncHTTPClient/RequestBag.swift @@ -394,7 +394,7 @@ final class RequestBag { } } -extension RequestBag: HTTPSchedulableRequest { +extension RequestBag: HTTPSchedulableRequest, HTTPClientTaskDelegate { var tlsConfiguration: TLSConfiguration? { self.request.tlsConfiguration } @@ -511,9 +511,3 @@ extension RequestBag: HTTPExecutableRequest { } } } - -extension RequestBag: HTTPClientTaskDelegate { - func cancel() { - self.fail(HTTPClientError.cancelled) - } -} diff --git a/Tests/AsyncHTTPClientTests/HTTP1ClientChannelHandlerTests.swift b/Tests/AsyncHTTPClientTests/HTTP1ClientChannelHandlerTests.swift index bdf897b3d..2aa010491 100644 --- a/Tests/AsyncHTTPClientTests/HTTP1ClientChannelHandlerTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP1ClientChannelHandlerTests.swift @@ -327,7 +327,7 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { XCTAssertNoThrow(try embedded.writeInbound(HTTPClientResponsePart.head(responseHead))) // canceling the request - requestBag.cancel() + requestBag.fail(HTTPClientError.cancelled) XCTAssertThrowsError(try requestBag.task.futureResult.wait()) { XCTAssertEqual($0 as? HTTPClientError, .cancelled) } diff --git a/Tests/AsyncHTTPClientTests/HTTP2ClientRequestHandlerTests.swift b/Tests/AsyncHTTPClientTests/HTTP2ClientRequestHandlerTests.swift index c0e5b6054..2b68fceb3 100644 --- a/Tests/AsyncHTTPClientTests/HTTP2ClientRequestHandlerTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP2ClientRequestHandlerTests.swift @@ -276,7 +276,7 @@ class HTTP2ClientRequestHandlerTests: XCTestCase { XCTAssertNoThrow(try embedded.writeInbound(HTTPClientResponsePart.head(responseHead))) // canceling the request - requestBag.cancel() + requestBag.fail(HTTPClientError.cancelled) XCTAssertThrowsError(try requestBag.task.futureResult.wait()) { XCTAssertEqual($0 as? HTTPClientError, .cancelled) } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift index 06324d3fc..8f292239e 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift @@ -52,6 +52,7 @@ extension HTTPClientTests { ("testStreaming", testStreaming), ("testFileDownload", testFileDownload), ("testFileDownloadError", testFileDownloadError), + ("testFileDownloadCustomError", testFileDownloadCustomError), ("testRemoteClose", testRemoteClose), ("testReadTimeout", testReadTimeout), ("testConnectTimeout", testConnectTimeout), diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 70a44ed07..8cd0b3bba 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -569,6 +569,31 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { XCTAssertEqual(0, progress.receivedBytes) } + func testFileDownloadCustomError() throws { + let request = try Request(url: self.defaultHTTPBinURLPrefix + "get") + struct CustomError: Equatable, Error {} + + try TemporaryFileHelpers.withTemporaryFilePath { path in + let delegate = try FileDownloadDelegate(path: path, reportHead: { task, head in + XCTAssertEqual(head.status, .ok) + task.fail(reason: CustomError()) + }, reportProgress: { _, _ in + XCTFail("should never be called") + }) + XCTAssertThrowsError( + try self.defaultClient.execute( + request: request, + delegate: delegate + ) + .wait() + ) { error in + XCTAssertEqualTypeAndValue(error, CustomError()) + } + + XCTAssertFalse(TemporaryFileHelpers.fileExists(path: path)) + } + } + func testRemoteClose() { XCTAssertThrowsError(try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "close").wait()) { XCTAssertEqual($0 as? HTTPClientError, .remoteConnectionClosed) diff --git a/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests.swift b/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests.swift index e85571ad1..2cf222afe 100644 --- a/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests.swift @@ -334,7 +334,7 @@ class HTTPConnectionPoolTests: XCTestCase { pool.executeRequest(requestBag) XCTAssertNoThrow(try eventLoop.scheduleTask(in: .seconds(1)) {}.futureResult.wait()) - requestBag.cancel() + requestBag.fail(HTTPClientError.cancelled) XCTAssertThrowsError(try requestBag.task.futureResult.wait()) { XCTAssertEqual($0 as? HTTPClientError, .cancelled) diff --git a/Tests/AsyncHTTPClientTests/RequestBagTests.swift b/Tests/AsyncHTTPClientTests/RequestBagTests.swift index 36efee949..54de39e12 100644 --- a/Tests/AsyncHTTPClientTests/RequestBagTests.swift +++ b/Tests/AsyncHTTPClientTests/RequestBagTests.swift @@ -220,7 +220,7 @@ final class RequestBagTests: XCTestCase { XCTAssert(bag.eventLoop === embeddedEventLoop) let executor = MockRequestExecutor(eventLoop: embeddedEventLoop) - bag.cancel() + bag.fail(HTTPClientError.cancelled) bag.willExecuteRequest(executor) XCTAssertTrue(executor.isCancelled, "The request bag, should call cancel immediately on the executor") @@ -301,7 +301,7 @@ final class RequestBagTests: XCTestCase { bag.fail(MyError()) XCTAssertEqual(delegate.hitDidReceiveError, 1) - bag.cancel() + bag.fail(HTTPClientError.cancelled) XCTAssertEqual(delegate.hitDidReceiveError, 1) XCTAssertThrowsError(try bag.task.futureResult.wait()) { @@ -342,7 +342,7 @@ final class RequestBagTests: XCTestCase { XCTAssertEqual(delegate.hitDidSendRequestHead, 1) XCTAssertEqual(delegate.hitDidSendRequest, 1) - bag.cancel() + bag.fail(HTTPClientError.cancelled) XCTAssertTrue(executor.isCancelled, "The request bag, should call cancel immediately on the executor") XCTAssertThrowsError(try bag.task.futureResult.wait()) { @@ -376,7 +376,7 @@ final class RequestBagTests: XCTestCase { bag.requestWasQueued(queuer) XCTAssertEqual(queuer.hitCancelCount, 0) - bag.cancel() + bag.fail(HTTPClientError.cancelled) XCTAssertEqual(queuer.hitCancelCount, 1) XCTAssertThrowsError(try bag.task.futureResult.wait()) { @@ -445,9 +445,9 @@ final class RequestBagTests: XCTestCase { let executor = MockRequestExecutor(eventLoop: embeddedEventLoop) executor.runRequest(bag) - // This simulates a race between the user cancelling the task (which invokes `RequestBag.cancel`) and the + // This simulates a race between the user cancelling the task (which invokes `RequestBag.fail(_:)`) and the // call to `resumeRequestBodyStream` (which comes from the `Channel` event loop and so may have to hop. - bag.cancel() + bag.fail(HTTPClientError.cancelled) bag.resumeRequestBodyStream() XCTAssertEqual(executor.isCancelled, true)