Browse Source

Add DataPreprocessor API (#2903)

* Add DataPreprocessor API.

* Make DataPreprocessor a protocol.

* Cleanup whitespace.

* Rename included preprocessors, make public, add tests.

* Add test.

* Update documentation.
Jon Shier 6 years ago
parent
commit
01c4092854
3 changed files with 254 additions and 80 deletions
  1. 1 1
      Source/AFError.swift
  2. 66 22
      Source/ResponseSerialization.swift
  3. 187 57
      Tests/ResponseSerializationTests.swift

+ 1 - 1
Source/AFError.swift

@@ -162,7 +162,7 @@ public enum AFError: Error {
     case sessionDeinitialized
     /// `Session` was explicitly invalidated, possibly with the `Error` produced by the underlying `URLSession`.
     case sessionInvalidated(error: Error?)
-    /// `Request` was explcitly cancelled.
+    /// `Request` was explicitly cancelled.
     case explicitlyCancelled
     /// `URLConvertible` type failed to create a valid `URL`.
     case invalidURL(url: URLConvertible)

+ 66 - 22
Source/ResponseSerialization.swift

@@ -64,18 +64,46 @@ public protocol DownloadResponseSerializerProtocol {
 
 /// A serializer that can handle both data and download responses.
 public protocol ResponseSerializer: DataResponseSerializerProtocol & DownloadResponseSerializerProtocol {
+    /// `DataPreprocessor` used to prepare incoming `Data` for serialization.
+    var dataPreprocessor: DataPreprocessor { get }
     /// `HTTPMethod`s for which empty response bodies are considered appropriate.
     var emptyRequestMethods: Set<HTTPMethod> { get }
     /// HTTP response codes for which empty response bodies are considered appropriate.
     var emptyResponseCodes: Set<Int> { get }
 }
 
+/// Type used to preprocess `Data` before it handled by a serializer.
+public protocol DataPreprocessor {
+    /// Process           `Data` before it's handled by a serializer.
+    /// - Parameter data: The raw `Data` to process.
+    func preprocess(_ data: Data) throws -> Data
+}
+
+/// `DataPreprocessor` that returns passed `Data` without any transform.
+public struct PassthroughPreprocessor: DataPreprocessor {
+    public init() { }
+
+    public func preprocess(_ data: Data) throws -> Data { return data }
+}
+
+/// `DataPreprocessor` that trims Google's typical `)]}',\n` XSSI JSON header.
+public struct GoogleXSSIPreprocessor: DataPreprocessor {
+    public init() { }
+
+    public func preprocess(_ data: Data) throws -> Data {
+        return (data.prefix(6) == Data(")]}',\n".utf8)) ? data.dropFirst(6) : data
+    }
+}
+
 extension ResponseSerializer {
+    /// Default `DataPreprocessor`. `PassthroughPreprocessor` by default.
+    public static var defaultDataPreprocessor: DataPreprocessor { return PassthroughPreprocessor() }
     /// Default `HTTPMethod`s for which empty response bodies are considered appropriate. `[.head]` by default.
     public static var defaultEmptyRequestMethods: Set<HTTPMethod> { return [.head] }
     /// HTTP response codes for which empty response bodies are considered appropriate. `[204, 205]` by default.
     public static var defaultEmptyResponseCodes: Set<Int> { return [204, 205] }
 
+    public var dataPreprocessor: DataPreprocessor { return Self.defaultDataPreprocessor }
     public var emptyRequestMethods: Set<HTTPMethod> { return Self.defaultEmptyRequestMethods }
     public var emptyResponseCodes: Set<Int> { return Self.defaultEmptyResponseCodes }
 
@@ -267,7 +295,7 @@ extension DownloadRequest {
         -> Self
     {
         appendResponseSerializer {
-            // Start work that should be on the serilization queue.
+            // Start work that should be on the serialization queue.
             let result = Result<URL?, Error>(value: self.fileURL , error: self.error)
             // End work that should be on the serialization queue.
 
@@ -398,18 +426,20 @@ 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 dataPreprocessor: DataPreprocessor
     public let emptyResponseCodes: Set<Int>
-    /// HTTP request methods for which empty responses are allowed.
     public let emptyRequestMethods: Set<HTTPMethod>
 
     /// Creates an instance using the provided values.
     ///
     /// - Parameters:
+    ///   - dataPreprocessor:    `DataPreprocessor` used to prepare the received `Data` for serialization.
     ///   - emptyResponseCodes:  The HTTP response codes for which empty responses are allowed. `[204, 205]` by default.
     ///   - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default.
-    public init(emptyResponseCodes: Set<Int> = DataResponseSerializer.defaultEmptyResponseCodes,
+    public init(dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor,
+                emptyResponseCodes: Set<Int> = DataResponseSerializer.defaultEmptyResponseCodes,
                 emptyRequestMethods: Set<HTTPMethod> = DataResponseSerializer.defaultEmptyRequestMethods) {
+        self.dataPreprocessor = dataPreprocessor
         self.emptyResponseCodes = emptyResponseCodes
         self.emptyRequestMethods = emptyRequestMethods
     }
@@ -417,7 +447,7 @@ public final class DataResponseSerializer: ResponseSerializer {
     public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Data {
         guard error == nil else { throw error! }
 
-        guard let data = data, !data.isEmpty else {
+        guard var data = data, !data.isEmpty else {
             guard emptyResponseAllowed(forRequest: request, response: response) else {
                 throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
             }
@@ -425,6 +455,8 @@ public final class DataResponseSerializer: ResponseSerializer {
             return Data()
         }
 
+        data = try dataPreprocessor.preprocess(data)
+
         return data
     }
 }
@@ -457,23 +489,25 @@ 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 {
+    public let dataPreprocessor: DataPreprocessor
     /// 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.
     ///
     /// - Parameters:
+    ///   - dataPreprocessor:    `DataPreprocessor` used to prepare the received `Data` for serialization.
     ///   - 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. `[204, 205]` by default.
     ///   - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default.
-    public init(encoding: String.Encoding? = nil,
+    public init(dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor,
+                encoding: String.Encoding? = nil,
                 emptyResponseCodes: Set<Int> = StringResponseSerializer.defaultEmptyResponseCodes,
                 emptyRequestMethods: Set<HTTPMethod> = StringResponseSerializer.defaultEmptyRequestMethods) {
+        self.dataPreprocessor = dataPreprocessor
         self.encoding = encoding
         self.emptyResponseCodes = emptyResponseCodes
         self.emptyRequestMethods = emptyRequestMethods
@@ -482,7 +516,7 @@ public final class StringResponseSerializer: ResponseSerializer {
     public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> String {
         guard error == nil else { throw error! }
 
-        guard let data = data, !data.isEmpty else {
+        guard var data = data, !data.isEmpty else {
             guard emptyResponseAllowed(forRequest: request, response: response) else {
                 throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
             }
@@ -490,6 +524,8 @@ public final class StringResponseSerializer: ResponseSerializer {
             return ""
         }
 
+        data = try dataPreprocessor.preprocess(data)
+
         var convertedEncoding = encoding
 
         if let encodingName = response?.textEncodingName as CFString?, convertedEncoding == nil {
@@ -559,31 +595,33 @@ 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 {
-    /// `JSONSerialization.ReadingOptions` used when serializing a response.
-    public let options: JSONSerialization.ReadingOptions
-    /// HTTP response codes for which empty responses are allowed.
+    public let dataPreprocessor: DataPreprocessor
     public let emptyResponseCodes: Set<Int>
-    /// HTTP request methods for which empty responses are allowed.
     public let emptyRequestMethods: Set<HTTPMethod>
+    /// `JSONSerialization.ReadingOptions` used when serializing a response.
+    public let options: JSONSerialization.ReadingOptions
 
     /// Creates an instance with the provided values.
     ///
     /// - Parameters:
-    ///   - options:             The options to use. `.allowFragments` by default.
+    ///   - dataPreprocessor:    `DataPreprocessor` used to prepare the received `Data` for serialization.
     ///   - emptyResponseCodes:  The HTTP response codes for which empty responses are allowed. `[204, 205]` by default.
     ///   - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default.
-    public init(options: JSONSerialization.ReadingOptions = .allowFragments,
+    ///   - options:             The options to use. `.allowFragments` by default.
+    public init(dataPreprocessor: DataPreprocessor = JSONResponseSerializer.defaultDataPreprocessor,
                 emptyResponseCodes: Set<Int> = JSONResponseSerializer.defaultEmptyResponseCodes,
-                emptyRequestMethods: Set<HTTPMethod> = JSONResponseSerializer.defaultEmptyRequestMethods) {
-        self.options = options
+                emptyRequestMethods: Set<HTTPMethod> = JSONResponseSerializer.defaultEmptyRequestMethods,
+                options: JSONSerialization.ReadingOptions = .allowFragments) {
+        self.dataPreprocessor = dataPreprocessor
         self.emptyResponseCodes = emptyResponseCodes
         self.emptyRequestMethods = emptyRequestMethods
+        self.options = options
     }
 
     public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Any {
         guard error == nil else { throw error! }
 
-        guard let data = data, !data.isEmpty else {
+        guard var data = data, !data.isEmpty else {
             guard emptyResponseAllowed(forRequest: request, response: response) else {
                 throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
             }
@@ -591,6 +629,8 @@ public final class JSONResponseSerializer: ResponseSerializer {
             return NSNull()
         }
 
+        data = try dataPreprocessor.preprocess(data)
+
         do {
             return try JSONSerialization.jsonObject(with: data, options: options)
         } catch {
@@ -687,22 +727,24 @@ extension JSONDecoder: DataDecoder { }
 /// is considered an error. However, if the response is has a status code valid for empty responses (`204`, `205`), then
 /// the `Empty.value` value is returned.
 public final class DecodableResponseSerializer<T: Decodable>: ResponseSerializer {
+    public let dataPreprocessor: DataPreprocessor
     /// The `DataDecoder` instance used to decode responses.
     public let decoder: DataDecoder
-    /// 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.
     ///
     /// - Parameters:
+    ///   - dataPreprocessor:    `DataPreprocessor` used to prepare the received `Data` for serialization.
     ///   - decoder:             The `DataDecoder`. `JSONDecoder()` by default.
     ///   - emptyResponseCodes:  The HTTP response codes for which empty responses are allowed. `[204, 205]` by default.
     ///   - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default.
-    public init(decoder: DataDecoder = JSONDecoder(),
+    public init(dataPreprocessor: DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor,
+                decoder: DataDecoder = JSONDecoder(),
                 emptyResponseCodes: Set<Int> = DecodableResponseSerializer.defaultEmptyResponseCodes,
                 emptyRequestMethods: Set<HTTPMethod> = DecodableResponseSerializer.defaultEmptyRequestMethods) {
+        self.dataPreprocessor = dataPreprocessor
         self.decoder = decoder
         self.emptyResponseCodes = emptyResponseCodes
         self.emptyRequestMethods = emptyRequestMethods
@@ -711,7 +753,7 @@ public final class DecodableResponseSerializer<T: Decodable>: ResponseSerializer
     public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> T {
         guard error == nil else { throw error! }
 
-        guard let data = data, !data.isEmpty else {
+        guard var data = data, !data.isEmpty else {
             guard emptyResponseAllowed(forRequest: request, response: response) else {
                 throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
             }
@@ -723,6 +765,8 @@ public final class DecodableResponseSerializer<T: Decodable>: ResponseSerializer
             return emptyValue
         }
 
+        data = try dataPreprocessor.preprocess(data)
+
         do {
             return try decoder.decode(T.self, from: data)
         } catch {

+ 187 - 57
Tests/ResponseSerializationTests.swift

@@ -119,10 +119,7 @@ final class DataResponseSerializationTestCase: BaseTestCase {
         XCTAssertTrue(result.isSuccess)
         XCTAssertNotNil(result.value)
         XCTAssertNil(result.error)
-
-        if let data = result.value {
-            XCTAssertEqual(data.count, 0)
-        }
+        XCTAssertEqual(result.value?.count, 0)
     }
 
     func testThatDataResponseSerializerSucceedsWhenDataIsNilWithGETRequestAnd205ResponseStatusCode() {
@@ -138,10 +135,7 @@ final class DataResponseSerializationTestCase: BaseTestCase {
         XCTAssertTrue(result.isSuccess)
         XCTAssertNotNil(result.value)
         XCTAssertNil(result.error)
-
-        if let data = result.value {
-            XCTAssertEqual(data.count, 0)
-        }
+        XCTAssertEqual(result.value?.count, 0)
     }
 
     func testThatDataResponseSerializerSucceedsWhenDataIsNilWithHEADRequestAnd200ResponseStatusCode() {
@@ -157,10 +151,7 @@ final class DataResponseSerializationTestCase: BaseTestCase {
         XCTAssertTrue(result.isSuccess)
         XCTAssertNotNil(result.value)
         XCTAssertNil(result.error)
-
-        if let data = result.value {
-            XCTAssertEqual(data.count, 0)
-        }
+        XCTAssertEqual(result.value?.count, 0)
     }
 
     // MARK: StringResponseSerializer
@@ -338,10 +329,7 @@ final class DataResponseSerializationTestCase: BaseTestCase {
         XCTAssertTrue(result.isSuccess)
         XCTAssertNotNil(result.value)
         XCTAssertNil(result.error)
-
-        if let string = result.value {
-            XCTAssertEqual(string, "")
-        }
+        XCTAssertEqual(result.value, "")
     }
 
     func testThatStringResponseSerializerSucceedsWhenDataIsNilWithGETRequestAnd205ResponseStatusCode() {
@@ -357,10 +345,7 @@ final class DataResponseSerializationTestCase: BaseTestCase {
         XCTAssertTrue(result.isSuccess)
         XCTAssertNotNil(result.value)
         XCTAssertNil(result.error)
-
-        if let string = result.value {
-            XCTAssertEqual(string, "")
-        }
+        XCTAssertEqual(result.value, "")
     }
 
     func testThatStringResponseSerializerSucceedsWhenDataIsNilWithHEADRequestAnd200ResponseStatusCode() {
@@ -376,10 +361,7 @@ final class DataResponseSerializationTestCase: BaseTestCase {
         XCTAssertTrue(result.isSuccess)
         XCTAssertNotNil(result.value)
         XCTAssertNil(result.error)
-
-        if let string = result.value {
-            XCTAssertEqual(string, "")
-        }
+        XCTAssertEqual(result.value, "")
     }
 
     // MARK: JSONResponseSerializer
@@ -509,12 +491,7 @@ final class DataResponseSerializationTestCase: BaseTestCase {
         XCTAssertTrue(result.isSuccess)
         XCTAssertNotNil(result.value)
         XCTAssertNil(result.error)
-
-        if let json = result.value as? NSNull {
-            XCTAssertEqual(json, NSNull())
-        } else {
-            XCTFail("json should not be nil")
-        }
+        XCTAssertEqual(result.value as? NSNull, NSNull())
     }
 
     func testThatJSONResponseSerializerSucceedsWhenDataIsNilWithGETRequestAnd205ResponseStatusCode() {
@@ -530,12 +507,7 @@ final class DataResponseSerializationTestCase: BaseTestCase {
         XCTAssertTrue(result.isSuccess)
         XCTAssertNotNil(result.value)
         XCTAssertNil(result.error)
-
-        if let json = result.value as? NSNull {
-            XCTAssertEqual(json, NSNull())
-        } else {
-            XCTFail("json should not be nil")
-        }
+        XCTAssertEqual(result.value as? NSNull, NSNull())
     }
 
     func testThatJSONResponseSerializerSucceedsWhenDataIsNilWithHEADRequestAnd200ResponseStatusCode() {
@@ -551,12 +523,7 @@ final class DataResponseSerializationTestCase: BaseTestCase {
         XCTAssertTrue(result.isSuccess)
         XCTAssertNotNil(result.value)
         XCTAssertNil(result.error)
-
-        if let json = result.value as? NSNull {
-            XCTAssertEqual(json, NSNull())
-        } else {
-            XCTFail("json should not be nil")
-        }
+        XCTAssertEqual(result.value as? NSNull, NSNull())
     }
 }
 
@@ -935,12 +902,7 @@ final class DownloadResponseSerializationTestCase: BaseTestCase {
         XCTAssertTrue(result.isSuccess)
         XCTAssertNotNil(result.value)
         XCTAssertNil(result.error)
-
-        if let data = result.value {
-            XCTAssertEqual(data.count, 0)
-        } else {
-            XCTFail("data should not be nil")
-        }
+        XCTAssertEqual(result.value?.count, 0)
     }
 
     // MARK: Tests - String Response Serializer
@@ -964,7 +926,6 @@ final class DownloadResponseSerializationTestCase: BaseTestCase {
         }
     }
 
-
     func testThatStringResponseSerializerFailsWhenFileURLIsInvalid() {
         // Given
         let serializer = StringResponseSerializer()
@@ -1135,10 +1096,7 @@ final class DownloadResponseSerializationTestCase: BaseTestCase {
         XCTAssertTrue(result.isSuccess)
         XCTAssertNotNil(result.value)
         XCTAssertNil(result.error)
-
-        if let string = result.value {
-            XCTAssertEqual(string, "")
-        }
+        XCTAssertEqual(result.value, "")
     }
 
     // MARK: Tests - JSON Response Serializer
@@ -1285,13 +1243,11 @@ final class DownloadResponseSerializationTestCase: BaseTestCase {
         XCTAssertNotNil(result.value)
         XCTAssertNil(result.error)
 
-        if let json = result.value as? NSNull {
-            XCTAssertEqual(json, NSNull())
-        }
+        XCTAssertEqual(result.value as? NSNull, NSNull())
     }
 }
 
-final class CustomResponseSerializerTestCases: BaseTestCase {
+final class CustomResponseSerializerTests: BaseTestCase {
     func testThatCustomResponseSerializersCanBeWrittenWithoutCompilerIssues() {
         // Given
         final class UselessResponseSerializer: ResponseSerializer {
@@ -1316,6 +1272,180 @@ final class CustomResponseSerializerTestCases: BaseTestCase {
     }
 }
 
+final class DataPreprocessorSerializationTests: BaseTestCase {
+    struct DropFirst: DataPreprocessor {
+        func preprocess(_ data: Data) throws -> Data {
+            return data.dropFirst()
+        }
+    }
+
+    struct Throwing: DataPreprocessor {
+        struct Error: Swift.Error { }
+
+        func preprocess(_ data: Data) throws -> Data {
+            throw Error()
+        }
+    }
+
+    func testThatDataResponseSerializerProperlyCallsSuccessfulDataPreprocessor() {
+        // Given
+        let preprocessor = DropFirst()
+        let serializer = DataResponseSerializer(dataPreprocessor: preprocessor)
+        let data = Data("abcd".utf8)
+
+        // When
+        let result = Result { try serializer.serialize(request: nil, response: nil, data: data, error: nil) }
+
+        // Then
+        XCTAssertTrue(result.isSuccess)
+        XCTAssertEqual(result.value, Data("bcd".utf8))
+        XCTAssertNil(result.error)
+    }
+
+    func testThatDataResponseSerializerProperlyReceivesErrorFromFailingDataPreprocessor() {
+        // Given
+        let preprocessor = Throwing()
+        let serializer = DataResponseSerializer(dataPreprocessor: preprocessor)
+        let data = Data("abcd".utf8)
+
+        // When
+        let result = Result { try serializer.serialize(request: nil, response: nil, data: data, error: nil) }
+
+        // Then
+        XCTAssertTrue(result.isFailure)
+        XCTAssertNil(result.value)
+        XCTAssertNotNil(result.error)
+    }
+
+    func testThatStringResponseSerializerProperlyCallsSuccessfulDataPreprocessor() {
+        // Given
+        let preprocessor = DropFirst()
+        let serializer = StringResponseSerializer(dataPreprocessor: preprocessor)
+        let data = Data("abcd".utf8)
+
+        // When
+        let result = Result { try serializer.serialize(request: nil, response: nil, data: data, error: nil) }
+
+        // Then
+        XCTAssertTrue(result.isSuccess)
+        XCTAssertEqual(result.value, "bcd")
+        XCTAssertNil(result.error)
+    }
+
+    func testThatStringResponseSerializerProperlyReceivesErrorFromFailingDataPreprocessor() {
+        // Given
+        let preprocessor = Throwing()
+        let serializer = StringResponseSerializer(dataPreprocessor: preprocessor)
+        let data = Data("abcd".utf8)
+
+        // When
+        let result = Result { try serializer.serialize(request: nil, response: nil, data: data, error: nil) }
+
+        // Then
+        XCTAssertTrue(result.isFailure)
+        XCTAssertNil(result.value)
+        XCTAssertNotNil(result.error)
+    }
+
+    func testThatJSONResponseSerializerProperlyCallsSuccessfulDataPreprocessor() {
+        // Given
+        let preprocessor = DropFirst()
+        let serializer = JSONResponseSerializer(dataPreprocessor: preprocessor)
+        let data = Data("1\"abcd\"".utf8)
+
+        // When
+        let result = Result { try serializer.serialize(request: nil, response: nil, data: data, error: nil) }
+
+        // Then
+        XCTAssertTrue(result.isSuccess)
+        XCTAssertEqual(result.value as? String, "abcd")
+        XCTAssertNil(result.error)
+    }
+
+    func testThatJSONResponseSerializerProperlyReceivesErrorFromFailingDataPreprocessor() {
+        // Given
+        let preprocessor = Throwing()
+        let serializer = JSONResponseSerializer(dataPreprocessor: preprocessor)
+        let data = Data("1\"abcd\"".utf8)
+
+        // When
+        let result = Result { try serializer.serialize(request: nil, response: nil, data: data, error: nil) }
+
+        // Then
+        XCTAssertTrue(result.isFailure)
+        XCTAssertNil(result.value)
+        XCTAssertNotNil(result.error)
+    }
+
+    func testThatDecodableResponseSerializerProperlyCallsSuccessfulDataPreprocessor() {
+        // Given
+        let preprocessor = DropFirst()
+        let serializer = DecodableResponseSerializer<DecodableResponseSerializerTests.DecodableValue>(dataPreprocessor: preprocessor)
+        let data = Data("1{\"string\":\"string\"}".utf8)
+
+        // When
+        let result = Result { try serializer.serialize(request: nil, response: nil, data: data, error: nil) }
+
+        // Then
+        XCTAssertTrue(result.isSuccess)
+        XCTAssertEqual(result.value?.string, "string")
+        XCTAssertNil(result.error)
+    }
+
+    func testThatDecodableResponseSerializerProperlyReceivesErrorFromFailingDataPreprocessor() {
+        // Given
+        let preprocessor = Throwing()
+        let serializer = DecodableResponseSerializer<DecodableResponseSerializerTests.DecodableValue>(dataPreprocessor: preprocessor)
+        let data = Data("1{\"string\":\"string\"}".utf8)
+
+        // When
+        let result = Result { try serializer.serialize(request: nil, response: nil, data: data, error: nil) }
+
+        // Then
+        XCTAssertTrue(result.isFailure)
+        XCTAssertNil(result.value)
+        XCTAssertNotNil(result.error)
+    }
+}
+
+final class DataPreprocessorTests: BaseTestCase {
+    func testThatPassthroughPreprocessorPassesDataThrough() {
+        // Given
+        let preprocessor = PassthroughPreprocessor()
+        let data = Data("data".utf8)
+        
+        // When
+        let result = Result { try preprocessor.preprocess(data) }
+        
+        // Then
+        XCTAssertEqual(data, result.value, "Preprocessed data should equal original data.")
+    }
+    
+    func testThatGoogleXSSIPreprocessorProperlyPreprocessesData() {
+        // Given
+        let preprocessor = GoogleXSSIPreprocessor()
+        let data = Data(")]}',\nabcd".utf8)
+        
+        // When
+        let result = Result { try preprocessor.preprocess(data) }
+        
+        // Then
+        XCTAssertEqual(result.value.map { String(decoding: $0, as: UTF8.self) }, "abcd")
+    }
+    
+    func testThatGoogleXSSIPreprocessorDoesNotChangeDataIfPrefixDoesNotMatch() {
+        // Given
+        let preprocessor = GoogleXSSIPreprocessor()
+        let data = Data("abcd".utf8)
+        
+        // When
+        let result = Result { try preprocessor.preprocess(data) }
+        
+        // Then
+        XCTAssertEqual(result.value.map { String(decoding: $0, as: UTF8.self) }, "abcd")
+    }
+}
+
 extension HTTPURLResponse {
     convenience init(statusCode: Int, headers: HTTPHeaders? = nil) {
         let url = URL(string: "https://httpbin.org/get")!