Skip to content

Commit

Permalink
feat(interceptor): Make public rescue API async (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
pjechris authored Feb 13, 2023
1 parent 79e1655 commit a5a5e07
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 40 deletions.
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ excluded:
disabled_rules:
- statement_position
- type_name
- trailing_newline

opt_in_rules:
- closure_end_indentation
Expand Down
10 changes: 4 additions & 6 deletions Sources/SimpleHTTP/Interceptor/CompositeInterceptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,12 @@ extension CompositeInterceptor: Interceptor {
}
}

public func rescueRequest<Output>(_ request: Request<Output>, error: Error) -> AnyPublisher<Void, Error>? {
let publishers = compactMap { $0.rescueRequest(request, error: error) }

guard !publishers.isEmpty else {
return nil
public func shouldRescueRequest<Output>(_ request: Request<Output>, error: Error) async throws -> Bool {
for interceptor in interceptors where try await interceptor.shouldRescueRequest(request, error: error) {
return true
}

return Publishers.MergeMany(publishers).eraseToAnyPublisher()
return false
}

public func adaptOutput<Output>(_ response: Output, for request: Request<Output>) throws -> Output {
Expand Down
31 changes: 1 addition & 30 deletions Sources/SimpleHTTP/Interceptor/Interceptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public protocol RequestInterceptor {
/// catch and retry a failed request
/// - Returns: nil if the request should not be retried. Otherwise a publisher that will be executed before
/// retrying the request
func rescueRequest<Output>(_ request: Request<Output>, error: Error) -> AnyPublisher<Void, Error>?
func shouldRescueRequest<Output>(_ request: Request<Output>, error: Error) async throws -> Bool
}

/// a protocol intercepting a session response
Expand All @@ -25,32 +25,3 @@ public protocol ResponseInterceptor {
/// - Parameter request: the request that was sent to the server
func receivedResponse<Output>(_ result: Result<Output, Error>, for request: Request<Output>)
}

extension RequestInterceptor {
func shouldRescueRequest<Output>(_ request: Request<Output>, error: Error) async throws -> Bool {
var cancellable: Set<AnyCancellable> = []
let onCancel = { cancellable.removeAll() }

guard let rescuePublisher = rescueRequest(request, error: error) else {
return false
}

return try await withTaskCancellationHandler(
handler: { onCancel() },
operation: {
try await withCheckedThrowingContinuation { continuation in
rescuePublisher
.sink(
receiveCompletion: {
if case let .failure(error) = $0 {
return continuation.resume(throwing: error)
}
},
receiveValue: { _ in
continuation.resume(returning: true)
})
.store(in: &cancellable)
}
})
}
}
41 changes: 41 additions & 0 deletions Tests/SimpleHTTPTests/Interceptor/CompositeInterceptorTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import XCTest
import SimpleHTTP

class CompositeInterceptorTests: XCTestCase {
func test_shouldRescue_moreThanOneInterceptorRescue_callFirstOneOnly() async throws {
let interceptors: CompositeInterceptor = [
InterceptorStub(shouldRequestMock: { _ in false }),
InterceptorStub(shouldRequestMock: { _ in true }),
InterceptorStub(shouldRequestMock: { _ in
XCTFail("should not be called because request was already rescued")
return true
})
]

let result = try await interceptors
.shouldRescueRequest(Request<Void>.get("/test"), error: HTTPError(statusCode: 888))

XCTAssertTrue(result)
}
}

private struct InterceptorStub: Interceptor {
var shouldRequestMock: (Error) throws -> Bool = { _ in false }

func shouldRescueRequest<Output>(_ request: Request<Output>, error: Error) async throws -> Bool {
try shouldRequestMock(error)
}

func adaptRequest<Output>(_ request: Request<Output>) -> Request<Output> {
request
}

func adaptOutput<Output>(_ output: Output, for request: Request<Output>) throws -> Output {
output
}

func receivedResponse<Output>(_ result: Result<Output, Error>, for request: Request<Output>) {

}

}
8 changes: 4 additions & 4 deletions Tests/SimpleHTTPTests/Session/SessionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class SessionAsyncTests: XCTestCase {

interceptor.rescueRequestErrorMock = { _ in
isRescued.toggle()
return Just(()).setFailureType(to: Error.self).eraseToAnyPublisher()
return true
}

_ = try await session.response(for: .void())
Expand Down Expand Up @@ -120,16 +120,16 @@ private extension Request {
}

private class InterceptorStub: Interceptor {
var rescueRequestErrorMock: ((Error) -> AnyPublisher<Void, Error>?)?
var rescueRequestErrorMock: (Error) throws -> Bool = { _ in false }
var receivedResponseMock: ((Any, Any) -> Void)?
var adaptResponseMock: ((Any, Any) throws -> Any)?

func adaptRequest<Output>(_ request: Request<Output>) -> Request<Output> {
request
}

func rescueRequest<Output>(_ request: Request<Output>, error: Error) -> AnyPublisher<Void, Error>? {
rescueRequestErrorMock?(error)
func shouldRescueRequest<Output>(_ request: Request<Output>, error: Error) async throws -> Bool {
try rescueRequestErrorMock(error)
}

func adaptOutput<Output>(_ output: Output, for request: Request<Output>) throws -> Output {
Expand Down

0 comments on commit a5a5e07

Please sign in to comment.