Procházet zdrojové kódy

Added RequestAdapterState struct to store additional state during adaptation (#3504)

* Added RequestAdapterState struct to store additional state during adaptation

* Updated adapter tests to verify RequestAdapterState passthrough

* Updated RequestAdapter documentation in AdvancedUsage

* Added comment about RequestAdapterState method becoming new requirement
Christian Noon před 4 roky
rodič
revize
79e9419d40

+ 15 - 1
Documentation/AdvancedUsage.md

@@ -501,7 +501,7 @@ Alamofire’s `RequestInterceptor` protocol (composed of the `RequestAdapter` an
 ### `RequestAdapter`
 Alamofire’s  `RequestAdapter` protocol allows each `URLRequest` that’s to be performed by a `Session` to be inspected and mutated before being issued over the network. One very common use of an adapter is to add an `Authorization` header to requests behind a certain type of authentication.
 
-The `RequestAdapter` protocol has a single requirement: 
+The `RequestAdapter` protocol has one required method: 
 
 ```swift
 func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void)
@@ -525,6 +525,20 @@ func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping
 }
 ```
 
+The `RequestAdapter` also contains a second method with a default protocol extension implementation to support backwards compatibility:
+
+```swift
+func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping (Result<URLRequest, Error>) -> Void)
+```
+
+This second method uses a `RequestAdapterState` type to expose additional internal state beyond the first method including:
+
+- `requestID`: The `UUID` of the `Request` associated with the `URLRequest` to adapt.
+
+This `requestID` is very useful when trying to map custom types associated with the original `Request` to perform custom operations inside the `RequestAdapter`.
+
+> This second method will become the new requirement in the next MAJOR version of Alamofire.
+
 ### `RequestRetrier`
 Alamofire’s `RequestRetrier` protocol allows a `Request` that encountered an `Error` while being executed to be retried. This includes `Error`s produced at any stage of Alamofire’s [request pipeline](#the-request-pipeline).
 

+ 53 - 0
Source/RequestInterceptor.swift

@@ -24,6 +24,17 @@
 
 import Foundation
 
+/// Stores all state associated with a `URLRequest` being adapted.
+public struct RequestAdapterState {
+    /// The `UUID` of the `Request` associated with the `URLRequest` to adapt.
+    public let requestID: UUID
+
+    /// The `Session` associated with the `URLRequest` to adapt.
+    public let session: Session
+}
+
+// MARK: -
+
 /// A type that can inspect and optionally adapt a `URLRequest` in some manner if necessary.
 public protocol RequestAdapter {
     /// Inspects and adapts the specified `URLRequest` in some manner and calls the completion handler with the Result.
@@ -33,6 +44,20 @@ public protocol RequestAdapter {
     ///   - session:    The `Session` that will execute the `URLRequest`.
     ///   - completion: The completion handler that must be called when adaptation is complete.
     func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void)
+
+    /// Inspects and adapts the specified `URLRequest` in some manner and calls the completion handler with the Result.
+    ///
+    /// - Parameters:
+    ///   - urlRequest: The `URLRequest` to adapt.
+    ///   - state:      The `RequestAdapterState` associated with the `URLRequest`.
+    ///   - completion: The completion handler that must be called when adaptation is complete.
+    func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping (Result<URLRequest, Error>) -> Void)
+}
+
+extension RequestAdapter {
+    public func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping (Result<URLRequest, Error>) -> Void) {
+        adapt(urlRequest, for: state.session, completion: completion)
+    }
 }
 
 // MARK: -
@@ -126,6 +151,10 @@ open class Adapter: RequestInterceptor {
     open func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
         adaptHandler(urlRequest, session, completion)
     }
+
+    open func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping (Result<URLRequest, Error>) -> Void) {
+        adaptHandler(urlRequest, state.session, completion)
+    }
 }
 
 #if swift(>=5.5)
@@ -237,6 +266,30 @@ open class Interceptor: RequestInterceptor {
         }
     }
 
+    open func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping (Result<URLRequest, Error>) -> Void) {
+        adapt(urlRequest, using: state, adapters: adapters, completion: completion)
+    }
+
+    private func adapt(_ urlRequest: URLRequest,
+                       using state: RequestAdapterState,
+                       adapters: [RequestAdapter],
+                       completion: @escaping (Result<URLRequest, Error>) -> Void) {
+        var pendingAdapters = adapters
+
+        guard !pendingAdapters.isEmpty else { completion(.success(urlRequest)); return }
+
+        let adapter = pendingAdapters.removeFirst()
+
+        adapter.adapt(urlRequest, using: state) { result in
+            switch result {
+            case let .success(urlRequest):
+                self.adapt(urlRequest, using: state, adapters: pendingAdapters, completion: completion)
+            case .failure:
+                completion(result)
+            }
+        }
+    }
+
     open func retry(_ request: Request,
                     for session: Session,
                     dueTo error: Error,

+ 3 - 1
Source/Session.swift

@@ -1068,7 +1068,9 @@ open class Session {
             return
         }
 
-        adapter.adapt(initialRequest, for: self) { result in
+        let adapterState = RequestAdapterState(requestID: request.id, session: self)
+
+        adapter.adapt(initialRequest, using: adapterState) { result in
             do {
                 let adaptedRequest = try result.get()
                 try adaptedRequest.validate()

+ 62 - 0
Tests/RequestInterceptorTests.swift

@@ -99,6 +99,48 @@ final class AdapterTestCase: BaseTestCase {
         XCTAssertTrue(result.isSuccess)
     }
 
+    func testThatAdapterCallsAdaptHandlerWithStateAPI() {
+        // Given
+        class StateCaptureAdapter: Adapter {
+            private(set) var urlRequest: URLRequest?
+            private(set) var state: RequestAdapterState?
+
+            override func adapt(_ urlRequest: URLRequest,
+                                using state: RequestAdapterState,
+                                completion: @escaping (Result<URLRequest, Error>) -> Void) {
+                self.urlRequest = urlRequest
+                self.state = state
+
+                super.adapt(urlRequest, using: state, completion: completion)
+            }
+        }
+
+        let urlRequest = Endpoint().urlRequest
+        let session = Session()
+        let requestID = UUID()
+
+        var adapted = false
+
+        let adapter = StateCaptureAdapter { urlRequest, _, completion in
+            adapted = true
+            completion(.success(urlRequest))
+        }
+
+        let state = RequestAdapterState(requestID: requestID, session: session)
+
+        var result: Result<URLRequest, Error>!
+
+        // When
+        adapter.adapt(urlRequest, using: state) { result = $0 }
+
+        // Then
+        XCTAssertTrue(adapted)
+        XCTAssertTrue(result.isSuccess)
+        XCTAssertEqual(adapter.urlRequest, urlRequest)
+        XCTAssertEqual(adapter.state?.requestID, requestID)
+        XCTAssertEqual(adapter.state?.session.session, session.session)
+    }
+
     func testThatAdapterCallsRequestRetrierDefaultImplementationInProtocolExtension() {
         // Given
         let session = Session(startRequestsImmediately: false)
@@ -330,6 +372,26 @@ final class InterceptorTests: BaseTestCase {
         XCTAssertTrue(result.failure is MockError)
     }
 
+    func testThatInterceptorCanAdaptRequestWithMultipleAdaptersUsingStateAPI() {
+        // Given
+        let urlRequest = Endpoint().urlRequest
+        let session = Session()
+
+        let adapter1 = Adapter { urlRequest, _, completion in completion(.success(urlRequest)) }
+        let adapter2 = Adapter { _, _, completion in completion(.failure(MockError())) }
+        let interceptor = Interceptor(adapters: [adapter1, adapter2])
+        let state = RequestAdapterState(requestID: UUID(), session: session)
+
+        var result: Result<URLRequest, Error>!
+
+        // When
+        interceptor.adapt(urlRequest, using: state) { result = $0 }
+
+        // Then
+        XCTAssertTrue(result.isFailure)
+        XCTAssertTrue(result.failure is MockError)
+    }
+
     func testThatInterceptorCanAdaptRequestAsynchronously() {
         // Given
         let urlRequest = Endpoint().urlRequest

+ 9 - 4
Tests/SessionTests.swift

@@ -40,7 +40,7 @@ final class SessionTestCase: BaseTestCase {
             self.throwsError = throwsError
         }
 
-        func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
+        func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping (Result<URLRequest, Error>) -> Void) {
             adaptedCount += 1
 
             let result: Result<URLRequest, Error> = Result {
@@ -67,7 +67,7 @@ final class SessionTestCase: BaseTestCase {
             self.throwsError = throwsError
         }
 
-        func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
+        func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping (Result<URLRequest, Error>) -> Void) {
             adaptedCount += 1
 
             let result: Result<URLRequest, Error> = Result {
@@ -101,7 +101,10 @@ final class SessionTestCase: BaseTestCase {
         var shouldRetry = true
         var retryDelay: TimeInterval?
 
-        func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
+        func adapt(
+            _ urlRequest: URLRequest,
+            using state: RequestAdapterState,
+            completion: @escaping (Result<URLRequest, Error>) -> Void) {
             adaptCalledCount += 1
 
             let result: Result<URLRequest, Error> = Result {
@@ -165,7 +168,9 @@ final class SessionTestCase: BaseTestCase {
         var retryCount = 0
         var retryErrors: [Error] = []
 
-        func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
+        func adapt(_ urlRequest: URLRequest,
+                   using state: RequestAdapterState,
+                   completion: @escaping (Result<URLRequest, Error>) -> Void) {
             adaptCalledCount += 1
 
             let result: Result<URLRequest, Error> = Result {