Browse Source

Merge pull request #627 from Alamofire/feature/results_and_validation_error_handling

Christian Noon 10 years ago
parent
commit
fedbc04501

+ 12 - 0
Alamofire.xcodeproj/project.pbxproj

@@ -7,6 +7,10 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		4C0E5BF81B673D3400816CCC /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0E5BF71B673D3400816CCC /* Result.swift */; };
+		4C0E5BF91B673D3400816CCC /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0E5BF71B673D3400816CCC /* Result.swift */; };
+		4C1DC8541B68908E00476DE3 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1DC8531B68908E00476DE3 /* Error.swift */; };
+		4C1DC8551B68908E00476DE3 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1DC8531B68908E00476DE3 /* Error.swift */; };
 		4C23EB431B327C5B0090E0BC /* MultipartFormData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C23EB421B327C5B0090E0BC /* MultipartFormData.swift */; };
 		4C23EB441B327C5B0090E0BC /* MultipartFormData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C23EB421B327C5B0090E0BC /* MultipartFormData.swift */; };
 		4C256A531B096C770065714F /* BaseTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C256A501B096C2C0065714F /* BaseTestCase.swift */; };
@@ -112,6 +116,8 @@
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXFileReference section */
+		4C0E5BF71B673D3400816CCC /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = "<group>"; };
+		4C1DC8531B68908E00476DE3 /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = "<group>"; };
 		4C23EB421B327C5B0090E0BC /* MultipartFormData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipartFormData.swift; sourceTree = "<group>"; };
 		4C256A501B096C2C0065714F /* BaseTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTestCase.swift; sourceTree = "<group>"; };
 		4C3238E61B3604DB00FE04AE /* MultipartFormDataTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipartFormDataTests.swift; sourceTree = "<group>"; };
@@ -323,9 +329,11 @@
 		4CDE2C481AF8A14A00BABAE5 /* Core */ = {
 			isa = PBXGroup;
 			children = (
+				4C1DC8531B68908E00476DE3 /* Error.swift */,
 				4CDE2C361AF8932A00BABAE5 /* Manager.swift */,
 				4CE2724E1AF88FB500F1D59A /* ParameterEncoding.swift */,
 				4CDE2C391AF899EC00BABAE5 /* Request.swift */,
+				4C0E5BF71B673D3400816CCC /* Result.swift */,
 			);
 			name = Core;
 			sourceTree = "<group>";
@@ -615,12 +623,14 @@
 				4CE272501AF88FB500F1D59A /* ParameterEncoding.swift in Sources */,
 				4CDE2C3B1AF899EC00BABAE5 /* Request.swift in Sources */,
 				4CDE2C471AF89FF300BABAE5 /* ResponseSerialization.swift in Sources */,
+				4C1DC8551B68908E00476DE3 /* Error.swift in Sources */,
 				4CDE2C381AF8932A00BABAE5 /* Manager.swift in Sources */,
 				4DD67C251A5C590000ED2280 /* Alamofire.swift in Sources */,
 				4C23EB441B327C5B0090E0BC /* MultipartFormData.swift in Sources */,
 				4C811F8E1B51856D00E0F59A /* ServerTrustPolicy.swift in Sources */,
 				4CDE2C3E1AF89D4900BABAE5 /* Download.swift in Sources */,
 				4CDE2C441AF89F0900BABAE5 /* Validation.swift in Sources */,
+				4C0E5BF91B673D3400816CCC /* Result.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -632,12 +642,14 @@
 				4CE2724F1AF88FB500F1D59A /* ParameterEncoding.swift in Sources */,
 				4CDE2C3A1AF899EC00BABAE5 /* Request.swift in Sources */,
 				4CDE2C461AF89FF300BABAE5 /* ResponseSerialization.swift in Sources */,
+				4C1DC8541B68908E00476DE3 /* Error.swift in Sources */,
 				4CDE2C371AF8932A00BABAE5 /* Manager.swift in Sources */,
 				F897FF4119AA800700AB5182 /* Alamofire.swift in Sources */,
 				4C23EB431B327C5B0090E0BC /* MultipartFormData.swift in Sources */,
 				4C811F8D1B51856D00E0F59A /* ServerTrustPolicy.swift in Sources */,
 				4CDE2C3D1AF89D4900BABAE5 /* Download.swift in Sources */,
 				4CDE2C431AF89F0900BABAE5 /* Validation.swift in Sources */,
+				4C0E5BF81B673D3400816CCC /* Result.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 0 - 5
Source/Alamofire.swift

@@ -22,11 +22,6 @@
 
 import Foundation
 
-/// Alamofire errors
-public let AlamofireErrorDomain = "com.alamofire.error"
-public let AlamofireInputStreamReadFailed = -6000
-public let AlamofireOutputStreamWriteFailed = -6001
-
 // MARK: - URLStringConvertible
 
 /**

+ 50 - 0
Source/Error.swift

@@ -0,0 +1,50 @@
+// Error.swift
+//
+// Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import Foundation
+
+/// The `Error` struct provides a convenience for creating custom Alamofire NSErrors.
+public struct Error {
+    /// The domain used for creating all Alamofire errors.
+    public static let Domain = "com.alamofire.error"
+
+    /// The custom error codes generated by Alamofire.
+    public enum Code: Int {
+        case InputStreamReadFailed           = -6000
+        case OutputStreamWriteFailed         = -6001
+        case ContentTypeValidationFailed     = -6002
+        case StatusCodeValidationFailed      = -6003
+        case DataSerializationFailed         = -6004
+        case StringSerializationFailed       = -6005
+        case JSONSerializationFailed         = -6006
+        case PropertyListSerializationFailed = -6007
+    }
+
+    static func errorWithCode(code: Code, failureReason: String) -> NSError {
+        return errorWithCode(code.rawValue, failureReason: failureReason)
+    }
+
+    static func errorWithCode(code: Int, failureReason: String) -> NSError {
+        let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason]
+        return NSError(domain: Domain, code: code, userInfo: userInfo)
+    }
+}

+ 12 - 20
Source/MultipartFormData.swift

@@ -217,10 +217,7 @@ public class MultipartFormData {
             appendBodyPart(fileURL: fileURL, name: name, fileName: fileName, mimeType: mimeType)
         } else {
             let failureReason = "Failed to extract the fileName of the provided URL: \(fileURL)"
-            let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason]
-            let error = NSError(domain: AlamofireErrorDomain, code: NSURLErrorBadURL, userInfo: userInfo)
-
-            setBodyPartError(error)
+            setBodyPartError(Error.errorWithCode(NSURLErrorBadURL, failureReason: failureReason))
         }
     }
 
@@ -248,7 +245,7 @@ public class MultipartFormData {
 
         guard fileURL.fileURL else {
             let failureReason = "The file URL does not point to a file URL: \(fileURL)"
-            let error = errorWithCode(NSURLErrorBadURL, failureReason: failureReason)
+            let error = Error.errorWithCode(NSURLErrorBadURL, failureReason: failureReason)
             setBodyPartError(error)
             return
         }
@@ -267,7 +264,7 @@ public class MultipartFormData {
         }
 
         guard isReachable else {
-            let error = errorWithCode(NSURLErrorBadURL, failureReason: "The file URL is not reachable: \(fileURL)")
+            let error = Error.errorWithCode(NSURLErrorBadURL, failureReason: "The file URL is not reachable: \(fileURL)")
             setBodyPartError(error)
             return
         }
@@ -283,7 +280,7 @@ public class MultipartFormData {
             where NSFileManager.defaultManager().fileExistsAtPath(path, isDirectory: &isDirectory) && !isDirectory else
         {
             let failureReason = "The file URL is a directory, not a file: \(fileURL)"
-            let error = errorWithCode(NSURLErrorBadURL, failureReason: failureReason)
+            let error = Error.errorWithCode(NSURLErrorBadURL, failureReason: failureReason)
             setBodyPartError(error)
             return
         }
@@ -307,7 +304,7 @@ public class MultipartFormData {
 
         guard let length = bodyContentLength else {
             let failureReason = "Could not fetch attributes from the file URL: \(fileURL)"
-            let error = errorWithCode(NSURLErrorBadURL, failureReason: failureReason)
+            let error = Error.errorWithCode(NSURLErrorBadURL, failureReason: failureReason)
             setBodyPartError(error)
             return
         }
@@ -318,7 +315,7 @@ public class MultipartFormData {
 
         guard let stream = NSInputStream(URL: fileURL) else {
             let failureReason = "Failed to create an input stream from the file URL: \(fileURL)"
-            let error = errorWithCode(NSURLErrorCannotOpenFile, failureReason: failureReason)
+            let error = Error.errorWithCode(NSURLErrorCannotOpenFile, failureReason: failureReason)
             setBodyPartError(error)
             return
         }
@@ -419,10 +416,10 @@ public class MultipartFormData {
 
         if let path = fileURL.path where NSFileManager.defaultManager().fileExistsAtPath(path) {
             let failureReason = "A file already exists at the given file URL: \(fileURL)"
-            throw errorWithCode(NSURLErrorBadURL, failureReason: failureReason)
+            throw Error.errorWithCode(NSURLErrorBadURL, failureReason: failureReason)
         } else if !fileURL.fileURL {
             let failureReason = "The URL does not point to a valid file: \(fileURL)"
-            throw errorWithCode(NSURLErrorBadURL, failureReason: failureReason)
+            throw Error.errorWithCode(NSURLErrorBadURL, failureReason: failureReason)
         }
 
         let outputStream: NSOutputStream
@@ -431,7 +428,7 @@ public class MultipartFormData {
             outputStream = possibleOutputStream
         } else {
             let failureReason = "Failed to create an output stream with the given URL: \(fileURL)"
-            throw errorWithCode(NSURLErrorCannotOpenFile, failureReason: failureReason)
+            throw Error.errorWithCode(NSURLErrorCannotOpenFile, failureReason: failureReason)
         }
 
         outputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
@@ -501,7 +498,7 @@ public class MultipartFormData {
                 encoded.appendBytes(buffer, length: bytesRead)
             } else if bytesRead < 0 {
                 let failureReason = "Failed to read from input stream: \(inputStream)"
-                error = errorWithCode(AlamofireInputStreamReadFailed, failureReason: failureReason)
+                error = Error.errorWithCode(.InputStreamReadFailed, failureReason: failureReason)
                 break
             } else {
                 break
@@ -562,7 +559,7 @@ public class MultipartFormData {
                 try writeBuffer(&buffer, toOutputStream: outputStream)
             } else if bytesRead < 0 {
                 let failureReason = "Failed to read from input stream: \(inputStream)"
-                throw errorWithCode(AlamofireInputStreamReadFailed, failureReason: failureReason)
+                throw Error.errorWithCode(.InputStreamReadFailed, failureReason: failureReason)
             } else {
                 break
             }
@@ -604,7 +601,7 @@ public class MultipartFormData {
 
                 if bytesWritten < 0 {
                     let failureReason = "Failed to write to output stream: \(outputStream)"
-                    throw errorWithCode(AlamofireOutputStreamWriteFailed, failureReason: failureReason)
+                    throw Error.errorWithCode(.OutputStreamWriteFailed, failureReason: failureReason)
                 }
 
                 bytesToWrite -= bytesWritten
@@ -672,9 +669,4 @@ public class MultipartFormData {
             bodyPartError = error
         }
     }
-
-    private func errorWithCode(code: Int, failureReason: String) -> NSError {
-        let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason]
-        return NSError(domain: AlamofireErrorDomain, code: code, userInfo: userInfo)
-    }
 }

+ 80 - 52
Source/ResponseSerialization.swift

@@ -32,10 +32,9 @@ public protocol ResponseSerializer {
     typealias SerializedObject
 
     /**
-        A closure used by response handlers that takes a request, response, and data and returns a serialized object and
-        any error that occured in the process.
+        A closure used by response handlers that takes a request, response, and data and returns a result.
     */
-    var serializeResponse: (NSURLRequest?, NSHTTPURLResponse?, NSData?) -> (SerializedObject?, NSError?) { get }
+    var serializeResponse: (NSURLRequest?, NSHTTPURLResponse?, NSData?) -> Result<SerializedObject> { get }
 }
 
 /**
@@ -46,10 +45,9 @@ public struct GenericResponseSerializer<T>: ResponseSerializer {
     public typealias SerializedObject = T
 
     /**
-        A closure used by response handlers that takes a request, response, and data and returns a serialized object and
-        any error that occured in the process.
+        A closure used by response handlers that takes a request, response, and data and returns a result.
     */
-    public var serializeResponse: (NSURLRequest?, NSHTTPURLResponse?, NSData?) -> (SerializedObject?, NSError?)
+    public var serializeResponse: (NSURLRequest?, NSHTTPURLResponse?, NSData?) -> Result<SerializedObject>
 
     /**
         Initializes the `GenericResponseSerializer` instance with the given serialize response closure.
@@ -58,7 +56,7 @@ public struct GenericResponseSerializer<T>: ResponseSerializer {
 
         - returns: The new generic response serializer instance.
     */
-    public init(serializeResponse: (NSURLRequest?, NSHTTPURLResponse?, NSData?) -> (SerializedObject?, NSError?)) {
+    public init(serializeResponse: (NSURLRequest?, NSHTTPURLResponse?, NSData?) -> Result<SerializedObject>) {
         self.serializeResponse = serializeResponse
     }
 }
@@ -67,6 +65,28 @@ public struct GenericResponseSerializer<T>: ResponseSerializer {
 
 extension Request {
 
+    /**
+        Adds a handler to be called once the request has finished.
+
+        - parameter queue:             The queue on which the completion handler is dispatched.
+        - parameter completionHandler: The code to be executed once the request has finished.
+
+        - returns: The request.
+    */
+    public func response(
+        queue: dispatch_queue_t? = nil,
+        completionHandler: (NSURLRequest?, NSHTTPURLResponse?, NSData?, NSError?) -> Void)
+        -> Self
+    {
+        delegate.queue.addOperationWithBlock {
+            dispatch_async(queue ?? dispatch_get_main_queue()) {
+                completionHandler(self.request, self.response, self.delegate.data, self.delegate.error)
+            }
+        }
+
+        return self
+    }
+
     /**
         Adds a handler to be called once the request has finished.
 
@@ -80,17 +100,18 @@ extension Request {
     public func response<T: ResponseSerializer, V where T.SerializedObject == V>(
         queue: dispatch_queue_t? = nil,
         responseSerializer: T,
-        completionHandler: (NSURLRequest?, NSHTTPURLResponse?, V?, NSError?) -> Void)
+        completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<V>) -> Void)
         -> Self
     {
         delegate.queue.addOperationWithBlock {
-            let result: V?
-            let error: NSError?
+            var result = responseSerializer.serializeResponse(self.request, self.response, self.delegate.data)
 
-            (result, error) = responseSerializer.serializeResponse(self.request, self.response, self.delegate.data)
+            if let error = self.delegate.error {
+                result = .Failure(self.delegate.data, error)
+            }
 
             dispatch_async(queue ?? dispatch_get_main_queue()) {
-                completionHandler(self.request, self.response, result, self.delegate.error ?? error)
+                completionHandler(self.request, self.response, result)
             }
         }
 
@@ -109,7 +130,13 @@ extension Request {
     */
     public static func dataResponseSerializer() -> GenericResponseSerializer<NSData> {
         return GenericResponseSerializer { request, response, data in
-            return (data, nil)
+            guard let validData = data else {
+                let failureReason = "Data could not be serialized. Input data was nil."
+                let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
+                return .Failure(data, error)
+            }
+
+            return .Success(validData)
         }
     }
 
@@ -120,7 +147,7 @@ extension Request {
 
         - returns: The request.
     */
-    public func response(completionHandler: (NSURLRequest?, NSHTTPURLResponse?, NSData?, NSError?) -> Void) -> Self {
+    public func responseData(completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<NSData>) -> Void) -> Self {
         return response(responseSerializer: Request.dataResponseSerializer(), completionHandler: completionHandler)
     }
 }
@@ -143,8 +170,10 @@ extension Request {
         -> GenericResponseSerializer<String>
     {
         return GenericResponseSerializer { _, response, data in
-            guard let data = data where data.length > 0 else {
-                return (nil, nil)
+            guard let validData = data where validData.length > 0 else {
+                let failureReason = "String could not be serialized. Input data was nil or contained zero bytes."
+                let error = Error.errorWithCode(.StringSerializationFailed, failureReason: failureReason)
+                return .Failure(data, error)
             }
 
             if let encodingName = response?.textEncodingName where encoding == nil {
@@ -153,9 +182,15 @@ extension Request {
                 )
             }
 
-            let string = NSString(data: data, encoding: encoding ?? NSISOLatin1StringEncoding) as? String
+            let actualEncoding = encoding ?? NSISOLatin1StringEncoding
 
-            return (string, nil)
+            if let string = NSString(data: validData, encoding: actualEncoding) as? String {
+                return .Success(string)
+            } else {
+                let failureReason = "String could not be serialized with encoding: \(actualEncoding)"
+                let error = Error.errorWithCode(.StringSerializationFailed, failureReason: failureReason)
+                return .Failure(data, error)
+            }
         }
     }
 
@@ -165,16 +200,15 @@ extension Request {
         - parameter encoding:          The string encoding. If `nil`, the string encoding will be determined from the 
                                        server response, falling back to the default HTTP default character set, 
                                        ISO-8859-1.
-        - parameter completionHandler: A closure to be executed once the request has finished. The closure takes 4 
-                                       arguments: the URL request, the URL response, if one was received, the string, 
-                                       if one could be created from the URL response and data, and any error produced 
-                                       while creating the string.
+        - parameter completionHandler: A closure to be executed once the request has finished. The closure takes 3
+                                       arguments: the URL request, the URL response and the result produced while
+                                       creating the string.
 
         - returns: The request.
     */
     public func responseString(
         encoding encoding: NSStringEncoding? = nil,
-        completionHandler: (NSURLRequest?, NSHTTPURLResponse?, String?, NSError?) -> Void)
+        completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<String>) -> Void)
         -> Self
     {
         return response(
@@ -201,20 +235,18 @@ extension Request {
         -> GenericResponseSerializer<AnyObject>
     {
         return GenericResponseSerializer { request, response, data in
-            guard let data = data where data.length > 0 else {
-                return (nil, nil)
+            guard let validData = data where validData.length > 0 else {
+                let failureReason = "JSON could not be serialized. Input data was nil or contained zero bytes."
+                let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
+                return .Failure(data, error)
             }
 
-            var JSON: AnyObject?
-            var serializationError: NSError?
-
             do {
-                JSON = try NSJSONSerialization.JSONObjectWithData(data, options: options)
+                let JSON = try NSJSONSerialization.JSONObjectWithData(validData, options: options)
+                return .Success(JSON)
             } catch {
-                serializationError = error as NSError
+                return .Failure(data, error as NSError)
             }
-
-            return (JSON, serializationError)
         }
     }
 
@@ -222,16 +254,15 @@ extension Request {
         Adds a handler to be called once the request has finished.
 
         - parameter options:           The JSON serialization reading options. `.AllowFragments` by default.
-        - parameter completionHandler: A closure to be executed once the request has finished. The closure takes 4 
-                                       arguments: the URL request, the URL response, if one was received, the JSON 
-                                       object, if one could be created from the URL response and data, and any error 
-                                       produced while creating the JSON object.
+        - parameter completionHandler: A closure to be executed once the request has finished. The closure takes 3
+                                       arguments: the URL request, the URL response and the result produced while
+                                       creating the JSON object.
 
         - returns: The request.
     */
     public func responseJSON(
         options options: NSJSONReadingOptions = .AllowFragments,
-        completionHandler: (NSURLRequest?, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void)
+        completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<AnyObject>) -> Void)
         -> Self
     {
         return response(
@@ -249,7 +280,7 @@ extension Request {
         Creates a response serializer that returns an object constructed from the response data using 
         `NSPropertyListSerialization` with the specified reading options.
 
-        - parameter options: The property list reading options. `0` by default.
+        - parameter options: The property list reading options. `NSPropertyListReadOptions()` by default.
 
         - returns: A property list object response serializer.
     */
@@ -258,20 +289,18 @@ extension Request {
         -> GenericResponseSerializer<AnyObject>
     {
         return GenericResponseSerializer { request, response, data in
-            guard let data = data where data.length > 0 else {
-                return (nil, nil)
+            guard let validData = data where validData.length > 0 else {
+                let failureReason = "Property list could not be serialized. Input data was nil or contained zero bytes."
+                let error = Error.errorWithCode(.PropertyListSerializationFailed, failureReason: failureReason)
+                return .Failure(data, error)
             }
 
-            var plist: AnyObject?
-            var propertyListSerializationError: NSError?
-
             do {
-                plist = try NSPropertyListSerialization.propertyListWithData(data, options: options, format: nil)
+                let plist = try NSPropertyListSerialization.propertyListWithData(validData, options: options, format: nil)
+                return .Success(plist)
             } catch {
-                propertyListSerializationError = error as NSError
+                return .Failure(data, error as NSError)
             }
-
-            return (plist, propertyListSerializationError)
         }
     }
 
@@ -279,16 +308,15 @@ extension Request {
         Adds a handler to be called once the request has finished.
 
         - parameter options:           The property list reading options. `0` by default.
-        - parameter completionHandler: A closure to be executed once the request has finished. The closure takes 4 
-                                       arguments: the URL request, the URL response, if one was received, the property 
-                                       list, if one could be created from the URL response and data, and any error 
-                                       produced while creating the property list.
+        - parameter completionHandler: A closure to be executed once the request has finished. The closure takes 3
+                                       arguments: the URL request, the URL response and the result produced while
+                                       creating the property list.
 
         - returns: The request.
     */
     public func responsePropertyList(
         options options: NSPropertyListReadOptions = NSPropertyListReadOptions(),
-        completionHandler: (NSURLRequest?, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void)
+        completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<AnyObject>) -> Void)
         -> Self
     {
         return response(

+ 81 - 0
Source/Result.swift

@@ -0,0 +1,81 @@
+// Result.swift
+//
+// Copyright (c) 2014–2015 Alamofire Software Foundation (http://alamofire.org/)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import Foundation
+
+/**
+    Used to represent whether a request was successful or encountered an error.
+
+    - Success: The request and all post processing operations were successful resulting in the serialization of the 
+               provided associated value.
+    - Failure: The request encountered an error resulting in a failure. The associated values are the original data 
+               provided by the server as well as the error that caused the failure.
+*/
+public enum Result<Value> {
+    case Success(Value)
+    case Failure(NSData?, NSError)
+
+    /// Returns `true` if the result is a success, `false` otherwise.
+    public var isSuccess: Bool {
+        switch self {
+        case .Success:
+            return true
+        case .Failure:
+            return false
+        }
+    }
+
+    /// Returns `true` if the result is a failure, `false` otherwise.
+    public var isFailure: Bool {
+        return !isSuccess
+    }
+
+    /// Returns the associated value if the result is a success, `nil` otherwise.
+    public var value: Value? {
+        switch self {
+        case .Success(let value):
+            return value
+        case .Failure:
+            return nil
+        }
+    }
+
+    /// Returns the associated data value if the result is a failure, `nil` otherwise.
+    public var data: NSData? {
+        switch self {
+        case .Success:
+            return nil
+        case .Failure(let data, _):
+            return data
+        }
+    }
+
+    /// Returns the associated error value if the result is a failure, `nil` otherwise.
+    public var error: NSError? {
+        switch self {
+        case .Success:
+            return nil
+        case .Failure(_, let error):
+            return error
+        }
+    }
+}

+ 37 - 7
Source/Validation.swift

@@ -24,11 +24,22 @@ import Foundation
 
 extension Request {
 
+    /**
+        Used to represent whether validation was successful or encountered an error resulting in a failure.
+
+        - Success: The validation was successful.
+        - Failure: The validation failed encountering the provided error.
+    */
+    public enum ValidationResult {
+        case Success
+        case Failure(NSError)
+    }
+
     /**
         A closure used to validate a request that takes a URL request and URL response, and returns whether the 
         request was valid.
     */
-    public typealias Validation = (NSURLRequest?, NSHTTPURLResponse) -> Bool
+    public typealias Validation = (NSURLRequest?, NSHTTPURLResponse) -> ValidationResult
 
     /**
         Validates the request, using the specified closure.
@@ -41,8 +52,11 @@ extension Request {
     */
     public func validate(validation: Validation) -> Self {
         delegate.queue.addOperationWithBlock {
-            if let response = self.response where self.delegate.error == nil && !validation(self.request, response) {
-                self.delegate.error = NSError(domain: AlamofireErrorDomain, code: -1, userInfo: nil)
+            if let
+                response = self.response where self.delegate.error == nil,
+                case let .Failure(error) = validation(self.request, response)
+            {
+                self.delegate.error = error
             }
         }
 
@@ -62,7 +76,12 @@ extension Request {
     */
     public func validate<S: SequenceType where S.Generator.Element == Int>(statusCode acceptableStatusCode: S) -> Self {
         return validate { _, response in
-            return acceptableStatusCode.contains(response.statusCode)
+            if acceptableStatusCode.contains(response.statusCode) {
+                return .Success
+            } else {
+                let failureReason = "Response status code was unacceptable: \(response.statusCode)"
+                return .Failure(Error.errorWithCode(.StatusCodeValidationFailed, failureReason: failureReason))
+            }
         }
     }
 
@@ -117,18 +136,29 @@ extension Request {
             {
                 for contentType in acceptableContentTypes {
                     if let acceptableMIMEType = MIMEType(contentType) where acceptableMIMEType.matches(responseMIMEType) {
-                        return true
+                        return .Success
                     }
                 }
             } else {
                 for contentType in acceptableContentTypes {
                     if let MIMEType = MIMEType(contentType) where MIMEType.type == "*" && MIMEType.subtype == "*" {
-                        return true
+                        return .Success
                     }
                 }
             }
 
-            return false
+            let failureReason: String
+
+            if let responseContentType = response.MIMEType {
+                failureReason = (
+                    "Response content type \"\(responseContentType)\" does not match any acceptable " +
+                    "content types: \(acceptableContentTypes)"
+                )
+            } else {
+                failureReason = "Response content type was missing and acceptable content type does not match \"*/*\""
+            }
+
+            return .Failure(Error.errorWithCode(.ContentTypeValidationFailed, failureReason: failureReason))
         }
     }
 

+ 1 - 2
Tests/CacheTests.swift

@@ -182,8 +182,7 @@ class CacheTestCase: BaseTestCase {
         let request = manager.request(urlRequest)
         request.response(
             queue,
-            responseSerializer: Request.dataResponseSerializer(),
-            completionHandler: { (_, response, data: NSData?, _) in
+            completionHandler: { _, response, data, _ in
                 completion(request.request, response)
             }
         )

+ 7 - 7
Tests/DownloadTests.swift

@@ -337,19 +337,17 @@ class DownloadResumeDataTestCase: BaseTestCase {
 
         var request: NSURLRequest?
         var response: NSHTTPURLResponse?
-        var JSON: AnyObject?
-        var error: NSError?
+        var result: Result<AnyObject>!
 
         // When
         let download = Alamofire.download(.GET, URLString, destination: destination)
         download.progress { _, _, _ in
             download.cancel()
         }
-        download.responseJSON { responseRequest, responseResponse, responseJSON, responseError in
+        download.responseJSON { responseRequest, responseResponse, responseResult in
             request = responseRequest
             response = responseResponse
-            JSON = responseJSON
-            error = responseError
+            result = responseResult
 
             expectation.fulfill()
         }
@@ -359,8 +357,10 @@ class DownloadResumeDataTestCase: BaseTestCase {
         // Then
         XCTAssertNotNil(request, "request should not be nil")
         XCTAssertNotNil(response, "response should not be nil")
-        XCTAssertNil(JSON, "JSON should be nil")
-        XCTAssertNotNil(error, "error should not be nil")
+
+        XCTAssertTrue(result.isFailure, "result should be a failure")
+        XCTAssertNotNil(result.data, "data should not be nil")
+        XCTAssertNotNil(result.error, "error should not be nil")
 
         XCTAssertNotNil(download.resumeData, "resume data should not be nil")
     }

+ 4 - 5
Tests/RequestTests.swift

@@ -81,21 +81,20 @@ class RequestResponseTestCase: BaseTestCase {
     func testRequestResponse() {
         // Given
         let URLString = "https://httpbin.org/get"
-        let serializer = Request.stringResponseSerializer(encoding: NSUTF8StringEncoding)
 
         let expectation = expectationWithDescription("GET request should succeed: \(URLString)")
 
         var request: NSURLRequest?
         var response: NSHTTPURLResponse?
-        var string: String?
+        var data: NSData?
         var error: NSError?
 
         // When
         Alamofire.request(.GET, URLString, parameters: ["foo": "bar"])
-            .response(responseSerializer: serializer) { responseRequest, responseResponse, responseString, responseError in
+            .response { responseRequest, responseResponse, responseData, responseError in
                 request = responseRequest
                 response = responseResponse
-                string = responseString
+                data = responseData
                 error = responseError
 
                 expectation.fulfill()
@@ -106,7 +105,7 @@ class RequestResponseTestCase: BaseTestCase {
         // Then
         XCTAssertNotNil(request, "request should not be nil")
         XCTAssertNotNil(response, "response should not be nil")
-        XCTAssertNotNil(string, "string should not be nil")
+        XCTAssertNotNil(data, "data should not be nil")
         XCTAssertNil(error, "error should be nil")
     }
 

+ 203 - 21
Tests/ResponseTests.swift

@@ -24,24 +24,22 @@ import Alamofire
 import Foundation
 import XCTest
 
-class JSONResponseTestCase: BaseTestCase {
-    func testGETRequestJSONResponse() {
+class ResponseDataTestCase: BaseTestCase {
+    func testThatResponseDataReturnsSuccessResultWithValidData() {
         // Given
         let URLString = "https://httpbin.org/get"
-        let expectation = expectationWithDescription("\(URLString)")
+        let expectation = expectationWithDescription("request should succeed")
 
         var request: NSURLRequest?
         var response: NSHTTPURLResponse?
-        var JSON: AnyObject?
-        var error: NSError?
+        var result: Result<NSData>!
 
         // When
         Alamofire.request(.GET, URLString, parameters: ["foo": "bar"])
-            .responseJSON { responseRequest, responseResponse, responseJSON, responseError in
+            .responseData { responseRequest, responseResponse, responseResult in
                 request = responseRequest
                 response = responseResponse
-                JSON = responseJSON
-                error = responseError
+                result = responseResult
 
                 expectation.fulfill()
             }
@@ -51,35 +49,220 @@ class JSONResponseTestCase: BaseTestCase {
         // Then
         XCTAssertNotNil(request, "request should not be nil")
         XCTAssertNotNil(response, "response should not be nil")
-        XCTAssertNotNil(JSON, "JSON should not be nil")
-        XCTAssertNil(error, "error should be nil")
+        XCTAssertTrue(result.isSuccess, "result should be success")
+        XCTAssertNotNil(result.value, "result value should not be nil")
+        XCTAssertNil(result.data, "result data should be nil")
+        XCTAssertNil(result.error, "result error should be nil")
+    }
+
+    func testThatResponseDataReturnsFailureResultWithOptionalDataAndError() {
+        // Given
+        let URLString = "https://invalid-url-here.org/this/does/not/exist"
+        let expectation = expectationWithDescription("request should fail with 404")
+
+        var request: NSURLRequest?
+        var response: NSHTTPURLResponse?
+        var result: Result<NSData>!
+
+        // When
+        Alamofire.request(.GET, URLString, parameters: ["foo": "bar"])
+            .responseData { responseRequest, responseResponse, responseResult in
+                request = responseRequest
+                response = responseResponse
+                result = responseResult
+
+                expectation.fulfill()
+            }
+
+        waitForExpectationsWithTimeout(defaultTimeout, handler: nil)
+
+        // Then
+        XCTAssertNotNil(request, "request should not be nil")
+        XCTAssertNil(response, "response should be nil")
+        XCTAssertTrue(result.isFailure, "result should be a failure")
+        XCTAssertNil(result.value, "result value should not be nil")
+        XCTAssertNotNil(result.data, "result data should be nil")
+        XCTAssertNotNil(result.error, "result error should be nil")
+    }
+}
+
+// MARK: -
+
+class ResponseStringTestCase: BaseTestCase {
+    func testThatResponseStringReturnsSuccessResultWithValidString() {
+        // Given
+        let URLString = "https://httpbin.org/get"
+        let expectation = expectationWithDescription("request should succeed")
+
+        var request: NSURLRequest?
+        var response: NSHTTPURLResponse?
+        var result: Result<String>!
+
+        // When
+        Alamofire.request(.GET, URLString, parameters: ["foo": "bar"])
+            .responseString { responseRequest, responseResponse, responseResult in
+                request = responseRequest
+                response = responseResponse
+                result = responseResult
+
+                expectation.fulfill()
+            }
+
+        waitForExpectationsWithTimeout(defaultTimeout, handler: nil)
+
+        // Then
+        XCTAssertNotNil(request, "request should not be nil")
+        XCTAssertNotNil(response, "response should not be nil")
+        XCTAssertTrue(result.isSuccess, "result should be success")
+        XCTAssertNotNil(result.value, "result value should not be nil")
+        XCTAssertNil(result.data, "result data should be nil")
+        XCTAssertNil(result.error, "result error should be nil")
+    }
+
+    func testThatResponseStringReturnsFailureResultWithOptionalDataAndError() {
+        // Given
+        let URLString = "https://invalid-url-here.org/this/does/not/exist"
+        let expectation = expectationWithDescription("request should fail with 404")
+
+        var request: NSURLRequest?
+        var response: NSHTTPURLResponse?
+        var result: Result<String>!
+
+        // When
+        Alamofire.request(.GET, URLString, parameters: ["foo": "bar"])
+            .responseString { responseRequest, responseResponse, responseResult in
+                request = responseRequest
+                response = responseResponse
+                result = responseResult
+
+                expectation.fulfill()
+            }
+
+        waitForExpectationsWithTimeout(defaultTimeout, handler: nil)
+
+        // Then
+        XCTAssertNotNil(request, "request should not be nil")
+        XCTAssertNil(response, "response should be nil")
+        XCTAssertTrue(result.isFailure, "result should be a failure")
+        XCTAssertNil(result.value, "result value should not be nil")
+        XCTAssertNotNil(result.data, "result data should be nil")
+        XCTAssertNotNil(result.error, "result error should be nil")
+    }
+}
+
+// MARK: -
+
+class ResponseJSONTestCase: BaseTestCase {
+    func testThatResponseJSONReturnsSuccessResultWithValidJSON() {
+        // Given
+        let URLString = "https://httpbin.org/get"
+        let expectation = expectationWithDescription("request should succeed")
+
+        var request: NSURLRequest?
+        var response: NSHTTPURLResponse?
+        var result: Result<AnyObject>!
+
+        // When
+        Alamofire.request(.GET, URLString, parameters: ["foo": "bar"])
+            .responseJSON { responseRequest, responseResponse, responseResult in
+                request = responseRequest
+                response = responseResponse
+                result = responseResult
+
+                expectation.fulfill()
+            }
+
+        waitForExpectationsWithTimeout(defaultTimeout, handler: nil)
+
+        // Then
+        XCTAssertNotNil(request, "request should not be nil")
+        XCTAssertNotNil(response, "response should not be nil")
+        XCTAssertTrue(result.isSuccess, "result should be success")
+        XCTAssertNotNil(result.value, "result value should not be nil")
+        XCTAssertNil(result.data, "result data should be nil")
+        XCTAssertNil(result.error, "result error should be nil")
+    }
+
+    func testThatResponseStringReturnsFailureResultWithOptionalDataAndError() {
+        // Given
+        let URLString = "https://invalid-url-here.org/this/does/not/exist"
+        let expectation = expectationWithDescription("request should fail with 404")
+
+        var request: NSURLRequest?
+        var response: NSHTTPURLResponse?
+        var result: Result<AnyObject>!
+
+        // When
+        Alamofire.request(.GET, URLString, parameters: ["foo": "bar"])
+            .responseJSON { responseRequest, responseResponse, responseResult in
+                request = responseRequest
+                response = responseResponse
+                result = responseResult
+
+                expectation.fulfill()
+            }
+
+        waitForExpectationsWithTimeout(defaultTimeout, handler: nil)
+
+        // Then
+        XCTAssertNotNil(request, "request should not be nil")
+        XCTAssertNil(response, "response should be nil")
+        XCTAssertTrue(result.isFailure, "result should be a failure")
+        XCTAssertNil(result.value, "result value should not be nil")
+        XCTAssertNotNil(result.data, "result data should be nil")
+        XCTAssertNotNil(result.error, "result error should be nil")
+    }
+
+    func testThatResponseJSONReturnsSuccessResultForGETRequest() {
+        // Given
+        let URLString = "https://httpbin.org/get"
+        let expectation = expectationWithDescription("request should succeed")
+
+        var request: NSURLRequest?
+        var response: NSHTTPURLResponse?
+        var result: Result<AnyObject>!
+
+        // When
+        Alamofire.request(.GET, URLString, parameters: ["foo": "bar"])
+            .responseJSON { responseRequest, responseResponse, responseResult in
+                request = responseRequest
+                response = responseResponse
+                result = responseResult
+
+                expectation.fulfill()
+            }
+
+        waitForExpectationsWithTimeout(defaultTimeout, handler: nil)
+
+        // Then
+        XCTAssertNotNil(request, "request should not be nil")
+        XCTAssertNotNil(response, "response should not be nil")
+        XCTAssertTrue(result.isSuccess, "result should be success")
 
         // The `as NSString` cast is necessary due to a compiler bug. See the following rdar for more info.
         // - https://openradar.appspot.com/radar?id=5517037090635776
-        if let args = JSON?["args" as NSString] as? [String: String] {
+        if let args = result.value?["args" as NSString] as? [String: String] {
             XCTAssertEqual(args, ["foo": "bar"], "args should match parameters")
         } else {
             XCTFail("args should not be nil")
         }
     }
 
-    func testPOSTRequestJSONResponse() {
+    func testThatResponseJSONReturnsSuccessResultForPOSTRequest() {
         // Given
         let URLString = "https://httpbin.org/post"
-        let expectation = expectationWithDescription("\(URLString)")
+        let expectation = expectationWithDescription("request should succeed")
 
         var request: NSURLRequest?
         var response: NSHTTPURLResponse?
-        var JSON: AnyObject?
-        var error: NSError?
+        var result: Result<AnyObject>!
 
         // When
         Alamofire.request(.POST, URLString, parameters: ["foo": "bar"])
-            .responseJSON { responseRequest, responseResponse, responseJSON, responseError in
+            .responseJSON { responseRequest, responseResponse, responseResult in
                 request = responseRequest
                 response = responseResponse
-                JSON = responseJSON
-                error = responseError
+                result = responseResult
 
                 expectation.fulfill()
             }
@@ -89,12 +272,11 @@ class JSONResponseTestCase: BaseTestCase {
         // Then
         XCTAssertNotNil(request, "request should not be nil")
         XCTAssertNotNil(response, "response should not be nil")
-        XCTAssertNotNil(JSON, "JSON should not be nil")
-        XCTAssertNil(error, "error should be nil")
+        XCTAssertTrue(result.isSuccess, "result should be success")
 
         // The `as NSString` cast is necessary due to a compiler bug. See the following rdar for more info.
         // - https://openradar.appspot.com/radar?id=5517037090635776
-        if let form = JSON?["form" as NSString] as? [String: String] {
+        if let form = result.value?["form" as NSString] as? [String: String] {
             XCTAssertEqual(form, ["foo": "bar"], "form should match parameters")
         } else {
             XCTFail("form should not be nil")

+ 1 - 1
Tests/TLSEvaluationTests.swift

@@ -263,7 +263,7 @@ class TLSEvaluationExpiredLeafCertificateTestCase: BaseTestCase {
             }
 
         waitForExpectationsWithTimeout(defaultTimeout, handler: nil)
-        
+
         // Then
         XCTAssertNil(error, "error should be nil")
     }

+ 63 - 8
Tests/ValidationTests.swift

@@ -65,7 +65,11 @@ class StatusCodeValidationTestCase: BaseTestCase {
 
         // Then
         XCTAssertNotNil(error, "error should not be nil")
-        XCTAssertEqual(error?.domain ?? "", AlamofireErrorDomain, "error should be in Alamofire error domain")
+
+        if let error = error {
+            XCTAssertEqual(error.domain, Error.Domain, "domain should be Alamofire error domain")
+            XCTAssertEqual(error.code, Error.Code.StatusCodeValidationFailed.rawValue, "code should be status code validation failure")
+        }
     }
 
     func testThatValidationForRequestWithNoAcceptableStatusCodesFails() {
@@ -87,7 +91,11 @@ class StatusCodeValidationTestCase: BaseTestCase {
 
         // Then
         XCTAssertNotNil(error, "error should not be nil")
-        XCTAssertEqual(error?.domain ?? "", AlamofireErrorDomain, "error should be in Alamofire error domain")
+
+        if let error = error {
+            XCTAssertEqual(error.domain, Error.Domain, "domain should be Alamofire error domain")
+            XCTAssertEqual(error.code, Error.Code.StatusCodeValidationFailed.rawValue, "code should be status code validation failure")
+        }
     }
 }
 
@@ -157,7 +165,11 @@ class ContentTypeValidationTestCase: BaseTestCase {
 
         // Then
         XCTAssertNotNil(error, "error should not be nil")
-        XCTAssertEqual(error?.domain ?? "", AlamofireErrorDomain, "error should be in Alamofire error domain")
+
+        if let error = error {
+            XCTAssertEqual(error.domain, Error.Domain, "domain should be Alamofire error domain")
+            XCTAssertEqual(error.code, Error.Code.ContentTypeValidationFailed.rawValue, "code should be content type validation failure")
+        }
     }
 
     func testThatValidationForRequestWithNoAcceptableContentTypeResponseFails() {
@@ -179,7 +191,11 @@ class ContentTypeValidationTestCase: BaseTestCase {
 
         // Then
         XCTAssertNotNil(error, "error should not be nil")
-        XCTAssertEqual(error?.domain ?? "", AlamofireErrorDomain, "error should be in Alamofire error domain")
+
+        if let error = error {
+            XCTAssertEqual(error.domain, Error.Domain, "domain should be Alamofire error domain")
+            XCTAssertEqual(error.code, Error.Code.ContentTypeValidationFailed.rawValue, "code should be content type validation failure")
+        }
     }
 }
 
@@ -208,7 +224,7 @@ class MultipleValidationTestCase: BaseTestCase {
         XCTAssertNil(error, "error should be nil")
     }
 
-    func testThatValidationForRequestWithUnacceptableStatusCodeAndContentTypeResponseFails() {
+    func testThatValidationForRequestWithUnacceptableStatusCodeAndContentTypeResponseFailsWithStatusCodeError() {
         // Given
         let URLString = "https://httpbin.org/xml"
         let expectation = expectationWithDescription("request should succeed and return xml")
@@ -228,7 +244,38 @@ class MultipleValidationTestCase: BaseTestCase {
 
         // Then
         XCTAssertNotNil(error, "error should not be nil")
-        XCTAssertEqual(error?.domain ?? "", AlamofireErrorDomain, "error should be in Alamofire error domain")
+
+        if let error = error {
+            XCTAssertEqual(error.domain, Error.Domain, "domain should be Alamofire error domain")
+            XCTAssertEqual(error.code, Error.Code.StatusCodeValidationFailed.rawValue, "code should be status code validation failure")
+        }
+    }
+
+    func testThatValidationForRequestWithUnacceptableStatusCodeAndContentTypeResponseFailsWithContentTypeError() {
+        // Given
+        let URLString = "https://httpbin.org/xml"
+        let expectation = expectationWithDescription("request should succeed and return xml")
+
+        var error: NSError?
+
+        // When
+        Alamofire.request(.GET, URLString)
+            .validate(contentType: ["application/octet-stream"])
+            .validate(statusCode: 400..<600)
+            .response { _, _, _, responseError in
+                error = responseError
+                expectation.fulfill()
+        }
+
+        waitForExpectationsWithTimeout(defaultTimeout, handler: nil)
+
+        // Then
+        XCTAssertNotNil(error, "error should not be nil")
+
+        if let error = error {
+            XCTAssertEqual(error.domain, Error.Domain, "domain should be Alamofire error domain")
+            XCTAssertEqual(error.code, Error.Code.ContentTypeValidationFailed.rawValue, "code should be content type validation failure")
+        }
     }
 }
 
@@ -278,7 +325,11 @@ class AutomaticValidationTestCase: BaseTestCase {
 
         // Then
         XCTAssertNotNil(error, "error should not be nil")
-        XCTAssertEqual(error!.domain, AlamofireErrorDomain, "error should be in Alamofire error domain")
+
+        if let error = error {
+            XCTAssertEqual(error.domain, Error.Domain, "domain should be Alamofire error domain")
+            XCTAssertEqual(error.code, Error.Code.StatusCodeValidationFailed.rawValue, "code should be status code validation failure")
+        }
     }
 
     func testThatValidationForRequestWithAcceptableWildcardContentTypeResponseSucceeds() {
@@ -353,6 +404,10 @@ class AutomaticValidationTestCase: BaseTestCase {
 
         // Then
         XCTAssertNotNil(error, "error should not be nil")
-        XCTAssertEqual(error!.domain, AlamofireErrorDomain, "error should be in Alamofire error domain")
+
+        if let error = error {
+            XCTAssertEqual(error.domain, Error.Domain, "domain should be Alamofire error domain")
+            XCTAssertEqual(error.code, Error.Code.ContentTypeValidationFailed.rawValue, "code should be content type validation failure")
+        }
     }
 }