Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Network Rearchitecture #1341

Merged
merged 109 commits into from
Sep 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
505df39
add noodling from scratch project
designatednerd Apr 29, 2020
ccf49c8
change generics on the request chain
designatednerd Apr 29, 2020
1d262ae
add interceptor provider protocol and a couple default implementations
designatednerd Apr 30, 2020
58a2b8d
separate out legacy vs codable parsing
designatednerd Apr 30, 2020
c5cd160
bits o'cleanup
designatednerd Jul 14, 2020
7f57d20
Working on legacy request chain...ish
designatednerd Jul 14, 2020
73dc705
add readability extension
designatednerd Jul 14, 2020
eff721a
Move cache policy up to base request type
designatednerd Jul 14, 2020
99b1c8f
start adding legacy cache interceptor
designatednerd Jul 15, 2020
ad892c6
add send for result to mock transport
designatednerd Jul 24, 2020
a63bc5d
use send for result to get an actual result
designatednerd Jul 24, 2020
f573bb8
Break off trying to read from the cache too early
designatednerd Jul 24, 2020
1588e1b
Add some documentation to the request chain
designatednerd Jul 27, 2020
d671cf1
add additional error interceptor so people can examine errors more di…
designatednerd Jul 28, 2020
3d2f0fb
add and test legacy cache write interceptor
designatednerd Jul 28, 2020
e4689a3
move cancellation handling to the chain rather than the individual in…
designatednerd Jul 28, 2020
d834070
rearrange a bunch of stuff and make a few things public
designatednerd Jul 28, 2020
085adc5
add quasi-todos for codable interceptor chain
designatednerd Jul 28, 2020
745d3e5
remove fetch source type since it duplicates data in GraphQLResult
designatednerd Jul 28, 2020
97ce0cc
update a ton of documentation
designatednerd Jul 28, 2020
ba1823f
add ability to specify callback queue and have chain return value as …
designatednerd Jul 28, 2020
c618935
moar docs!
designatednerd Jul 29, 2020
ff4b578
initial stab at an upload request
designatednerd Aug 5, 2020
69ce161
initial stab at an APQ interceptor
designatednerd Aug 5, 2020
e67e29b
Constrain on GraphQLResult<Operation.Data> rather than more generic `…
designatednerd Aug 5, 2020
103cb9c
remove unused error
designatednerd Aug 11, 2020
ac73778
Add APQ interceptor to default interceptors
designatednerd Aug 11, 2020
00beb84
Make sure upload requests are `POST` requests
designatednerd Aug 11, 2020
e970fd8
add `uploadForResult` handling to ApolloClient
designatednerd Aug 11, 2020
27975cf
update upload tests to use new architecture
designatednerd Aug 11, 2020
aeb85cb
make star wars server tests able to use any network transport
designatednerd Aug 11, 2020
77df5ad
add parsing for http network transport in parse for result
designatednerd Aug 11, 2020
9d93367
make sure store can be passed in when creating networking stack
designatednerd Aug 11, 2020
8576947
update websocket transports to use more generic uploading transport
designatednerd Aug 11, 2020
5f64ff7
add and use max retry interceptor
designatednerd Aug 11, 2020
35cfe85
Add tests for request chain with APQs
designatednerd Aug 11, 2020
1af4e90
Update HTTP response to be optional until created.
designatednerd Aug 13, 2020
efd1fec
Actually, data from a GraphQL request should never be nil, so don't a…
designatednerd Aug 13, 2020
9b0718f
Move errors to their specific interceptors so people can tell where t…
designatednerd Aug 13, 2020
68cd516
update todos that need to be resolved before merge to warnings
designatednerd Aug 13, 2020
41467ab
Remove duplicated read interceptor
designatednerd Aug 14, 2020
a4c4a4b
add test URLs to test helpers so we stop having to hard-code URLs all…
designatednerd Aug 14, 2020
a67423e
use mock instead of HTTP network transport for split network transpor…
designatednerd Aug 14, 2020
b1e5f9d
use request chain trainsport for caching roundtrip tests
designatednerd Aug 14, 2020
5a237fa
user request chain transport instead of http transport for non-websoc…
designatednerd Aug 14, 2020
b14027b
make sure you can set client name and client version on request chains
designatednerd Aug 18, 2020
444c465
get rid of separate `sendForResult` etc and centralize on a single `s…
designatednerd Aug 18, 2020
e3f27fc
update mock URL transport to be a subclass of RequestChain transport,…
designatednerd Aug 18, 2020
7a944ed
Make sure we're checking a thread-safe value for the last received re…
designatednerd Aug 18, 2020
3a73b66
Get rid of now-unnecessary fetch query operation and asynchronous ope…
designatednerd Aug 18, 2020
a7f66c3
make sure Apollo Test Support is added to Codegen tests
designatednerd Aug 18, 2020
ce42918
Fix build failures in codegen tests
designatednerd Aug 18, 2020
b946e18
add default cache policy on request chain network transport
designatednerd Aug 18, 2020
1fd913c
switch APQ tests to using Request Chain Network Transport
designatednerd Aug 18, 2020
6808f9f
make APQ tests async to account for request construction being async …
designatednerd Aug 18, 2020
632285b
add check for persisted query retry failure to APQ interceptor
designatednerd Aug 18, 2020
2e99554
move star wars server tests to using request chain transport by default
designatednerd Aug 18, 2020
ace861c
pick fight with future me
designatednerd Aug 18, 2020
660eb0d
update skipped tests in schemes
designatednerd Aug 18, 2020
f92ba25
remove old skipped tests in schemes
designatednerd Aug 18, 2020
594eea2
add inititalizer where data is decodable to graphQL result
designatednerd Aug 18, 2020
5a23ee8
Get rid of old HTTPNetworkTransport
designatednerd Aug 19, 2020
2939afe
return the data that didn't parse when it doesn't parse
designatednerd Aug 19, 2020
d2bf67f
Make sure public interceptors have public initializers
designatednerd Aug 19, 2020
b73084f
add a bunch of tests and test interceptors
designatednerd Aug 19, 2020
3aeb966
Add test to validate empty array of interceptors error
designatednerd Aug 19, 2020
632621f
update request chain to check if there's a result when we run out of …
designatednerd Aug 19, 2020
8f032e8
get rid of now-unnecessary finalizing interceptor
designatednerd Aug 19, 2020
288596f
Update cache write intercetptor to proceed rather than return value s…
designatednerd Aug 19, 2020
2f1b54c
Make sure client gets invalidated on deinit, but only when appropriate.
designatednerd Aug 20, 2020
9cf523d
Add a legacy response property to HTTPResponse to facilitate not havi…
designatednerd Aug 20, 2020
b4e0665
fix whitespace sadness
designatednerd Aug 31, 2020
70a38eb
get rid of now-unused context from client
designatednerd Aug 31, 2020
54bea9d
Make sure watcher is cancelled at the end of the test so it doesn't a…
designatednerd Aug 31, 2020
9e20638
temporarily pass nil for extensinons
designatednerd Aug 31, 2020
e8b777c
update mock network transport to be able to update network body, also…
designatednerd Sep 1, 2020
2665f7e
use marginally slower parsing for initial parse if we're going to the…
designatednerd Sep 2, 2020
a4cb8ba
remove now-unused method
designatednerd Sep 2, 2020
554e659
add workaround for the fact that removing the context means actual fe…
designatednerd Sep 2, 2020
ebcb61a
bump timeout on initial fetch expectation for CI
designatednerd Sep 2, 2020
00e79b5
delete removed tests from list of skipped tests in schemes
designatednerd Sep 3, 2020
1e24f0b
get rid of now-unused queue properties
designatednerd Sep 3, 2020
946ce0e
move queue handling down to the network transport level
designatednerd Sep 3, 2020
1da7651
add docs to request chain and request chain network transport
designatednerd Sep 3, 2020
89bdbc3
cancel watcher at the end of watch query tests
designatednerd Sep 3, 2020
d785898
Document more things!
designatednerd Sep 3, 2020
f80c9c9
Make client name and version non-optional to centralize naming handli…
designatednerd Sep 3, 2020
4abe114
Use a UUID for the task rather than an unsafe mutable raw pointer to …
designatednerd Sep 3, 2020
e9879f2
boop fetch expectation time back down
designatednerd Sep 3, 2020
c295cd9
add now-required parameters to web socket
designatednerd Sep 3, 2020
c6a1f99
fix typo 🤦‍♀️
designatednerd Sep 3, 2020
e363582
use context identifier rather than task identifier to disambiguate id…
designatednerd Sep 3, 2020
fdaf7b4
Fix missing documentation
designatednerd Sep 3, 2020
80927a2
rename identifier to context identifier in callback, add docs to apol…
designatednerd Sep 3, 2020
ba76ace
fix missed `contextIdentifer`
designatednerd Sep 3, 2020
9516ab2
align cache miss on `.returnCacheDataAndFetch` with existing code
designatednerd Sep 3, 2020
e588a52
rm whitespace
designatednerd Sep 3, 2020
624df57
add context identifier to equality comparison on HTTPRequest
designatednerd Sep 4, 2020
4bd0d6a
Remove the retry count from the request and make it a property of the…
designatednerd Sep 4, 2020
caead65
documentation for Parseable
designatednerd Sep 4, 2020
2a6df16
make sure errors are public and localized
designatednerd Sep 4, 2020
e220b02
don't use an apollo store in the codable transport (yet?)
designatednerd Sep 8, 2020
b09ffcb
Make sure Foundation is imported where needed
designatednerd Sep 8, 2020
795af72
make sure foundation is imported on SplitNetworkTransport
designatednerd Sep 8, 2020
bc42ea5
move setup for additional headers into the initializer of HTTPRequest
designatednerd Sep 9, 2020
d18aab3
add debug description for http request
designatednerd Sep 9, 2020
f35ae0a
completely rewrite the `Initialization` document to handle setting up…
designatednerd Sep 9, 2020
3f2a6e7
Make provided interceptor providers open so they can be subclassed.
designatednerd Sep 9, 2020
38135ee
update tutorial for request chain network transport
designatednerd Sep 9, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 144 additions & 15 deletions Apollo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ let package = Package(
.testTarget(
name: "ApolloCodegenTests",
dependencies: [
"ApolloTestSupport",
"ApolloCodegenLib"
]),
.testTarget(
Expand Down
230 changes: 41 additions & 189 deletions Sources/Apollo/ApolloClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public enum CachePolicy {
case returnCacheDataDontFetch
/// Return data from the cache if available, and always fetch results from the server.
case returnCacheDataAndFetch

/// The current default cache policy.
public static var `default`: CachePolicy {
.returnCacheDataElseFetch
}
}

/// A handler for operation results.
Expand All @@ -28,9 +33,6 @@ public class ApolloClient {

public let store: ApolloStore // <- conformance to ApolloClientProtocol

private let queue: DispatchQueue
private let operationQueue: OperationQueue

public enum ApolloClientError: Error, LocalizedError {
case noUploadTransport

Expand All @@ -50,69 +52,18 @@ public class ApolloClient {
public init(networkTransport: NetworkTransport, store: ApolloStore = ApolloStore(cache: InMemoryNormalizedCache())) {
self.networkTransport = networkTransport
self.store = store

queue = DispatchQueue(label: "com.apollographql.ApolloClient")
operationQueue = OperationQueue()
operationQueue.underlyingQueue = queue
}

/// Creates a client with an HTTP network transport connecting to the specified URL.
///
/// - Parameter url: The URL of a GraphQL server to connect to.
public convenience init(url: URL) {
self.init(networkTransport: HTTPNetworkTransport(url: url))
}

fileprivate func send<Operation: GraphQLOperation>(operation: Operation,
shouldPublishResultToStore: Bool,
context: UnsafeMutableRawPointer?,
resultHandler: @escaping GraphQLResultHandler<Operation.Data>) -> Cancellable {
return networkTransport.send(operation: operation) { [weak self] result in
guard let self = self else {
return
}
self.handleOperationResult(shouldPublishResultToStore: shouldPublishResultToStore,
context: context,
result,
resultHandler: resultHandler)
}
}

private func handleOperationResult<Data: GraphQLSelectionSet>(shouldPublishResultToStore: Bool,
context: UnsafeMutableRawPointer?,
_ result: Result<GraphQLResponse<Data>, Error>,
resultHandler: @escaping GraphQLResultHandler<Data>) {
switch result {
case .failure(let error):
resultHandler(.failure(error))
case .success(let response):
// If there is no need to publish the result to the store, we can use a fast path.
if !shouldPublishResultToStore {
do {
let result = try response.parseResultFast()
resultHandler(.success(result))
} catch {
resultHandler(.failure(error))
}
return
}

firstly {
try response.parseResult(cacheKeyForObject: self.cacheKeyForObject)
}.andThen { [weak self] (result, records) in
guard let self = self else {
return
}
if let records = records {
self.store.publish(records: records, context: context).catch { error in
preconditionFailure(String(describing: error))
}
}
resultHandler(.success(result))
}.catch { error in
resultHandler(.failure(error))
}
}
let store = ApolloStore(cache: InMemoryNormalizedCache())
let provider = LegacyInterceptorProvider(store: store)
let transport = RequestChainNetworkTransport(interceptorProvider: provider,
endpointURL: url)

self.init(networkTransport: transport, store: store)
}
}

Expand All @@ -124,180 +75,81 @@ extension ApolloClient: ApolloClientProtocol {
get {
return self.store.cacheKeyForObject
}

set {
self.store.cacheKeyForObject = newValue
}
}

public func clearCache(callbackQueue: DispatchQueue = .main, completion: ((Result<Void, Error>) -> Void)? = nil) {
public func clearCache(callbackQueue: DispatchQueue = .main,
completion: ((Result<Void, Error>) -> Void)? = nil) {
self.store.clearCache(completion: completion)
}

@discardableResult public func fetch<Query: GraphQLQuery>(query: Query,
cachePolicy: CachePolicy = .returnCacheDataElseFetch,
context: UnsafeMutableRawPointer? = nil,
contextIdentifier: UUID? = nil,
queue: DispatchQueue = DispatchQueue.main,
resultHandler: GraphQLResultHandler<Query.Data>? = nil) -> Cancellable {
let resultHandler = wrapResultHandler(resultHandler, queue: queue)

// If we don't have to go through the cache, there is no need to create an operation
// and we can return a network task directly
if cachePolicy == .fetchIgnoringCacheData || cachePolicy == .fetchIgnoringCacheCompletely {
return self.send(operation: query, shouldPublishResultToStore: cachePolicy != .fetchIgnoringCacheCompletely, context: context, resultHandler: resultHandler)
} else {
let operation = FetchQueryOperation(client: self, query: query, cachePolicy: cachePolicy, context: context, resultHandler: resultHandler)
self.operationQueue.addOperation(operation)
return operation
return self.networkTransport.send(operation: query,
cachePolicy: cachePolicy,
contextIdentifier: contextIdentifier,
callbackQueue: queue) { result in
resultHandler?(result)
}
}

public func watch<Query: GraphQLQuery>(query: Query,
cachePolicy: CachePolicy = .returnCacheDataElseFetch,
queue: DispatchQueue = .main,
resultHandler: @escaping GraphQLResultHandler<Query.Data>) -> GraphQLQueryWatcher<Query> {
let watcher = GraphQLQueryWatcher(client: self,
query: query,
resultHandler: wrapResultHandler(resultHandler, queue: queue))
resultHandler: resultHandler)
watcher.fetch(cachePolicy: cachePolicy)
return watcher
}

@discardableResult
public func perform<Mutation: GraphQLMutation>(mutation: Mutation,
context: UnsafeMutableRawPointer? = nil,
queue: DispatchQueue = DispatchQueue.main,
queue: DispatchQueue = .main,
resultHandler: GraphQLResultHandler<Mutation.Data>? = nil) -> Cancellable {
return self.send(operation: mutation,
shouldPublishResultToStore: true,
context: context,
resultHandler: wrapResultHandler(resultHandler, queue: queue))
return self.networkTransport.send(operation: mutation,
cachePolicy: .default,
contextIdentifier: nil,
callbackQueue: queue) { result in
resultHandler?(result)
}
}

@discardableResult
public func upload<Operation: GraphQLOperation>(operation: Operation,
context: UnsafeMutableRawPointer? = nil,
files: [GraphQLFile],
queue: DispatchQueue = .main,
resultHandler: GraphQLResultHandler<Operation.Data>? = nil) -> Cancellable {
let wrappedHandler = wrapResultHandler(resultHandler, queue: queue)
guard let uploadingTransport = self.networkTransport as? UploadingNetworkTransport else {
assertionFailure("Trying to upload without an uploading transport. Please make sure your network transport conforms to `UploadingNetworkTransport`.")
wrappedHandler(.failure(ApolloClientError.noUploadTransport))
queue.async {
resultHandler?(.failure(ApolloClientError.noUploadTransport))
}
return EmptyCancellable()
}

return uploadingTransport.upload(operation: operation, files: files) { [weak self] result in
guard let self = self else {
return
}
self.handleOperationResult(shouldPublishResultToStore: true,
context: context, result,
resultHandler: wrappedHandler)
return uploadingTransport.upload(operation: operation,
files: files,
callbackQueue: queue) { result in
resultHandler?(result)
}
}

@discardableResult
public func subscribe<Subscription: GraphQLSubscription>(subscription: Subscription,
queue: DispatchQueue = .main,
resultHandler: @escaping GraphQLResultHandler<Subscription.Data>) -> Cancellable {
return self.send(operation: subscription,
shouldPublishResultToStore: true,
context: nil,
resultHandler: wrapResultHandler(resultHandler, queue: queue))
return self.networkTransport.send(operation: subscription,
cachePolicy: .default,
contextIdentifier: nil,
callbackQueue: queue,
completionHandler: resultHandler)
}
}

private func wrapResultHandler<Data>(_ resultHandler: GraphQLResultHandler<Data>?, queue handlerQueue: DispatchQueue) -> GraphQLResultHandler<Data> {
guard let resultHandler = resultHandler else {
return { _ in }
}

return { result in
handlerQueue.async {
resultHandler(result)
}
}
}

private final class FetchQueryOperation<Query: GraphQLQuery>: AsynchronousOperation, Cancellable {
weak var client: ApolloClient?
let query: Query
let cachePolicy: CachePolicy
let context: UnsafeMutableRawPointer?
let resultHandler: GraphQLResultHandler<Query.Data>

private var networkTask: Cancellable?

init(client: ApolloClient,
query: Query,
cachePolicy: CachePolicy,
context: UnsafeMutableRawPointer?,
resultHandler: @escaping GraphQLResultHandler<Query.Data>) {
self.client = client
self.query = query
self.cachePolicy = cachePolicy
self.context = context
self.resultHandler = resultHandler
}

override public func start() {
if isCancelled {
state = .finished
return
}

state = .executing

if cachePolicy == .fetchIgnoringCacheData {
fetchFromNetwork()
return
}

client?.store.load(query: query) { [weak self] result in
guard let self = self else {
return
}
if self.isCancelled {
self.state = .finished
return
}

switch result {
case .success:
self.resultHandler(result)

if self.cachePolicy != .returnCacheDataAndFetch {
self.state = .finished
return
}
case .failure:
if self.cachePolicy == .returnCacheDataDontFetch {
self.resultHandler(result)
self.state = .finished
return
}
}

self.fetchFromNetwork()
}
}

func fetchFromNetwork() {
networkTask = client?.send(operation: query,
shouldPublishResultToStore: true,
context: context) { [weak self] result in
guard let self = self else {
return
}
self.resultHandler(result)
self.state = .finished
return
}
}

override public func cancel() {
super.cancel()
networkTask?.cancel()
}
}
14 changes: 3 additions & 11 deletions Sources/Apollo/ApolloClientProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,55 +22,47 @@ public protocol ApolloClientProtocol: class {
/// - Parameters:
/// - query: The query to fetch.
/// - cachePolicy: A cache policy that specifies when results should be fetched from the server and when data should be loaded from the local cache.
/// - context: [optional] A context to use for the cache to work with results. Should default to nil.
/// - queue: A dispatch queue on which the result handler will be called. Defaults to the main queue.
/// - contextIdentifier: [optional] A unique identifier for this request, to help with deduping cache hits for watchers. Should default to `nil`.
/// - resultHandler: [optional] A closure that is called when query results are available or when an error occurs.
/// - Returns: An object that can be used to cancel an in progress fetch.
func fetch<Query: GraphQLQuery>(query: Query,
cachePolicy: CachePolicy,
context: UnsafeMutableRawPointer?,
contextIdentifier: UUID?,
queue: DispatchQueue,
resultHandler: GraphQLResultHandler<Query.Data>?) -> Cancellable

/// Watches a query by first fetching an initial result from the server or from the local cache, depending on the current contents of the cache and the specified cache policy. After the initial fetch, the returned query watcher object will get notified whenever any of the data the query result depends on changes in the local cache, and calls the result handler again with the new result.
///
/// - Parameters:
/// - query: The query to fetch.
/// - fetchHTTPMethod: The HTTP Method to be used.
/// - cachePolicy: A cache policy that specifies when results should be fetched from the server or from the local cache.
/// - queue: A dispatch queue on which the result handler will be called. Should default to the main queue.
/// - resultHandler: [optional] A closure that is called when query results are available or when an error occurs.
/// - Returns: A query watcher object that can be used to control the watching behavior.
func watch<Query: GraphQLQuery>(query: Query,
cachePolicy: CachePolicy,
queue: DispatchQueue,
resultHandler: @escaping GraphQLResultHandler<Query.Data>) -> GraphQLQueryWatcher<Query>

/// Performs a mutation by sending it to the server.
///
/// - Parameters:
/// - mutation: The mutation to perform.
/// - context: [optional] A context to use for the cache to work with results. Should default to nil.
/// - queue: A dispatch queue on which the result handler will be called. Defaults to the main queue.
/// - resultHandler: An optional closure that is called when mutation results are available or when an error occurs.
/// - Returns: An object that can be used to cancel an in progress mutation.
func perform<Mutation: GraphQLMutation>(mutation: Mutation,
context: UnsafeMutableRawPointer?,
queue: DispatchQueue,
resultHandler: GraphQLResultHandler<Mutation.Data>?) -> Cancellable

/// Uploads the given files with the given operation.
///
/// - Parameters:
/// - operation: The operation to send
/// - context: [optional] A context to use for the cache to work with results. Should default to nil.
/// - files: An array of `GraphQLFile` objects to send.
/// - queue: A dispatch queue on which the result handler will be called. Should default to the main queue.
/// - completionHandler: The completion handler to execute when the request completes or errors
/// - completionHandler: The completion handler to execute when the request completes or errors. Note that an error will be returned If your `networkTransport` does not also conform to `UploadingNetworkTransport`.
/// - Returns: An object that can be used to cancel an in progress request.
/// - Throws: If your `networkTransport` does not also conform to `UploadingNetworkTransport`.
func upload<Operation: GraphQLOperation>(operation: Operation,
context: UnsafeMutableRawPointer?,
files: [GraphQLFile],
queue: DispatchQueue,
resultHandler: GraphQLResultHandler<Operation.Data>?) -> Cancellable
Expand Down
20 changes: 20 additions & 0 deletions Sources/Apollo/ApolloErrorInterceptor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Foundation

/// An error interceptor called to allow further examination of error data when an error occurs in the chain.
public protocol ApolloErrorInterceptor {
Copy link

@amadeu01 amadeu01 Aug 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it would work, but what do you think of instead using protocol for this interceptor, actually, use just a function. For instance,

typealias ApolloErrorInterceptor =  (Error,
       RequestChain,
       HTTPRequest<Operation>,
       HTTPResponse<Operation>?,
       @escaping (Result<GraphQLResult<Operation.Data>, Error>) -> Void)) -> Void

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would wayyyyyy rather have this as a protocol, particularly since it gives longer term flexibility to add more methods if needed.


/// Asynchronously handles the receipt of an error at any point in the chain.
///
/// - Parameters:
/// - error: The received error
/// - chain: The chain the error was received on
/// - request: The request, 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<Operation: GraphQLOperation>(
error: Error,
chain: RequestChain,
request: HTTPRequest<Operation>,
response: HTTPResponse<Operation>?,
completion: @escaping (Result<GraphQLResult<Operation.Data>, Error>) -> Void)
}
Loading