From 0bde0550a9cb292e9f4fdf84fd4acb10ff911307 Mon Sep 17 00:00:00 2001 From: Ellen Shapiro Date: Wed, 12 Aug 2020 20:49:41 -0500 Subject: [PATCH] Update HTTP response to be optional until created. --- Sources/Apollo/ApolloErrorInterceptor.swift | 4 +-- Sources/Apollo/ApolloInterceptor.swift | 4 +-- .../AutomaticPersistedQueryInterceptor.swift | 4 +-- .../Apollo/CodableParsingInterceptor.swift | 25 +++++++++++++++---- Sources/Apollo/FinalizingInterceptor.swift | 8 +++--- Sources/Apollo/HTTPResponse.swift | 10 ++++---- .../Apollo/LegacyCacheReadInterceptor.swift | 2 +- .../Apollo/LegacyCacheWriteInterceptor.swift | 4 +-- Sources/Apollo/LegacyParsingInterceptor.swift | 21 +++++++++------- Sources/Apollo/MaxRetryInterceptor.swift | 2 +- Sources/Apollo/NetworkFetchInterceptor.swift | 7 +++--- Sources/Apollo/RequestChain.swift | 18 ++++++------- Sources/Apollo/ResponseCodeInterceptor.swift | 9 ++++--- 13 files changed, 68 insertions(+), 50 deletions(-) diff --git a/Sources/Apollo/ApolloErrorInterceptor.swift b/Sources/Apollo/ApolloErrorInterceptor.swift index 92721fa123..eee485b2a2 100644 --- a/Sources/Apollo/ApolloErrorInterceptor.swift +++ b/Sources/Apollo/ApolloErrorInterceptor.swift @@ -9,12 +9,12 @@ public protocol ApolloErrorInterceptor { /// - error: The received error /// - chain: The chain the error was received on /// - request: The request, as far as it was constructed - /// - response: The response, as far as it was constructed + /// - response: [optional] The response, if one was received /// - completion: The completion closure to fire when the operation has completed. Note that if you call `retry` on the chain, you will not want to call the completion block in this method. func handleErrorAsync( error: Error, chain: RequestChain, request: HTTPRequest, - response: HTTPResponse, + response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) } diff --git a/Sources/Apollo/ApolloInterceptor.swift b/Sources/Apollo/ApolloInterceptor.swift index 3498958746..00819d3fec 100644 --- a/Sources/Apollo/ApolloInterceptor.swift +++ b/Sources/Apollo/ApolloInterceptor.swift @@ -6,11 +6,11 @@ public protocol ApolloInterceptor: class { /// - Parameters: /// - chain: The chain the interceptor is a part of. /// - request: The request, as far as it has been constructed - /// - response: The response, as far as it has been constructed + /// - response: [optional] The response, if received /// - completion: The completion block to fire when data needs to be returned to the UI. func interceptAsync( chain: RequestChain, request: HTTPRequest, - response: HTTPResponse, + response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) } diff --git a/Sources/Apollo/AutomaticPersistedQueryInterceptor.swift b/Sources/Apollo/AutomaticPersistedQueryInterceptor.swift index 6a8d4096ef..12bd7e4e0f 100644 --- a/Sources/Apollo/AutomaticPersistedQueryInterceptor.swift +++ b/Sources/Apollo/AutomaticPersistedQueryInterceptor.swift @@ -9,7 +9,7 @@ public class AutomaticPersistedQueryInterceptor: ApolloInterceptor { public func interceptAsync( chain: RequestChain, request: HTTPRequest, - response: HTTPResponse, + response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) { guard @@ -22,7 +22,7 @@ public class AutomaticPersistedQueryInterceptor: ApolloInterceptor { return } - guard let result = response.parsedResponse else { + guard let result = response?.parsedResponse else { // This is in the wrong order - this needs to be parsed before we can check it. chain.handleErrorAsync(APQError.noParsedResponse, request: request, diff --git a/Sources/Apollo/CodableParsingInterceptor.swift b/Sources/Apollo/CodableParsingInterceptor.swift index c4bcbf3068..929390ec7a 100644 --- a/Sources/Apollo/CodableParsingInterceptor.swift +++ b/Sources/Apollo/CodableParsingInterceptor.swift @@ -2,6 +2,7 @@ import Foundation public enum ParserError: Error { case nilData + case noResponseToParse case couldNotParseToLegacyJSON } @@ -18,25 +19,39 @@ public class CodableParsingInterceptor: ApolloInte public func interceptAsync( chain: RequestChain, request: HTTPRequest, - response: HTTPResponse, + response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) { guard !self.isCancelled else { return } - guard let data = response.rawData else { - completion(.failure(ParserError.nilData)) + guard let createdResponse = response else { + chain.handleErrorAsync(ParserError.noResponseToParse, + request: request, + response: response, + completion: completion) + return + } + + guard let data = response?.rawData else { + chain.handleErrorAsync(ParserError.nilData, + request: request, + response: createdResponse, + completion: completion) return } do { let parsedData = try GraphQLResult(from: data, decoder: self.decoder) - response.parsedResponse = parsedData + createdResponse.parsedResponse = parsedData chain.proceedAsync(request: request, response: response, completion: completion) } catch { - completion(.failure(error)) + chain.handleErrorAsync(error, + request: request, + response: createdResponse, + completion: completion) } } } diff --git a/Sources/Apollo/FinalizingInterceptor.swift b/Sources/Apollo/FinalizingInterceptor.swift index 5b9361d319..724bbce929 100644 --- a/Sources/Apollo/FinalizingInterceptor.swift +++ b/Sources/Apollo/FinalizingInterceptor.swift @@ -10,12 +10,12 @@ public class FinalizingInterceptor: ApolloInterceptor { public func interceptAsync( chain: RequestChain, request: HTTPRequest, - response: HTTPResponse, + response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) { - guard let parsed = response.parsedResponse else { - chain.handleErrorAsync(FinalizationError.nilParsedValue(httpResponse: response.httpResponse, - rawData: response.rawData), + guard let parsed = response?.parsedResponse else { + chain.handleErrorAsync(FinalizationError.nilParsedValue(httpResponse: response?.httpResponse, + rawData: response?.rawData), request: request, response: response, completion: completion) diff --git a/Sources/Apollo/HTTPResponse.swift b/Sources/Apollo/HTTPResponse.swift index c8418ff08d..74363a426e 100644 --- a/Sources/Apollo/HTTPResponse.swift +++ b/Sources/Apollo/HTTPResponse.swift @@ -2,17 +2,17 @@ import Foundation /// Data about a response received by an HTTP request. public class HTTPResponse { - public var httpResponse: HTTPURLResponse? + public var httpResponse: HTTPURLResponse public var rawData: Data? public var parsedResponse: GraphQLResult? /// Designated initializer /// /// - Parameters: - /// - response: [optional] The `HTTPURLResponse` received from the server. Will be nil if not yet received or if the response received was not an `HTTPURLResponse`. - /// - rawData: [optional] The raw, unparsed data received from the server. Will be nil if not yet received or if data received from the server was nil. - /// - parsedResponse: [optional] The response parsed into the `ParsedValue` type. Will be nil if not yet received, not yet parsed, or if parsing failed. - public init(response: HTTPURLResponse?, + /// - response: The `HTTPURLResponse` received from the server. + /// - rawData: [optional] The raw, unparsed data received from the server. Will be nil if data received from the server was nil. + /// - parsedResponse: [optional] The response parsed into the `ParsedValue` type. Will be nil if not yet parsed, or if parsing failed. + public init(response: HTTPURLResponse, rawData: Data?, parsedResponse: GraphQLResult?) { self.httpResponse = response diff --git a/Sources/Apollo/LegacyCacheReadInterceptor.swift b/Sources/Apollo/LegacyCacheReadInterceptor.swift index bf2d27342a..c38761afc2 100644 --- a/Sources/Apollo/LegacyCacheReadInterceptor.swift +++ b/Sources/Apollo/LegacyCacheReadInterceptor.swift @@ -19,7 +19,7 @@ public class LegacyCacheReadInterceptor: ApolloInterceptor { public func interceptAsync( chain: RequestChain, request: HTTPRequest, - response: HTTPResponse, + response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) { switch request.operation.operationType { diff --git a/Sources/Apollo/LegacyCacheWriteInterceptor.swift b/Sources/Apollo/LegacyCacheWriteInterceptor.swift index 31b1c723d7..31f16875d6 100644 --- a/Sources/Apollo/LegacyCacheWriteInterceptor.swift +++ b/Sources/Apollo/LegacyCacheWriteInterceptor.swift @@ -15,7 +15,7 @@ public class LegacyCacheWriteInterceptor: ApolloInterceptor { public func interceptAsync( chain: RequestChain, request: HTTPRequest, - response: HTTPResponse, + response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) { guard request.cachePolicy != .fetchIgnoringCacheCompletely else { @@ -26,7 +26,7 @@ public class LegacyCacheWriteInterceptor: ApolloInterceptor { return } - guard let data = response.rawData else { + guard let data = response?.rawData else { chain.handleErrorAsync(ParserError.nilData, request: request, response: response, diff --git a/Sources/Apollo/LegacyParsingInterceptor.swift b/Sources/Apollo/LegacyParsingInterceptor.swift index 38b3612c96..645b7bbf11 100644 --- a/Sources/Apollo/LegacyParsingInterceptor.swift +++ b/Sources/Apollo/LegacyParsingInterceptor.swift @@ -6,14 +6,16 @@ public class LegacyParsingInterceptor: ApolloInterceptor { public func interceptAsync( chain: RequestChain, request: HTTPRequest, - response: HTTPResponse, + response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) { - guard let data = response.rawData else { - chain.handleErrorAsync(ParserError.nilData, - request: request, - response: response, - completion: completion) + guard + let createdResponse = response, + let data = createdResponse.rawData else { + chain.handleErrorAsync(ParserError.nilData, + request: request, + response: response, + completion: completion) return } @@ -25,11 +27,12 @@ public class LegacyParsingInterceptor: ApolloInterceptor { let graphQLResponse = GraphQLResponse(operation: request.operation, body: body) let parsedResult = try graphQLResponse.parseResultFast() - let typedResult = parsedResult - response.parsedResponse = typedResult + let typedResult = parsedResult + + createdResponse.parsedResponse = typedResult chain.proceedAsync(request: request, - response: response, + response: createdResponse, completion: completion) } catch { diff --git a/Sources/Apollo/MaxRetryInterceptor.swift b/Sources/Apollo/MaxRetryInterceptor.swift index f614d116a8..6b7b3e3d52 100644 --- a/Sources/Apollo/MaxRetryInterceptor.swift +++ b/Sources/Apollo/MaxRetryInterceptor.swift @@ -19,7 +19,7 @@ public class MaxRetryInterceptor: ApolloInterceptor { public func interceptAsync( chain: RequestChain, request: HTTPRequest, - response: HTTPResponse, + response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) { guard request.retryCount <= self.maxRetries else { let error = RetryError.hitMaxRetryCount(count: self.maxRetries, diff --git a/Sources/Apollo/NetworkFetchInterceptor.swift b/Sources/Apollo/NetworkFetchInterceptor.swift index 19e7c89551..4648a9a02c 100644 --- a/Sources/Apollo/NetworkFetchInterceptor.swift +++ b/Sources/Apollo/NetworkFetchInterceptor.swift @@ -15,7 +15,7 @@ public class NetworkFetchInterceptor: ApolloInterceptor, Cancellable { public func interceptAsync( chain: RequestChain, request: HTTPRequest, - response: HTTPResponse, + response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) { let urlRequest: URLRequest @@ -45,8 +45,9 @@ public class NetworkFetchInterceptor: ApolloInterceptor, Cancellable { response: response, completion: completion) case .success(let (data, httpResponse)): - response.httpResponse = httpResponse - response.rawData = data + let response = HTTPResponse(response: httpResponse, + rawData: data, + parsedResponse: nil) chain.proceedAsync(request: request, response: response, completion: completion) diff --git a/Sources/Apollo/RequestChain.swift b/Sources/Apollo/RequestChain.swift index f52ca3419e..2540303838 100644 --- a/Sources/Apollo/RequestChain.swift +++ b/Sources/Apollo/RequestChain.swift @@ -44,21 +44,18 @@ public class RequestChain: Cancellable { request: HTTPRequest, completion: @escaping (Result, Error>) -> Void) { assert(self.currentIndex == 0, "The interceptor index should be zero when calling this method") - - let response: HTTPResponse = HTTPResponse(response: nil, - rawData: nil, - parsedResponse: nil) + guard let firstInterceptor = self.interceptors.first else { handleErrorAsync(ChainError.noInterceptors, request: request, - response: response, + response: nil, completion: completion) return } firstInterceptor.interceptAsync(chain: self, request: request, - response: response, + response: nil, completion: completion) } @@ -68,9 +65,10 @@ public class RequestChain: Cancellable { /// - request: The in-progress request object /// - response: The in-progress response object /// - completion: The completion closure to call when data has been processed and should be returned to the UI. - public func proceedAsync(request: HTTPRequest, - response: HTTPResponse, - completion: @escaping (Result, Error>) -> Void) { + public func proceedAsync( + request: HTTPRequest, + response: HTTPResponse?, + completion: @escaping (Result, Error>) -> Void) { guard self.isNotCancelled else { // Do not proceed, this chain has been cancelled. return @@ -135,7 +133,7 @@ public class RequestChain: Cancellable { public func handleErrorAsync( _ error: Error, request: HTTPRequest, - response: HTTPResponse, + response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) { guard self.isNotCancelled else { return diff --git a/Sources/Apollo/ResponseCodeInterceptor.swift b/Sources/Apollo/ResponseCodeInterceptor.swift index 0cbd94f594..7394ce8b12 100644 --- a/Sources/Apollo/ResponseCodeInterceptor.swift +++ b/Sources/Apollo/ResponseCodeInterceptor.swift @@ -10,13 +10,14 @@ public class ResponseCodeInterceptor: ApolloInterceptor { public func interceptAsync( chain: RequestChain, request: HTTPRequest, - response: HTTPResponse, + response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) { - guard response.httpResponse?.apollo.isSuccessful == true else { - let error = ResponseCodeError.invalidResponseCode(response: response.httpResponse, + + guard response?.httpResponse.apollo.isSuccessful == true else { + let error = ResponseCodeError.invalidResponseCode(response: response?.httpResponse, - rawData: response.rawData) + rawData: response?.rawData) chain.handleErrorAsync(error, request: request,