Browse Source

Refactored interceptor protocol, added RetryResult, Adapter, and Retrier

Christian Noon 7 years ago
parent
commit
c7759c970e
5 changed files with 221 additions and 87 deletions
  1. 33 4
      Source/AFError.swift
  2. 121 32
      Source/RequestInterceptor.swift
  3. 12 12
      Source/RetryPolicy.swift
  4. 29 9
      Source/Session.swift
  5. 26 30
      Tests/SessionTests.swift

+ 33 - 4
Source/AFError.swift

@@ -30,10 +30,13 @@ import Foundation
 /// - explicitlyCancelled:         Returned when a `Request` is explicitly cancelled.
 /// - invalidURL:                  Returned when a `URLConvertible` type fails to create a valid `URL`.
 /// - parameterEncodingFailed:     Returned when a parameter encoding object throws an error during the encoding process.
+/// - parameterEncoderFailed:      Returned when a parameter encoder throws an error during the encoding process.
 /// - multipartEncodingFailed:     Returned when some step in the multipart encoding process fails.
+/// - requestAdaptationFailed:     Returned when a `RequestAdapter` throws an error during request adaptation.
 /// - responseValidationFailed:    Returned when a `validate()` call fails.
-/// - responseSerializationFailed: Returned when a response serializer encounters an error in the serialization process.
-/// - certificatePinningFailed:    Returned when a response fails certificate pinning.
+/// - responseSerializationFailed: Returned when a response serializer throws an error in the serialization process.
+/// - serverTrustEvaluationFailed: Returned when a `ServerTrustEvaluating` instance fails during the server trust evaluation process.
+/// - requestRetryFailed:          Returned when a `RequestRetrier` throws an error during the request retry process.
 public enum AFError: Error {
     /// The underlying reason the parameter encoding error occurred.
     ///
@@ -187,9 +190,11 @@ public enum AFError: Error {
     case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
     case parameterEncoderFailed(reason: ParameterEncoderFailureReason)
     case multipartEncodingFailed(reason: MultipartEncodingFailureReason)
+    case requestAdaptationFailed(error: Error)
     case responseValidationFailed(reason: ResponseValidationFailureReason)
     case responseSerializationFailed(reason: ResponseSerializationFailureReason)
     case serverTrustEvaluationFailed(reason: ServerTrustFailureReason)
+    case requestRetryFailed(retryError: Error, originError: Error)
 }
 
 extension Error {
@@ -234,6 +239,13 @@ extension AFError {
         return false
     }
 
+    /// Returns whether the AFError is a request adaptation error. When `true`, the `underlyingError` property will
+    /// contain the associated value.
+    public var isRequestAdaptationError: Bool {
+        if case .requestAdaptationFailed = self { return true }
+        return false
+    }
+
     /// Returns whether the `AFError` is a response validation error. When `true`, the `acceptableContentTypes`,
     /// `responseContentType`, and `responseCode` properties will contain the associated values.
     public var isResponseValidationError: Bool {
@@ -253,6 +265,13 @@ extension AFError {
         if case .serverTrustEvaluationFailed = self { return true }
         return false
     }
+
+    /// Returns whether the AFError is a request retry error. When `true`, the `underlyingError` property will
+    /// contain the associated value.
+    public var isRequestRetryError: Bool {
+        if case .requestRetryFailed = self { return true }
+        return false
+    }
 }
 
 // MARK: - Convenience Properties
@@ -278,8 +297,9 @@ extension AFError {
         }
     }
 
-    /// The `Error` returned by a system framework associated with a `.parameterEncodingFailed`,
-    /// `.parameterEncoderFailed`, `.multipartEncodingFailed` or `.responseSerializationFailed` error.
+    /// The underlying `Error` responsible for generating the failure associated with `.parameterEncodingFailed`,
+    /// `.parameterEncoderFailed`, `.multipartEncodingFailed`, `.requestAdaptationFailed`,
+    /// `.responseSerializationFailed`, `.requestRetryFailed` errors.
     public var underlyingError: Error? {
         switch self {
         case .parameterEncodingFailed(let reason):
@@ -288,8 +308,12 @@ extension AFError {
             return reason.underlyingError
         case .multipartEncodingFailed(let reason):
             return reason.underlyingError
+        case .requestAdaptationFailed(let error):
+            return error
         case .responseSerializationFailed(let reason):
             return reason.underlyingError
+        case .requestRetryFailed(let retryError, _):
+            return retryError
         default:
             return nil
         }
@@ -459,12 +483,17 @@ extension AFError: LocalizedError {
             return reason.localizedDescription
         case .multipartEncodingFailed(let reason):
             return reason.localizedDescription
+        case .requestAdaptationFailed(let error):
+            return "Request adaption failed with error: \(error.localizedDescription)"
         case .responseValidationFailed(let reason):
             return reason.localizedDescription
         case .responseSerializationFailed(let reason):
             return reason.localizedDescription
         case .serverTrustEvaluationFailed:
             return "Server trust evaluation failed."
+        case .requestRetryFailed(let retryError, let originError):
+            return "Request retry failed with retry error: \(retryError.localizedDescription), " +
+                "origin error: \(originError.localizedDescription)"
         }
     }
 }

+ 121 - 32
Source/RequestInterceptor.swift

@@ -30,12 +30,42 @@ public protocol RequestAdapter {
     ///
     /// - Parameters:
     ///   - urlRequest: The `URLRequest` to adapt.
+    ///   - session:    The `Session` that will execute the `URLRequest`.
     ///   - completion: The completion handler that must be called when adaptation is complete.
-    func adapt(_ urlRequest: URLRequest, completion: @escaping (Result<URLRequest>) -> Void)
+    func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest>) -> Void)
 }
 
 // MARK: -
 
+public enum RetryResult {
+    case retry
+    case retryWithDelay(TimeInterval)
+    case doNotRetry
+    case doNotRetryWithError(Error)
+}
+
+extension RetryResult {
+    var retryRequired: Bool {
+        switch self {
+        case .retry, .retryWithDelay: return true
+        default:                      return false
+        }
+    }
+
+    var delay: TimeInterval? {
+        switch self {
+        case .retryWithDelay(let delay): return delay
+        case .retry:                     return 0.0
+        default:                         return nil
+        }
+    }
+
+    var error: Error? {
+        guard case .doNotRetryWithError(let error) = self else { return nil }
+        return error
+    }
+}
+
 /// A type that determines whether a request should be retried after being executed by the specified session manager
 /// and encountering an error.
 public protocol RequestRetrier {
@@ -45,11 +75,11 @@ public protocol RequestRetrier {
     /// to be retried. The one requirement is that the completion closure is called to ensure the request is properly
     /// cleaned up after.
     ///
-    /// - parameter session:    The session the request was executed on.
-    /// - parameter request:    The request that failed due to the encountered error.
-    /// - parameter error:      The error encountered when executing the request.
+    /// - parameter request:    The `Request` that failed due to the encountered error.
+    /// - parameter session:    The `Session` the request was executed on.
+    /// - parameter error:      The `Error` encountered when executing the request.
     /// - parameter completion: The completion closure to be executed when retry decision has been determined.
-    func should(_ session: Session, retry request: Request, with error: Error, completion: @escaping (Result<TimeInterval>) -> Void)
+    func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void)
 }
 
 // MARK: -
@@ -57,29 +87,87 @@ public protocol RequestRetrier {
 /// A type that intercepts requests to potentially adapt and retry them.
 public protocol RequestInterceptor: RequestAdapter, RequestRetrier {}
 
+extension RequestInterceptor {
+    public func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest>) -> Void) {
+        completion(.success(urlRequest))
+    }
+
+    public func retry(
+        _ request: Request,
+        for session: Session,
+        dueTo error: Error,
+        completion: @escaping (RetryResult) -> Void)
+    {
+        completion(.doNotRetry)
+    }
+}
+
+public typealias AdaptHandler = (URLRequest, Session, _ completion: (Result<URLRequest>) -> Void) -> Void
+public typealias RetryHandler = (Request, Session, Error, _ completion: (RetryResult) -> Void) -> Void
+
+// MARK: -
+
+open class Adapter: RequestInterceptor {
+    private let adaptHandler: AdaptHandler
+
+    public init(_ adaptHandler: @escaping AdaptHandler) {
+        self.adaptHandler = adaptHandler
+    }
+
+    open func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest>) -> Void) {
+        adaptHandler(urlRequest, session, completion)
+    }
+}
+
+// MARK: -
+
+open class Retrier: RequestInterceptor {
+    private let retryHandler: RetryHandler
+
+    public init(_ retryHandler: @escaping RetryHandler) {
+        self.retryHandler = retryHandler
+    }
+
+    open func retry(
+        _ request: Request,
+        for session: Session,
+        dueTo error: Error,
+        completion: @escaping (RetryResult) -> Void)
+    {
+        retryHandler(request, session, error, completion)
+    }
+}
+
 // MARK: -
 
-open class Interceptor {
+open class Interceptor: RequestInterceptor {
     public let adapters: [RequestAdapter]
     public let retriers: [RequestRetrier]
 
+    public init(adaptHandler: @escaping AdaptHandler, retryHandler: @escaping RetryHandler) {
+        self.adapters = [Adapter(adaptHandler)]
+        self.retriers = [Retrier(retryHandler)]
+    }
+
+    public init(adapter: RequestAdapter, retrier: RequestRetrier) {
+        self.adapters = [adapter]
+        self.retriers = [retrier]
+    }
+
     public init(adapters: [RequestAdapter] = [], retriers: [RequestRetrier] = []) {
         self.adapters = adapters
         self.retriers = retriers
     }
-}
-
-// MARK: - RequestAdapter
 
-extension Interceptor: RequestAdapter {
-    open func adapt(_ urlRequest: URLRequest, completion: @escaping (_ result: Result<URLRequest>) -> Void) {
-        adapt(urlRequest, using: adapters, completion: completion)
+    open func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest>) -> Void) {
+        adapt(urlRequest, for: session, using: adapters, completion: completion)
     }
 
     private func adapt(
         _ urlRequest: URLRequest,
+        for session: Session,
         using adapters: [RequestAdapter],
-        completion: @escaping (_ result: Result<URLRequest>) -> Void)
+        completion: @escaping (Result<URLRequest>) -> Void)
     {
         var pendingAdapters = adapters
 
@@ -87,44 +175,45 @@ extension Interceptor: RequestAdapter {
 
         let adapter = pendingAdapters.removeFirst()
 
-        adapter.adapt(urlRequest) { result in
+        adapter.adapt(urlRequest, for: session) { result in
             switch result {
             case .success(let urlRequest):
-                self.adapt(urlRequest, using: pendingAdapters, completion: completion)
+                self.adapt(urlRequest, for: session, using: pendingAdapters, completion: completion)
             case .failure:
                 completion(result)
             }
         }
     }
-}
 
-// MARK: - RequestRetrier
-
-extension Interceptor: RequestRetrier {
-    open func should(_ session: Session, retry request: Request, with error: Error, completion: @escaping (_ result: Result<TimeInterval>) -> Void) {
-        completion(.success(0.0))
-        should(session, retry: request, with: error, using: retriers, completion: completion)
+    open func retry(
+        _ request: Request,
+        for session: Session,
+        dueTo error: Error,
+        completion: @escaping (RetryResult) -> Void)
+    {
+        retry(request, for: session, dueTo: error, using: retriers, completion: completion)
     }
 
-    private func should(
-        _ session: Session,
-        retry request: Request,
-        with error: Error,
+    private func retry(
+        _ request: Request,
+        for session: Session,
+        dueTo error: Error,
         using retriers: [RequestRetrier],
-        completion: @escaping (_ result: Result<TimeInterval>) -> Void)
+        completion: @escaping (RetryResult) -> Void)
     {
         var pendingRetriers = retriers
 
-        guard !pendingRetriers.isEmpty else { completion(.failure(error)); return }
+        guard !pendingRetriers.isEmpty else { completion(.doNotRetry); return }
 
         let retrier = pendingRetriers.removeFirst()
 
-        retrier.should(session, retry: request, with: error) { result in
+        retrier.retry(request, for: session, dueTo: error) { result in
             switch result {
-            case .success:
+            case .retry, .retryWithDelay, .doNotRetryWithError:
                 completion(result)
-            case .failure(let error):
-                self.should(session, retry: request, with: error, using: pendingRetriers, completion: completion)
+            case .doNotRetry:
+                // Only continue to the next retrier if retry was not triggered and no error was encountered
+                self.retry(request, for: session, dueTo: error, using: pendingRetriers, completion: completion)
             }
         }
     }

+ 12 - 12
Source/RetryPolicy.swift

@@ -42,9 +42,9 @@ open class RetryPolicy {
         .delete,  // [Delete](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7) - not always idempotent
         .get,     // [GET](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3) - generally idempotent
         .head,    // [HEAD](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4) - generally idempotent
-        .options, // [OPTIONS](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2) - inheritantly idempotent
+        .options, // [OPTIONS](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2) - inherently idempotent
         .put,     // [PUT](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6) - not always idempotent
-        .trace    // [TRACE](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.8) - inheritantly idempotent
+        .trace    // [TRACE](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.8) - inherently idempotent
     ]
 
     /// The default HTTP status codes to retry.
@@ -73,7 +73,7 @@ open class RetryPolicy {
         //.backgroundSessionRequiresSharedContainer,
 
         // [System] The app is suspended or exits while a background data task is processing.
-        //   - [Enabled] App can be foregrounded to launched to recover.
+        //   - [Enabled] App can be foregrounded or launched to recover.
         .backgroundSessionWasDisconnected,
 
         // [Network] The URL Loading system received bad data from the server.
@@ -314,26 +314,26 @@ open class RetryPolicy {
 // MARK: -
 
 extension RetryPolicy: RequestRetrier {
-    public func should(
-        _ session: Session,
-        retry request: Request,
-        with error: Error,
-        completion: @escaping (_ result: Result<TimeInterval>) -> Void)
+    open func retry(
+        _ request: Request,
+        for session: Session,
+        dueTo error: Error,
+        completion: @escaping (RetryResult) -> Void)
     {
         if
             request.retryCount < retryLimit,
             let httpMethod = request.request?.method,
             retryableHTTPMethods.contains(httpMethod),
-            isRetryable(request.response, with: error)
+            shouldRetry(response: request.response, error: error)
         {
             let timeDelay = pow(Double(exponentialBackoffBase), Double(request.retryCount)) * exponentialBackoffScale
-            completion(.success(timeDelay))
+            completion(.retryWithDelay(timeDelay))
         } else {
-            completion(.failure(error))
+            completion(.doNotRetry)
         }
     }
 
-    private func isRetryable(_ response: HTTPURLResponse?, with error: Error) -> Bool {
+    private func shouldRetry(response: HTTPURLResponse?, error: Error) -> Bool {
         if let statusCode = response?.statusCode, retryableHTTPStatusCodes.contains(statusCode) {
             return true
         } else if let errorCode = (error as? URLError)?.code, retryableURLErrorCodes.contains(errorCode) {

+ 29 - 9
Source/Session.swift

@@ -425,7 +425,7 @@ open class Session {
             guard !request.isCancelled else { return }
 
             if let adapter = adapter(for: request) {
-                adapter.adapt(initialRequest) { result in
+                adapter.adapt(initialRequest, for: self) { result in
                     do {
                         let adaptedRequest = try result.unwrap()
 
@@ -434,7 +434,8 @@ open class Session {
                             self.didCreateURLRequest(adaptedRequest, for: request)
                         }
                     } catch {
-                        self.rootQueue.async { request.didFailToAdaptURLRequest(initialRequest, withError: error) }
+                        let adaptError = AFError.requestAdaptationFailed(error: error)
+                        self.rootQueue.async { request.didFailToAdaptURLRequest(initialRequest, withError: adaptError) }
                     }
                 }
             } else {
@@ -482,11 +483,19 @@ open class Session {
     // MARK: - Adapters and Retriers
 
     func adapter(for request: Request) -> RequestAdapter? {
-        return request.interceptor ?? interceptor
+        if let requestInterceptor = request.interceptor, let sessionInterceptor = interceptor {
+            return Interceptor(adapters: [requestInterceptor, sessionInterceptor])
+        } else {
+            return request.interceptor ?? interceptor
+        }
     }
 
     func retrier(for request: Request) -> RequestRetrier? {
-        return request.interceptor ?? interceptor
+        if let requestInterceptor = request.interceptor, let sessionInterceptor = interceptor {
+            return Interceptor(retriers: [requestInterceptor, sessionInterceptor])
+        } else {
+            return request.interceptor ?? interceptor
+        }
     }
 }
 
@@ -504,21 +513,32 @@ extension Session: RequestDelegate {
     public func retryRequest(_ request: Request, ifNecessaryWithError error: Error) {
         guard let retrier = retrier(for: request) else { request.finish(); return }
 
-        retrier.should(self, retry: request, with: error) { result in
+        retrier.retry(request, for: self, dueTo: error) { result in
             guard !request.isCancelled else { return }
 
             self.rootQueue.async {
                 guard !request.isCancelled else { return }
 
-                switch result {
-                case .success(let retryDelay):
-                    self.rootQueue.after(retryDelay) {
+                if result.retryRequired {
+                    let retry: () -> Void = {
                         guard !request.isCancelled else { return }
 
                         request.requestIsRetrying()
                         self.perform(request)
                     }
-                case .failure(let retryError):
+
+                    if let retryDelay = result.delay {
+                        self.rootQueue.after(retryDelay) { retry() }
+                    } else {
+                        self.rootQueue.async { retry() }
+                    }
+                } else {
+                    var retryError = error
+
+                    if let retryResultError = result.error {
+                        retryError = AFError.requestRetryFailed(retryError: retryResultError, originError: error)
+                    }
+
                     request.finish(error: retryError)
                 }
             }

+ 26 - 30
Tests/SessionTests.swift

@@ -39,7 +39,7 @@ class SessionTestCase: BaseTestCase {
             self.throwsError = throwsError
         }
 
-        func adapt(_ urlRequest: URLRequest, completion: @escaping (Result<URLRequest>) -> Void) {
+        func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest>) -> Void) {
             let result: Result<URLRequest> = Result {
                 guard !throwsError else { throw AFError.invalidURL(url: "") }
 
@@ -51,10 +51,6 @@ class SessionTestCase: BaseTestCase {
 
             completion(result)
         }
-
-        func should(_ session: Session, retry request: Request, with error: Error, completion: @escaping (Result<TimeInterval>) -> Void) {
-            completion(.failure(error))
-        }
     }
 
     private class RequestHandler: RequestInterceptor {
@@ -67,7 +63,7 @@ class SessionTestCase: BaseTestCase {
         var throwsErrorOnSecondAdapt = false
         var shouldRetry = true
 
-        func adapt(_ urlRequest: URLRequest, completion: @escaping (Result<URLRequest>) -> Void) {
+        func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest>) -> Void) {
             let result: Result<URLRequest> = Result {
                 if throwsErrorOnFirstAdapt {
                     throwsErrorOnFirstAdapt = false
@@ -93,21 +89,21 @@ class SessionTestCase: BaseTestCase {
             completion(result)
         }
 
-        func should(
-            _ session: Session,
-            retry request: Request,
-            with error: Error,
-            completion: @escaping (Result<TimeInterval>) -> Void)
+        func retry(
+            _ request: Request,
+            for session: Session,
+            dueTo error: Error,
+            completion: @escaping (RetryResult) -> Void)
         {
-            guard shouldRetry else { completion(.failure(error)); return }
+            guard shouldRetry else { completion(.doNotRetry); return }
 
             retryCount += 1
             retryErrors.append(error)
 
             if retryCount < 2 {
-                completion(.success(0.0))
+                completion(.retry)
             } else {
-                completion(.failure(error))
+                completion(.doNotRetry)
             }
         }
     }
@@ -117,7 +113,7 @@ class SessionTestCase: BaseTestCase {
         var retryCount = 0
         var retryErrors: [Error] = []
 
-        func adapt(_ urlRequest: URLRequest, completion: @escaping (Result<URLRequest>) -> Void) {
+        func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest>) -> Void) {
             let result: Result<URLRequest> = Result {
                 adaptedCount += 1
 
@@ -129,16 +125,16 @@ class SessionTestCase: BaseTestCase {
             completion(result)
         }
 
-        func should(
-            _ session: Session,
-            retry request: Request,
-            with error: Error,
-            completion: @escaping (Result<TimeInterval>) -> Void)
+        func retry(
+            _ request: Request,
+            for session: Session,
+            dueTo error: Error,
+            completion: @escaping (RetryResult) -> Void)
         {
             retryCount += 1
             retryErrors.append(error)
 
-            completion(.success(0.0))
+            completion(.retry)
         }
     }
 
@@ -642,7 +638,7 @@ class SessionTestCase: BaseTestCase {
 
         // Then
         XCTAssertEqual(request1.task?.originalRequest?.httpMethod, adapter.method.rawValue)
-        XCTAssertEqual(request2.task?.originalRequest?.httpMethod, HTTPMethod.get.rawValue)
+        XCTAssertEqual(request2.task?.originalRequest?.httpMethod, HTTPMethod.post.rawValue)
         XCTAssertEqual(requestHandler.adaptedCount, 1)
     }
 
@@ -671,7 +667,7 @@ class SessionTestCase: BaseTestCase {
 
         // Then
         XCTAssertEqual(request1.task?.originalRequest?.httpMethod, adapter.method.rawValue)
-        XCTAssertEqual(request2.task?.originalRequest?.httpMethod, HTTPMethod.get.rawValue)
+        XCTAssertEqual(request2.task?.originalRequest?.httpMethod, HTTPMethod.post.rawValue)
         XCTAssertEqual(requestHandler.adaptedCount, 1)
     }
 
@@ -701,7 +697,7 @@ class SessionTestCase: BaseTestCase {
 
         // Then
         XCTAssertEqual(request1.task?.originalRequest?.httpMethod, adapter.method.rawValue)
-        XCTAssertEqual(request2.task?.originalRequest?.httpMethod, HTTPMethod.post.rawValue)
+        XCTAssertEqual(request2.task?.originalRequest?.httpMethod, HTTPMethod.get.rawValue)
         XCTAssertEqual(requestHandler.adaptedCount, 1)
     }
 
@@ -731,7 +727,7 @@ class SessionTestCase: BaseTestCase {
 
         // Then
         XCTAssertEqual(request1.task?.originalRequest?.httpMethod, adapter.method.rawValue)
-        XCTAssertEqual(request2.task?.originalRequest?.httpMethod, HTTPMethod.post.rawValue)
+        XCTAssertEqual(request2.task?.originalRequest?.httpMethod, HTTPMethod.get.rawValue)
         XCTAssertEqual(requestHandler.adaptedCount, 1)
     }
 
@@ -761,7 +757,7 @@ class SessionTestCase: BaseTestCase {
 
         // Then
         XCTAssertEqual(request1.task?.originalRequest?.httpMethod, adapter.method.rawValue)
-        XCTAssertEqual(request2.task?.originalRequest?.httpMethod, HTTPMethod.post.rawValue)
+        XCTAssertEqual(request2.task?.originalRequest?.httpMethod, HTTPMethod.get.rawValue)
         XCTAssertEqual(requestHandler.adaptedCount, 1)
     }
 
@@ -796,8 +792,8 @@ class SessionTestCase: BaseTestCase {
         // Then
         for request in requests {
             if let error = request.error?.asAFError {
-                XCTAssertTrue(error.isInvalidURLError)
-                XCTAssertEqual(error.urlConvertible as? String, "")
+                XCTAssertTrue(error.isRequestAdaptationError)
+                XCTAssertEqual(error.underlyingError?.asAFError?.urlConvertible as? String, "")
             } else {
                 XCTFail("error should not be nil")
             }
@@ -979,8 +975,8 @@ class SessionTestCase: BaseTestCase {
         XCTAssertTrue(session.requestTaskMap.isEmpty)
 
         if let error = response?.result.error?.asAFError {
-            XCTAssertTrue(error.isInvalidURLError)
-            XCTAssertEqual(error.urlConvertible as? String, "")
+            XCTAssertTrue(error.isRequestAdaptationError)
+            XCTAssertEqual(error.underlyingError?.asAFError?.urlConvertible as? String, "")
         } else {
             XCTFail("error should not be nil")
         }