Browse Source

Customizable Empty Response Handling (#2639)

* Work towards server trust enhancements.

* Refactor the rewrite! (#2585)

* Refactor request storage out of SessionDelegate.

* Continue development.

* Rename SessionManager -> Session, update environment.

* Rename global Alamofire enum to AF, to avoid collision.

* Sort project.

* Whitespace cleanup.

* Reimplement module changes from bad rebase.

* Finalize errors, refactor testing.

* Standardize self-signed support, add error descriptions.

* Remove per-target setting.

* Make RequestAdapter async.

* Add HTTPHeaders type.

* Update for review suggestions.

* Add HTTPHeaders tests, cleanup whitespace.

* Add inline documentation.

* Prototype encodable request support.

* Formalize ParamterEncoder protocol and initial API.

* Whitespace cleanup.

* Add URLEncodedFormEncoder.

* Sort HTTPHeader methods.

* Implement and partially test URLEncodedFormParameterEncoder.

* Remove unnecessary TODOs.

* Correct spelling in the file name.

* Update errors, documentation, and tests.

* Support customizable empty reponse codes and methods.

* Allow customization of space encoding and percent escaped character set.

* Allow customization of space encoding and percent escaped character set.

* Alamofire 5: Empty Response Refactor (2nd Refactor) (#2646)

* Removed AnyResponseSerializer since it is no longer in use

* Moved empty request and response status code logic into protocol extension

* Squashed commit of the following:

commit a3dc191bec2b79bc189c2fbc05d2cf7fcccbfd02
Author: Jon Shier <jon@jonshier.com>
Date:   Sat Nov 24 17:35:17 2018 -0500

    Add additional tests.

commit ae8fde4f8957f84cf3dcb8ac6b99374e198e1a18
Merge: 34f3668 ea48749
Author: Jon Shier <jon@jonshier.com>
Date:   Sat Nov 24 16:43:12 2018 -0500

    Merge remote-tracking branch 'origin/feature/httpheaders' into feature/encodable-requests

    # Conflicts:
    #	Source/AFError.swift

commit ea48749660944da7837b4a329fdbcd86c87b9323
Author: Jon Shier <jon@jonshier.com>
Date:   Sat Nov 24 16:25:48 2018 -0500

    Call add instead of update.

commit 2d735627ac2ed9278dbc892be13f848728f6ae7d
Author: Jon Shier <jon@jonshier.com>
Date:   Sat Nov 24 16:24:48 2018 -0500

    Add add methods to HTTPHeaders, whitespace cleanup.

commit f2bf3952e23cf2ac03dddb26541fe545365a6aca
Merge: 410a2e3 7a73af6
Author: Jon Shier <jon@jonshier.com>
Date:   Wed Nov 21 19:45:38 2018 -0500

    Merge remote-tracking branch 'origin/alamofire5' into feature/httpheaders

    # Conflicts:
    #	Tests/SessionManagerTests.swift

commit 410a2e3d9edb8ab26d4b7ed396d686725c17ba59
Author: Jon Shier <jon@jonshier.com>
Date:   Wed Nov 21 19:44:15 2018 -0500

    Squashed commit of the following:

    commit 7a73af699037fb447c33412f92cfe032cfd84019
    Author: Jon Shier <jon@jonshier.com>
    Date:   Wed Nov 21 19:39:20 2018 -0500

        Async RequestAdapter (#2628)

        * Work towards server trust enhancements.

        * Refactor the rewrite! (#2585)

        * Refactor request storage out of SessionDelegate.

        * Continue development.

        * Rename SessionManager -> Session, update environment.

        * Rename global Alamofire enum to AF, to avoid collision.

        * Sort project.

        * Whitespace cleanup.

        * Reimplement module changes from bad rebase.

        * Finalize errors, refactor testing.

        * Standardize self-signed support, add error descriptions.

        * Remove per-target setting.

        * Make RequestAdapter async.

    commit ccfb96acd46c4477d24ea3f6a01c395cd273c5bd
    Author: Jon Shier <jon@jonshier.com>
    Date:   Wed Nov 21 19:32:04 2018 -0500

        Alamofire 5: Server Trust Errors (#2608)

        * Work towards server trust enhancements.

        * Refactor the rewrite! (#2585)

        * Refactor request storage out of SessionDelegate.

        * Continue development.

        * Rename SessionManager -> Session, update environment.

        * Rename global Alamofire enum to AF, to avoid collision.

        * Sort project.

        * Whitespace cleanup.

        * Reimplement module changes from bad rebase.

        * Finalize errors, refactor testing.

        * Standardize self-signed support, add error descriptions.

        * Remove per-target setting.

        * Refactor evaluation API, DRY up a little bit.

        * Update convienience property.

        * Add comment for public `Error` API.

commit 7a73af699037fb447c33412f92cfe032cfd84019
Author: Jon Shier <jon@jonshier.com>
Date:   Wed Nov 21 19:39:20 2018 -0500

    Async RequestAdapter (#2628)

    * Work towards server trust enhancements.

    * Refactor the rewrite! (#2585)

    * Refactor request storage out of SessionDelegate.

    * Continue development.

    * Rename SessionManager -> Session, update environment.

    * Rename global Alamofire enum to AF, to avoid collision.

    * Sort project.

    * Whitespace cleanup.

    * Reimplement module changes from bad rebase.

    * Finalize errors, refactor testing.

    * Standardize self-signed support, add error descriptions.

    * Remove per-target setting.

    * Make RequestAdapter async.

commit ccfb96acd46c4477d24ea3f6a01c395cd273c5bd
Author: Jon Shier <jon@jonshier.com>
Date:   Wed Nov 21 19:32:04 2018 -0500

    Alamofire 5: Server Trust Errors (#2608)

    * Work towards server trust enhancements.

    * Refactor the rewrite! (#2585)

    * Refactor request storage out of SessionDelegate.

    * Continue development.

    * Rename SessionManager -> Session, update environment.

    * Rename global Alamofire enum to AF, to avoid collision.

    * Sort project.

    * Whitespace cleanup.

    * Reimplement module changes from bad rebase.

    * Finalize errors, refactor testing.

    * Standardize self-signed support, add error descriptions.

    * Remove per-target setting.

    * Refactor evaluation API, DRY up a little bit.

    * Update convienience property.

    * Add comment for public `Error` API.

commit 34f36687944e891a196709a52da3df0b3b066d9c
Author: Jon Shier <jon@jonshier.com>
Date:   Wed Nov 21 19:26:32 2018 -0500

    Add additional tests.

commit ba2e15cba641af7626a38abda2d78a3ceda2612b
Author: Jon Shier <jon@jonshier.com>
Date:   Wed Nov 21 18:56:14 2018 -0500

    Update comments, DRY up single value container.

commit 38d764bf693f4312b62c9819c9d16644170f277f
Author: Jon Shier <jon@jonshier.com>
Date:   Wed Nov 21 18:36:01 2018 -0500

    Whitespace cleanup.

commit b0cf0c529c603b5603b7944032a52798e1d8d70b
Merge: 22ccd91 4adae8c
Author: Jon Shier <jon@jonshier.com>
Date:   Wed Nov 21 18:35:16 2018 -0500

    Merge remote-tracking branch 'origin/feature/httpheaders' into feature/encodable-requests

commit 4adae8c7b250486aeac4f55c1f06501ac44fc05b
Author: Jon Shier <jon@jonshier.com>
Date:   Wed Nov 21 18:30:59 2018 -0500

    Whitespace cleanup.

commit aaa3e6a10d2b14ad0dbc049dc5aa3b1b9bb8fa2e
Author: Jon Shier <jon@jonshier.com>
Date:   Wed Nov 21 18:29:30 2018 -0500

    Updates for review.

commit 22ccd9125263c538c7ee21c7d1f16bbb9be1f217
Author: Christian Noon <christian.noon@gmail.com>
Date:   Wed Nov 21 14:20:08 2018 -0800

    Enabled code coverage for iOS, macOS, and tvOS framework schemes (#2645)

* Update tests and clean up default value handling.

* Fix bad merge.

* Fix misspelling.
Jon Shier 7 years ago
parent
commit
f7ade3da0e

+ 3 - 3
Source/AFError.swift

@@ -130,13 +130,13 @@ public enum AFError: Error {
     /// - jsonSerializationFailed:         JSON serialization failed with an underlying system error.
     /// - invalidEmptyResponse:            Generic serialization failed for an empty response that wasn't type `Empty`.
     public enum ResponseSerializationFailureReason {
-        case inputDataNil
         case inputDataNilOrZeroLength
         case inputFileNil
         case inputFileReadFailed(at: URL)
         case stringSerializationFailed(encoding: String.Encoding)
         case jsonSerializationFailed(error: Error)
         case invalidEmptyResponse(type: String)
+        case jsonDecodingFailed(error: Error)
     }
 
     /// Underlying reason a server trust evaluation error occured.
@@ -534,8 +534,6 @@ extension AFError.MultipartEncodingFailureReason {
 extension AFError.ResponseSerializationFailureReason {
     var localizedDescription: String {
         switch self {
-        case .inputDataNil:
-            return "Response could not be serialized, input data was nil."
         case .inputDataNilOrZeroLength:
             return "Response could not be serialized, input data was nil or zero length."
         case .inputFileNil:
@@ -548,6 +546,8 @@ extension AFError.ResponseSerializationFailureReason {
             return "JSON could not be serialized because of error:\n\(error.localizedDescription)"
         case .invalidEmptyResponse(let type):
             return "Empty response could not be serialized to type: \(type). Use Empty as the expected type for such responses."
+        case .jsonDecodingFailed(let error):
+            return "JSON could not be decoded because of error:\n\(error.localizedDescription)"
         }
     }
 }

+ 126 - 106
Source/ResponseSerialization.swift

@@ -45,7 +45,33 @@ public protocol DownloadResponseSerializerProtocol {
 }
 
 /// A serializer that can handle both data and download responses.
-public protocol ResponseSerializer: DataResponseSerializerProtocol & DownloadResponseSerializerProtocol { }
+public protocol ResponseSerializer: DataResponseSerializerProtocol & DownloadResponseSerializerProtocol {
+    var emptyRequestMethods: Set<HTTPMethod> { get }
+    var emptyResponseCodes: Set<Int> { get }
+}
+
+extension ResponseSerializer {
+    public static var defaultEmptyRequestMethods: Set<HTTPMethod> { return [.head] }
+    public static var defaultEmptyResponseCodes: Set<Int> { return [204, 205] }
+
+    var emptyRequestMethods: Set<HTTPMethod> { return Self.defaultEmptyRequestMethods }
+    var emptyResponseCodes: Set<Int> { return Self.defaultEmptyResponseCodes }
+
+    public func requestAllowsEmptyResponseData(_ request: URLRequest?) -> Bool? {
+        return request.flatMap { $0.httpMethod }
+                      .flatMap(HTTPMethod.init)
+                      .map { emptyRequestMethods.contains($0) }
+    }
+
+    public func responseAllowsEmptyResponseData(_ response: HTTPURLResponse?) -> Bool? {
+        return response.flatMap { $0.statusCode }
+                       .map { emptyResponseCodes.contains($0) }
+    }
+
+    public func emptyResponseAllowed(forRequest request: URLRequest?, response: HTTPURLResponse?) -> Bool {
+        return requestAllowsEmptyResponseData(request) ?? responseAllowsEmptyResponseData(response) ?? false
+    }
+}
 
 /// By default, any serializer declared to conform to both types will get file serialization for free, as it just feeds
 /// the data read from disk into the data response serializer.
@@ -72,66 +98,6 @@ public extension DownloadResponseSerializerProtocol where Self: DataResponseSeri
     }
 }
 
-// MARK: - AnyResponseSerializer
-
-/// A generic `ResponseSerializer` conforming type.
-public final class AnyResponseSerializer<Value>: ResponseSerializer {
-    /// A closure which can be used to serialize data responses.
-    public typealias DataSerializer = (_ request: URLRequest?, _ response: HTTPURLResponse?, _ data: Data?, _ error: Error?) throws -> Value
-    /// A closure which can be used to serialize download reponses.
-    public typealias DownloadSerializer = (_ request: URLRequest?, _ response: HTTPURLResponse?, _ fileURL: URL?, _ error: Error?) throws -> Value
-
-    let dataSerializer: DataSerializer
-    let downloadSerializer: DownloadSerializer?
-
-    /// Initialze the instance with both a `DataSerializer` closure and a `DownloadSerializer` closure.
-    ///
-    /// - Parameters:
-    ///   - dataSerializer:     A `DataSerializer` closure.
-    ///   - downloadSerializer: A `DownloadSerializer` closure.
-    public init(dataSerializer: @escaping DataSerializer, downloadSerializer: @escaping DownloadSerializer) {
-        self.dataSerializer = dataSerializer
-        self.downloadSerializer = downloadSerializer
-    }
-
-    /// Initialze the instance with a `DataSerializer` closure. Download serialization will fallback to a default
-    /// implementation.
-    ///
-    /// - Parameters:
-    ///   - dataSerializer:     A `DataSerializer` closure.
-    public init(dataSerializer: @escaping DataSerializer) {
-        self.dataSerializer = dataSerializer
-        self.downloadSerializer = nil
-    }
-
-    public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Value {
-        return try dataSerializer(request, response, data, error)
-    }
-
-    public func serializeDownload(request: URLRequest?, response: HTTPURLResponse?, fileURL: URL?, error: Error?) throws -> Value {
-        return try downloadSerializer?(request, response, fileURL, error) ?? { (request, response, fileURL, error) in
-            guard error == nil else { throw error! }
-
-            guard let fileURL = fileURL else {
-                throw AFError.responseSerializationFailed(reason: .inputFileNil)
-            }
-
-            let data: Data
-            do {
-                data = try Data(contentsOf: fileURL)
-            } catch {
-                throw AFError.responseSerializationFailed(reason: .inputFileReadFailed(at: fileURL))
-            }
-
-            do {
-                return try serialize(request: request, response: response, data: data, error: error)
-            } catch {
-                throw error
-            }
-        }(request, response, fileURL, error)
-    }
-}
-
 // MARK: - Default
 
 extension DataRequest {
@@ -303,19 +269,35 @@ extension DataRequest {
 /// request returning `nil` or no data is considered an error. However, if the response is has a status code valid for
 /// empty responses (`204`, `205`), then an empty `Data` value is returned.
 public final class DataResponseSerializer: ResponseSerializer {
+    /// HTTP response codes for which empty responses are allowed.
+    public let emptyResponseCodes: Set<Int>
+    /// HTTP request methods for which empty responses are allowed.
+    public let emptyRequestMethods: Set<HTTPMethod>
 
-    public init() { }
+    /// Creates an instance using the provided values.
+    ///
+    /// - Parameters:
+    ///   - emptyResponseCodes:  The HTTP response codes for which empty responses are allowed. Defaults to
+    ///                          `[204, 205]`.
+    ///   - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. Defaults to `[.head]`.
+    public init(emptyResponseCodes: Set<Int> = DataResponseSerializer.defaultEmptyResponseCodes,
+                emptyRequestMethods: Set<HTTPMethod> = DataResponseSerializer.defaultEmptyRequestMethods) {
+        self.emptyResponseCodes = emptyResponseCodes
+        self.emptyRequestMethods = emptyRequestMethods
+    }
 
     public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Data {
         guard error == nil else { throw error! }
 
-        if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return Data() }
+        guard let data = data, !data.isEmpty else {
+            guard emptyResponseAllowed(forRequest: request, response: response) else {
+                throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
+            }
 
-        guard let validData = data else {
-            throw AFError.responseSerializationFailed(reason: .inputDataNil)
+            return Data()
         }
 
-        return validData
+        return data
     }
 }
 
@@ -347,23 +329,38 @@ extension DownloadRequest {
 /// data is considered an error. However, if the response is has a status code valid for empty responses (`204`, `205`),
 /// then an empty `String` is returned.
 public final class StringResponseSerializer: ResponseSerializer {
-    let encoding: String.Encoding?
-
-    /// Creates an instance with the given `String.Encoding`.
+    /// Optional string encoding used to validate the response.
+    public let encoding: String.Encoding?
+    /// HTTP response codes for which empty responses are allowed.
+    public let emptyResponseCodes: Set<Int>
+    /// HTTP request methods for which empty responses are allowed.
+    public let emptyRequestMethods: Set<HTTPMethod>
+
+    /// Creates an instance with the provided values.
     ///
-    /// - Parameter encoding: A string encoding. Defaults to `nil`, in which case the encoding will be determined from
-    ///                       the server response, falling back to the default HTTP character set, `ISO-8859-1`.
-    public init(encoding: String.Encoding? = nil) {
+    /// - Parameters:
+    ///   - encoding:            A string encoding. Defaults to `nil`, in which case the encoding will be determined
+    ///                          from the server response, falling back to the default HTTP character set, `ISO-8859-1`.
+    ///   - emptyResponseCodes:  The HTTP response codes for which empty responses are allowed. Defaults to
+    ///                          `[204, 205]`.
+    ///   - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. Defaults to `[.head]`.
+    public init(encoding: String.Encoding? = nil,
+                emptyResponseCodes: Set<Int> = StringResponseSerializer.defaultEmptyResponseCodes,
+                emptyRequestMethods: Set<HTTPMethod> = StringResponseSerializer.defaultEmptyRequestMethods) {
         self.encoding = encoding
+        self.emptyResponseCodes = emptyResponseCodes
+        self.emptyRequestMethods = emptyRequestMethods
     }
 
     public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> String {
         guard error == nil else { throw error! }
 
-        if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return "" }
+        guard let data = data, !data.isEmpty else {
+            guard emptyResponseAllowed(forRequest: request, response: response) else {
+                throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
+            }
 
-        guard let validData = data else {
-            throw AFError.responseSerializationFailed(reason: .inputDataNil)
+            return ""
         }
 
         var convertedEncoding = encoding
@@ -376,7 +373,7 @@ public final class StringResponseSerializer: ResponseSerializer {
 
         let actualEncoding = convertedEncoding ?? .isoLatin1
 
-        guard let string = String(data: validData, encoding: actualEncoding) else {
+        guard let string = String(data: data, encoding: actualEncoding) else {
             throw AFError.responseSerializationFailed(reason: .stringSerializationFailed(encoding: actualEncoding))
         }
 
@@ -435,28 +432,41 @@ extension DownloadRequest {
 /// `nil` or no data is considered an error. However, if the response is has a status code valid for empty responses
 /// (`204`, `205`), then an `NSNull`  value is returned.
 public final class JSONResponseSerializer: ResponseSerializer {
-    let options: JSONSerialization.ReadingOptions
-
-    /// Creates an instance with the given `JSONSerilization.ReadingOptions`.
+    /// `JSONSerialization.ReadingOptions` used when serializing a response.
+    public let options: JSONSerialization.ReadingOptions
+    /// HTTP response codes for which empty responses are allowed.
+    public let emptyResponseCodes: Set<Int>
+    /// HTTP request methods for which empty responses are allowed.
+    public let emptyRequestMethods: Set<HTTPMethod>
+
+    /// Creates an instance with the provided values.
     ///
-    /// - Parameter options: The options to use. Defaults to `.allowFragments`.
-    public init(options: JSONSerialization.ReadingOptions = .allowFragments) {
+    /// - Parameters:
+    ///   - options:             The options to use. Defaults to `.allowFragments`.
+    ///   - emptyResponseCodes:  The HTTP response codes for which empty responses are allowed. Defaults to
+    ///                          `[204, 205]`.
+    ///   - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. Defaults to `[.head]`.
+    public init(options: JSONSerialization.ReadingOptions = .allowFragments,
+                emptyResponseCodes: Set<Int> = JSONResponseSerializer.defaultEmptyResponseCodes,
+                emptyRequestMethods: Set<HTTPMethod> = JSONResponseSerializer.defaultEmptyRequestMethods) {
         self.options = options
+        self.emptyResponseCodes = emptyResponseCodes
+        self.emptyRequestMethods = emptyRequestMethods
     }
 
     public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Any {
         guard error == nil else { throw error! }
 
-        guard let validData = data, validData.count > 0 else {
-            if let response = response, emptyDataStatusCodes.contains(response.statusCode) {
-                return NSNull()
+        guard let data = data, !data.isEmpty else {
+            guard emptyResponseAllowed(forRequest: request, response: response) else {
+                throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
             }
 
-            throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
+            return NSNull()
         }
 
         do {
-            return try JSONSerialization.jsonObject(with: validData, options: options)
+            return try JSONSerialization.jsonObject(with: data, options: options)
         } catch {
             throw AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error))
         }
@@ -506,45 +516,58 @@ extension DownloadRequest {
 
 // MARK: - Empty
 
-/// A type representing an empty response. Use `Empty.response` to get the instance.
+/// A type representing an empty response. Use `Empty.value` to get the instance.
 public struct Empty: Decodable {
-    public static let response = Empty()
+    public static let value = Empty()
 }
 
 // MARK: - JSON Decodable
 
 /// A `ResponseSerializer` that decodes the response data as a generic value using a `JSONDecoder`. By default, a
 /// request returning `nil` or no data is considered an error. However, if the response is has a status code valid for
-/// empty responses (`204`, `205`), then the `Empty.response` value is returned.
+/// empty responses (`204`, `205`), then the `Empty.value` value is returned.
 public final class JSONDecodableResponseSerializer<T: Decodable>: ResponseSerializer {
-    let decoder: JSONDecoder
-
-    /// Creates an instance with the given `JSONDecoder` instance.
+    /// The `JSONDecoder` instance used to decode responses.
+    public let decoder: JSONDecoder
+    /// HTTP response codes for which empty responses are allowed.
+    public let emptyResponseCodes: Set<Int>
+    /// HTTP request methods for which empty responses are allowed.
+    public let emptyRequestMethods: Set<HTTPMethod>
+
+    /// Creates an instance using the values provided.
     ///
-    /// - Parameter decoder: A decoder. Defaults to a `JSONDecoder` with default settings.
-    public init(decoder: JSONDecoder = JSONDecoder()) {
+    /// - Parameters:
+    ///   - decoder:           The `JSONDecoder`. Defaults to a `JSONDecoder()`.
+    ///   - emptyResponseCodes:  The HTTP response codes for which empty responses are allowed. Defaults to
+    ///                          `[204, 205]`.
+    ///   - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. Defaults to `[.head]`.
+    public init(decoder: JSONDecoder = JSONDecoder(),
+                emptyResponseCodes: Set<Int> = JSONDecodableResponseSerializer.defaultEmptyResponseCodes,
+                emptyRequestMethods: Set<HTTPMethod> = JSONDecodableResponseSerializer.defaultEmptyRequestMethods) {
         self.decoder = decoder
+        self.emptyResponseCodes = emptyResponseCodes
+        self.emptyRequestMethods = emptyRequestMethods
     }
 
     public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> T {
         guard error == nil else { throw error! }
 
-        guard let validData = data, validData.count > 0 else {
-            if let response = response, emptyDataStatusCodes.contains(response.statusCode) {
-                guard let emptyResponse = Empty.response as? T else {
-                    throw AFError.responseSerializationFailed(reason: .invalidEmptyResponse(type: "\(T.self)"))
-                }
+        guard let data = data, !data.isEmpty else {
+            guard emptyResponseAllowed(forRequest: request, response: response) else {
+                throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
+            }
 
-                return emptyResponse
+            guard let emptyValue = Empty.value as? T else {
+                throw AFError.responseSerializationFailed(reason: .invalidEmptyResponse(type: "\(T.self)"))
             }
 
-            throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
+            return emptyValue
         }
 
         do {
-            return try decoder.decode(T.self, from: validData)
+            return try decoder.decode(T.self, from: data)
         } catch {
-            throw error
+            throw AFError.responseSerializationFailed(reason: .jsonDecodingFailed(error: error))
         }
     }
 }
@@ -568,6 +591,3 @@ extension DataRequest {
                         completionHandler: completionHandler)
     }
 }
-
-/// A set of HTTP response status code that do not contain response data.
-private let emptyDataStatusCodes: Set<Int> = [204, 205]

+ 10 - 10
Tests/AFError+AlamofireTests.swift

@@ -107,11 +107,6 @@ extension AFError {
 
     // ResponseSerializationFailureReason
 
-    var isInputDataNil: Bool {
-        if case let .responseSerializationFailed(reason) = self, reason.isInputDataNil { return true }
-        return false
-    }
-
     var isInputDataNilOrZeroLength: Bool {
         if case let .responseSerializationFailed(reason) = self, reason.isInputDataNilOrZeroLength { return true }
         return false
@@ -137,6 +132,11 @@ extension AFError {
         return false
     }
 
+    var isJSONDecodingFailed: Bool {
+        if case let .responseSerializationFailed(reason) = self, reason.isJSONDecodingFailed { return true }
+        return false
+    }
+
     // ResponseValidationFailureReason
 
     var isDataFileNil: Bool {
@@ -251,11 +251,6 @@ extension AFError.MultipartEncodingFailureReason {
 // MARK: -
 
 extension AFError.ResponseSerializationFailureReason {
-    var isInputDataNil: Bool {
-        if case .inputDataNil = self { return true }
-        return false
-    }
-
     var isInputDataNilOrZeroLength: Bool {
         if case .inputDataNilOrZeroLength = self { return true }
         return false
@@ -280,6 +275,11 @@ extension AFError.ResponseSerializationFailureReason {
         if case .jsonSerializationFailed = self { return true }
         return false
     }
+
+    var isJSONDecodingFailed: Bool {
+        if case .jsonDecodingFailed = self { return true }
+        return false
+    }
 }
 
 // MARK: -

+ 39 - 21
Tests/ResponseSerializationTests.swift

@@ -30,7 +30,7 @@ class DataResponseSerializationTestCase: BaseTestCase {
 
     // MARK: Properties
 
-    private let error = AFError.responseSerializationFailed(reason: .inputDataNil)
+    private let error = AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
 
     // MARK: DataResponseSerializer
 
@@ -61,7 +61,7 @@ class DataResponseSerializationTestCase: BaseTestCase {
         XCTAssertNotNil(result.error)
 
         if let error = result.error?.asAFError {
-            XCTAssertTrue(error.isInputDataNil)
+            XCTAssertTrue(error.isInputDataNilOrZeroLength)
         } else {
             XCTFail("error should not be nil")
         }
@@ -80,7 +80,7 @@ class DataResponseSerializationTestCase: BaseTestCase {
         XCTAssertNotNil(result.error)
 
         if let error = result.error?.asAFError {
-            XCTAssertTrue(error.isInputDataNil)
+            XCTAssertTrue(error.isInputDataNilOrZeroLength)
         } else {
             XCTFail("error should not be nil")
         }
@@ -100,7 +100,7 @@ class DataResponseSerializationTestCase: BaseTestCase {
         XCTAssertNotNil(result.error, "result error should not be nil")
 
         if let error = result.error?.asAFError {
-            XCTAssertTrue(error.isInputDataNil)
+            XCTAssertTrue(error.isInputDataNilOrZeroLength)
         } else {
             XCTFail("error should not be nil")
         }
@@ -139,13 +139,13 @@ class DataResponseSerializationTestCase: BaseTestCase {
         XCTAssertNotNil(result.error)
 
         if let error = result.error?.asAFError {
-            XCTAssertTrue(error.isInputDataNil)
+            XCTAssertTrue(error.isInputDataNilOrZeroLength)
         } else {
             XCTFail("error should not be nil")
         }
     }
 
-    func testThatStringResponseSerializerSucceedsWhenDataIsEmpty() {
+    func testThatStringResponseSerializerFailsWhenDataIsEmpty() {
         // Given
         let serializer = StringResponseSerializer()
 
@@ -153,9 +153,15 @@ class DataResponseSerializationTestCase: BaseTestCase {
         let result = Result { try serializer.serialize(request: nil, response: nil, data: Data(), error: nil) }
 
         // Then
-        XCTAssertTrue(result.isSuccess)
-        XCTAssertNotNil(result.value)
-        XCTAssertNil(result.error)
+        XCTAssertTrue(result.isFailure)
+        XCTAssertNil(result.value)
+        XCTAssertNotNil(result.error)
+        
+        if let error = result.error?.asAFError {
+            XCTAssertTrue(error.isInputDataNilOrZeroLength)
+        } else {
+            XCTFail("error should not be nil")
+        }
     }
 
     func testThatStringResponseSerializerSucceedsWithUTF8DataAndNoProvidedEncoding() {
@@ -254,7 +260,7 @@ class DataResponseSerializationTestCase: BaseTestCase {
         XCTAssertNotNil(result.error)
 
         if let error = result.error?.asAFError {
-            XCTAssertTrue(error.isInputDataNil)
+            XCTAssertTrue(error.isInputDataNilOrZeroLength)
         } else {
             XCTFail("error should not be nil")
         }
@@ -274,7 +280,7 @@ class DataResponseSerializationTestCase: BaseTestCase {
         XCTAssertNotNil(result.error)
 
         if let error = result.error?.asAFError {
-            XCTAssertTrue(error.isInputDataNil)
+            XCTAssertTrue(error.isInputDataNilOrZeroLength)
         } else {
             XCTFail("error should not be nil")
         }
@@ -386,7 +392,7 @@ class DataResponseSerializationTestCase: BaseTestCase {
         XCTAssertNotNil(result.error)
 
         if let error = result.error?.asAFError {
-            XCTAssertTrue(error.isInputDataNil)
+            XCTAssertTrue(error.isInputDataNilOrZeroLength)
         } else {
             XCTFail("error should not be nil")
         }
@@ -518,7 +524,7 @@ class DataResponseSerializationTestCase: BaseTestCase {
         XCTAssertNotNil(result.error)
 
         if let error = result.error?.asAFError {
-            XCTAssertTrue(error.isInputDataNil)
+            XCTAssertTrue(error.isInputDataNilOrZeroLength)
         } else {
             XCTFail("error should not be nil")
         }
@@ -596,7 +602,7 @@ class DownloadResponseSerializationTestCase: BaseTestCase {
         XCTAssertNil(result.error)
     }
 
-    func testThatDataResponseSerializerSucceedsWhenFileDataIsNil() {
+    func testThatDataResponseSerializerFailsWhenFileDataIsEmpty() {
         // Given
         let serializer = DataResponseSerializer()
 
@@ -604,9 +610,15 @@ class DownloadResponseSerializationTestCase: BaseTestCase {
         let result = Result { try serializer.serializeDownload(request: nil, response: nil, fileURL: jsonEmptyDataFileURL, error: nil) }
 
         // Then
-        XCTAssertTrue(result.isSuccess)
-        XCTAssertNotNil(result.value)
-        XCTAssertNil(result.error)
+        XCTAssertTrue(result.isFailure)
+        XCTAssertNil(result.value)
+        XCTAssertNotNil(result.error)
+
+        if let error = result.error?.asAFError {
+            XCTAssertTrue(error.isInputDataNilOrZeroLength)
+        } else {
+            XCTFail("error should not be nil")
+        }
     }
 
     func testThatDataResponseSerializerFailsWhenFileURLIsNil() {
@@ -747,7 +759,7 @@ class DownloadResponseSerializationTestCase: BaseTestCase {
         }
     }
 
-    func testThatStringResponseSerializerSucceedsWhenFileDataIsEmpty() {
+    func testThatStringResponseSerializerFailsWhenFileDataIsEmpty() {
         // Given
         let serializer = StringResponseSerializer()
 
@@ -755,9 +767,15 @@ class DownloadResponseSerializationTestCase: BaseTestCase {
         let result = Result { try serializer.serializeDownload(request: nil, response: nil, fileURL: stringEmptyDataFileURL, error: nil) }
 
         // Then
-        XCTAssertTrue(result.isSuccess)
-        XCTAssertNotNil(result.value)
-        XCTAssertNil(result.error)
+        XCTAssertTrue(result.isFailure)
+        XCTAssertNil(result.value)
+        XCTAssertNotNil(result.error)
+        
+        if let error = result.error?.asAFError {
+            XCTAssertTrue(error.isInputDataNilOrZeroLength)
+        } else {
+            XCTFail("error should not be nil")
+        }
     }
 
     func testThatStringResponseSerializerSucceedsWithUTF8DataAndNoProvidedEncoding() {