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

Result ALL THE THINGS!!! #644

Merged
merged 21 commits into from
Jul 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7b9c291
remove deprecated handler type
designatednerd Jul 16, 2019
52e1d8a
Switch to `Result` type with the `GraphQLResultHandler`
designatednerd Jul 16, 2019
586a55e
update ApolloTests and ApolloCacheDependentTests
designatednerd Jul 17, 2019
339b1f9
update SQLite tests for result type
designatednerd Jul 17, 2019
f772a2a
Fix placement of call to go out to network
designatednerd Jul 17, 2019
f485989
Check for correct errors in tests
designatednerd Jul 17, 2019
801efb9
Update Websocket to use swift result for completion handlers
designatednerd Jul 17, 2019
0bc5833
fix typo in test class name
designatednerd Jul 17, 2019
b42cc37
Update web socket tests for result type
designatednerd Jul 17, 2019
6ca1e8a
Update documentation
designatednerd Jul 17, 2019
4be1186
move send into an extension to make it clearer that this method is co…
designatednerd Jul 17, 2019
94a0073
rm whitespace
designatednerd Jul 17, 2019
0c059b4
pull out split network transport's `NetworkTransport` conformance; up…
designatednerd Jul 17, 2019
647a7da
pull out `WebSocketTransport`'s `NetworkTransport` and `WebSocketDele…
designatednerd Jul 17, 2019
3725fe0
rm excess whitespace 🤦‍♀️
designatednerd Jul 17, 2019
acf11ee
Clarify header comment a bit
designatednerd Jul 17, 2019
f9bafbf
rm excess whitespace
designatednerd Jul 17, 2019
6eac173
simplify check
designatednerd Jul 17, 2019
b29b681
clarify that GraphQLResult has both data and errors
designatednerd Jul 19, 2019
8c4cbde
`outerResult` -> `result` anywhere there isn't actually an inner result.
designatednerd Jul 19, 2019
c8b94d8
rm whitespace
designatednerd Jul 19, 2019
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
100 changes: 48 additions & 52 deletions Sources/Apollo/ApolloClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,8 @@ public enum CachePolicy {
/// A handler for operation results.
///
/// - Parameters:
/// - result: The result of the performed operation, or `nil` if an error occurred.
/// - error: An error that indicates why the mutation failed, or `nil` if the mutation was succesful.
public typealias GraphQLResultHandler<Data> = (_ result: GraphQLResult<Data>?, _ error: Error?) -> Void

@available(*, deprecated, renamed: "GraphQLResultHandler")
public typealias OperationResultHandler<Operation: GraphQLOperation> = GraphQLResultHandler<Operation.Data>
/// - result: The result of a performed operation. Will have a `GraphQLResult` with any parsed data and any GraphQL errors on `success`, and an `Error` on `failure`.
public typealias GraphQLResultHandler<Data> = (Result<GraphQLResult<Data>, Error>) -> Void

/// The `ApolloClient` class provides the core API for Apollo. This API provides methods to fetch and watch queries, and to perform mutations.
public class ApolloClient {
Expand Down Expand Up @@ -103,7 +99,6 @@ public class ApolloClient {
/// - 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 query results are available or when an error occurs.
/// - Returns: A query watcher object that can be used to control the watching behavior.

public func watch<Query: GraphQLQuery>(query: Query, cachePolicy: CachePolicy = .returnCacheDataElseFetch, queue: DispatchQueue = DispatchQueue.main, resultHandler: @escaping GraphQLResultHandler<Query.Data>) -> GraphQLQueryWatcher<Query> {
let watcher = GraphQLQueryWatcher(client: self, query: query, resultHandler: wrapResultHandler(resultHandler, queue: queue))
watcher.fetch(cachePolicy: cachePolicy)
Expand Down Expand Up @@ -135,47 +130,47 @@ public class ApolloClient {
}

fileprivate func send<Operation: GraphQLOperation>(operation: Operation, shouldPublishResultToStore: Bool, context: UnsafeMutableRawPointer?, resultHandler: @escaping GraphQLResultHandler<Operation.Data>) -> Cancellable {
return networkTransport.send(operation: operation) { (response, error) in
guard let response = response else {
resultHandler(nil, error)
return
}

// 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(result, nil)
} catch {
resultHandler(nil, error)
}
return
}

firstly {
try response.parseResult(cacheKeyForObject: self.cacheKeyForObject)
}.andThen { (result, records) in
if let records = records {
self.store.publish(records: records, context: context).catch { error in
preconditionFailure(String(describing: error))
return networkTransport.send(operation: operation) { result in
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 { (result, records) in
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))
}
resultHandler(result, nil)
}.catch { error in
resultHandler(nil, error)
}
}
}
}

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

return { (result, error) in
return { result in
handlerQueue.async {
resultHandler(result, error)
resultHandler(result)
}
}
}
Expand Down Expand Up @@ -210,34 +205,35 @@ private final class FetchQueryOperation<Query: GraphQLQuery>: AsynchronousOperat
return
}

client.store.load(query: query) { (result, error) in
if error == nil {
self.resultHandler(result, nil)

if self.cachePolicy != .returnCacheDataAndFetch {
self.state = .finished
return
}
}

client.store.load(query: query) { result in
if self.isCancelled {
self.state = .finished
return
}

if self.cachePolicy == .returnCacheDataDontFetch {
self.resultHandler(nil, nil)
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) { (result, error) in
self.resultHandler(result, error)
networkTask = client.send(operation: query, shouldPublishResultToStore: true, context: context) { result in
self.resultHandler(result)
self.state = .finished
return
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/Apollo/ApolloStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,9 @@ public final class ApolloStore {

public func load<Query: GraphQLQuery>(query: Query, resultHandler: @escaping GraphQLResultHandler<Query.Data>) {
load(query: query).andThen { result in
resultHandler(result, nil)
resultHandler(.success(result))
}.catch { error in
resultHandler(nil, error)
resultHandler(.failure(error))
}
}

Expand Down
14 changes: 10 additions & 4 deletions Sources/Apollo/GraphQLQueryWatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,17 @@ public final class GraphQLQueryWatcher<Query: GraphQLQuery>: Cancellable, Apollo
}

func fetch(cachePolicy: CachePolicy) {
fetching = client?.fetch(query: query, cachePolicy: cachePolicy, context: &context) { [weak self] (result, error) in
fetching = client?.fetch(query: query, cachePolicy: cachePolicy, context: &context) { [weak self] result in
guard let `self` = self else { return }

self.dependentKeys = result?.dependentKeys
self.resultHandler(result, error)

switch result {
case .success(let graphQLResult):
self.dependentKeys = graphQLResult.dependentKeys
case .failure:
break
}

self.resultHandler(result)
}
}

Expand Down
37 changes: 17 additions & 20 deletions Sources/Apollo/HTTPNetworkTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public protocol HTTPNetworkTransportRetryDelegate: HTTPNetworkTransportDelegate
// MARK: -

/// A network transport that uses HTTP POST requests to send GraphQL operations to a server, and that uses `URLSession` as the networking implementation.
public class HTTPNetworkTransport: NetworkTransport {
public class HTTPNetworkTransport {
let url: URL
let session: URLSession
let serializationFormat = JSONSerializationFormat.self
Expand Down Expand Up @@ -96,35 +96,23 @@ public class HTTPNetworkTransport: NetworkTransport {
self.delegate = delegate
}

/// Send a GraphQL operation to a server and return a response.
///
/// - Parameters:
/// - operation: The operation to send.
/// - completionHandler: A closure to call when a request completes.
/// - response: The response received from the server, or `nil` if an error occurred.
/// - error: An error that indicates why a request failed, or `nil` if the request was succesful.
/// - Returns: An object that can be used to cancel an in progress request.
public func send<Operation>(operation: Operation, completionHandler: @escaping (_ response: GraphQLResponse<Operation>?, _ error: Error?) -> Void) -> Cancellable {
return send(operation: operation, files: nil, completionHandler: completionHandler)
}

/// Uploads the given files with the given operation.
///
/// - Parameters:
/// - operation: The operation to send
/// - files: An array of `GraphQLFile` objects to send.
/// - completionHandler: The completion handler to execute when the request completes or errors
/// - Returns: An object that can be used to cancel an in progress request.
public func upload<Operation>(operation: Operation, files: [GraphQLFile], completionHandler: @escaping (_ response: GraphQLResponse<Operation>?, _ error: Error?) -> Void) -> Cancellable {
public func upload<Operation>(operation: Operation, files: [GraphQLFile], completionHandler: @escaping (_ result: Result<GraphQLResponse<Operation>, Error>) -> Void) -> Cancellable {
return send(operation: operation, files: files, completionHandler: completionHandler)
}

private func send<Operation>(operation: Operation, files: [GraphQLFile]?, completionHandler: @escaping (_ response: GraphQLResponse<Operation>?, _ error: Error?) -> Void) -> Cancellable {
private func send<Operation>(operation: Operation, files: [GraphQLFile]?, completionHandler: @escaping (_ results: Result<GraphQLResponse<Operation>, Error>) -> Void) -> Cancellable {
let request: URLRequest
do {
request = try self.createRequest(for: operation, files: files)
} catch {
completionHandler(nil, error)
completionHandler(.failure(error))
return EmptyCancellable()
}

Expand Down Expand Up @@ -176,7 +164,7 @@ public class HTTPNetworkTransport: NetworkTransport {
throw GraphQLHTTPResponseError(body: data, response: httpResponse, kind: .invalidResponse)
}
let response = GraphQLResponse(operation: operation, body: body)
completionHandler(response, nil)
completionHandler(.success(response))
} catch let parsingError {
self?.handleErrorOrRetry(operation: operation,
error: parsingError,
Expand All @@ -195,11 +183,11 @@ public class HTTPNetworkTransport: NetworkTransport {
error: Error,
for request: URLRequest,
response: URLResponse?,
completionHandler: @escaping (_ response: GraphQLResponse<Operation>?, _ error: Error?) -> Void) {
completionHandler: @escaping (_ result: Result<GraphQLResponse<Operation>, Error>) -> Void) {
guard
let delegate = self.delegate,
let retrier = delegate as? HTTPNetworkTransportRetryDelegate else {
completionHandler(nil, error)
completionHandler(.failure(error))
return
}

Expand All @@ -210,7 +198,7 @@ public class HTTPNetworkTransport: NetworkTransport {
response: response,
retryHandler: { [weak self] shouldRetry in
guard shouldRetry else {
completionHandler(nil, error)
completionHandler(.failure(error))
return
}

Expand Down Expand Up @@ -284,3 +272,12 @@ public class HTTPNetworkTransport: NetworkTransport {
return request
}
}

// MARK: - NetworkTransport conformance

extension HTTPNetworkTransport: NetworkTransport {

public func send<Operation>(operation: Operation, completionHandler: @escaping (_ result: Result<GraphQLResponse<Operation>, Error>) -> Void) -> Cancellable {
return send(operation: operation, files: nil, completionHandler: completionHandler)
}
}
7 changes: 3 additions & 4 deletions Sources/Apollo/NetworkTransport.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
/// A network transport is responsible for sending GraphQL operations to a server.
public protocol NetworkTransport {

/// Send a GraphQL operation to a server and return a response.
///
/// - Parameters:
/// - operation: The operation to send.
/// - completionHandler: A closure to call when a request completes.
/// - response: The response received from the server, or `nil` if an error occurred.
/// - error: An error that indicates why a request failed, or `nil` if the request was succesful.
/// - completionHandler: A closure to call when a request completes. On `success` will contain the response received from the server. On `failure` will contain the error which occurred.
/// - Returns: An object that can be used to cancel an in progress request.
func send<Operation>(operation: Operation, completionHandler: @escaping (_ response: GraphQLResponse<Operation>?, _ error: Error?) -> Void) -> Cancellable
func send<Operation>(operation: Operation, completionHandler: @escaping (_ result: Result<GraphQLResponse<Operation>, Error>) -> Void) -> Cancellable
}
20 changes: 15 additions & 5 deletions Sources/ApolloWebSocket/SplitNetworkTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,31 @@
import Apollo
#endif


public class SplitNetworkTransport: NetworkTransport {
/// A network transport that sends subscriptions using one `NetworkTransport` and other requests using another `NetworkTransport`. Ideal for sending subscriptions via a web socket but everything else via HTTP.
public class SplitNetworkTransport {
private let httpNetworkTransport: NetworkTransport
private let webSocketNetworkTransport: NetworkTransport

/// Designated initializer
///
/// - Parameters:
/// - httpNetworkTransport: A `NetworkTransport` to use for non-subscription requests. Should generally be a `HTTPNetworkTransport` or something similar.
/// - webSocketNetworkTransport: A `NetworkTransport` to use for subscription requests. Should generally be a `WebSocketTransport` or something similar.
public init(httpNetworkTransport: NetworkTransport, webSocketNetworkTransport: NetworkTransport) {
self.httpNetworkTransport = httpNetworkTransport
self.webSocketNetworkTransport = webSocketNetworkTransport
}
}

// MARK: - NetworkTransport conformance

extension SplitNetworkTransport: NetworkTransport {

public func send<Operation>(operation: Operation, completionHandler: @escaping (GraphQLResponse<Operation>?, Error?) -> Void) -> Cancellable {
public func send<Operation>(operation: Operation, completionHandler: @escaping (Result<GraphQLResponse<Operation>, Error>) -> Void) -> Cancellable {
if operation.operationType == .subscription {
return webSocketNetworkTransport.send(operation: operation, completionHandler: completionHandler)
return webSocketNetworkTransport.send(operation: operation, completionHandler: completionHandler)
} else {
return httpNetworkTransport.send(operation: operation, completionHandler: completionHandler)
return httpNetworkTransport.send(operation: operation, completionHandler: completionHandler)
}
}
}
Loading