diff --git a/Sources/Apollo/APQNetworkTransport.swift b/Sources/Apollo/APQNetworkTransport.swift index 1d8f75b43c..fd784d1e19 100644 --- a/Sources/Apollo/APQNetworkTransport.swift +++ b/Sources/Apollo/APQNetworkTransport.swift @@ -3,26 +3,30 @@ import CryptoSwift /// Wraps a NetworkTransport to perform automated persisted queries. public final class APQNetworkTransport: NetworkTransport { - fileprivate let version = 1 + fileprivate static let version = 1 + var isEnabled: Bool = true let networkTransport: NetworkTransport + let useGETForPersistedQuery: Bool - public init(networkTransport: NetworkTransport) { + public init(networkTransport: NetworkTransport, useGETForPersistedQuery: Bool = false) { self.networkTransport = networkTransport + self.useGETForPersistedQuery = useGETForPersistedQuery } public func send( operation: Operation, fetchHTTPMethod: FetchHTTPMethod, includeQuery: Bool, - extensions: GraphQLMap?, completionHandler: @escaping (GraphQLResponse?, Error?) -> Void + extensions: GraphQLMap?, + completionHandler: @escaping (GraphQLResponse?, Error?) -> Void ) -> Cancellable where Operation : GraphQLOperation { var newExtensions = extensions if isEnabled { newExtensions = (newExtensions ?? [:]).merging( [ "persistedQuery": [ - "version": version, + "version": type(of: self).version, "sha256Hash": operation.queryDocument.sha256() ] ], @@ -32,7 +36,7 @@ public final class APQNetworkTransport: NetworkTransport { return networkTransport.send( operation: operation, - fetchHTTPMethod: fetchHTTPMethod, + fetchHTTPMethod: isEnabled && useGETForPersistedQuery ? .GET : fetchHTTPMethod, includeQuery: !isEnabled, extensions: newExtensions ) { result, error in @@ -54,7 +58,6 @@ public final class APQNetworkTransport: NetworkTransport { ) } } - } fileprivate func hasErrorCode(errors: [GraphQLError], needleErrorCode: String) -> Bool { diff --git a/Sources/Apollo/ApolloClient.swift b/Sources/Apollo/ApolloClient.swift index 29c0edff7c..43d9e33947 100644 --- a/Sources/Apollo/ApolloClient.swift +++ b/Sources/Apollo/ApolloClient.swift @@ -81,7 +81,7 @@ 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: An object that can be used to cancel an in progress fetch. - @discardableResult public func fetch(query: Query, fetchHTTPMethod: FetchHTTPMethod = .POST, includeQuery: Bool = false, extensions: GraphQLMap? = nil, cachePolicy: CachePolicy = .returnCacheDataElseFetch, queue: DispatchQueue = DispatchQueue.main, resultHandler: OperationResultHandler? = nil) -> Cancellable { + @discardableResult public func fetch(query: Query, fetchHTTPMethod: FetchHTTPMethod = .POST, includeQuery: Bool = true, extensions: GraphQLMap? = nil, cachePolicy: CachePolicy = .returnCacheDataElseFetch, queue: DispatchQueue = DispatchQueue.main, resultHandler: OperationResultHandler? = nil) -> Cancellable { return _fetch(query: query, fetchHTTPMethod: fetchHTTPMethod, includeQuery: includeQuery, extensions: extensions, cachePolicy: cachePolicy, queue: queue, resultHandler: resultHandler) } @@ -137,7 +137,7 @@ 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 mutation results are available or when an error occurs. /// - Returns: An object that can be used to cancel an in progress mutation. - @discardableResult public func perform(mutation: Mutation, fetchHTTPMethod: FetchHTTPMethod = .POST, includeQuery: Bool, extensions: GraphQLMap?, queue: DispatchQueue = DispatchQueue.main, resultHandler: OperationResultHandler? = nil) -> Cancellable { + @discardableResult public func perform(mutation: Mutation, fetchHTTPMethod: FetchHTTPMethod = .POST, includeQuery: Bool = true, extensions: GraphQLMap? = nil, queue: DispatchQueue = DispatchQueue.main, resultHandler: OperationResultHandler? = nil) -> Cancellable { return _perform(mutation: mutation, fetchHTTPMethod: fetchHTTPMethod, includeQuery: includeQuery, extensions: extensions, queue: queue, resultHandler: resultHandler) } diff --git a/Sources/Apollo/HTTPNetworkTransport.swift b/Sources/Apollo/HTTPNetworkTransport.swift index 2ebcdbd1de..d95b0e97f4 100644 --- a/Sources/Apollo/HTTPNetworkTransport.swift +++ b/Sources/Apollo/HTTPNetworkTransport.swift @@ -175,7 +175,7 @@ public class HTTPNetworkTransport: NetworkTransport { includeQuery: Bool, extensions: GraphQLMap? ) -> GraphQLMap { - let commonBody: GraphQLMap = [ + var body: GraphQLMap = [ "variables": operation.variables, ] @@ -183,37 +183,34 @@ public class HTTPNetworkTransport: NetworkTransport { guard let operationIdentifier = operation.operationIdentifier else { preconditionFailure("To send operation identifiers, Apollo types must be generated with operationIdentifiers") } - return commonBody.merging(["id": operationIdentifier], uniquingKeysWith: { $1 }) + return body.merging(["id": operationIdentifier], uniquingKeysWith: { $1 }) } - let response = commonBody - .merging( - ["query": includeQuery ? operation.queryDocument : nil, "extensions": extensions], - uniquingKeysWith: { $1 } - ) - .compactMapValues { $0 } + if let extensions = extensions { + body["extensions"] = extensions + } + + if includeQuery { + body["query"] = operation.queryDocument + } - return response + return body } private func mountUrlWithQueryParamsIfNeeded(body: GraphQLMap) -> URL? { var queryStringItems = [URLQueryItem]() - if let query = body.jsonObject["query"] { - queryStringItems.append(URLQueryItem(name: "query", value: "\(query)")) + if let query = optionalValue(for: body, key: "query") { + queryStringItems.append(URLQueryItem(name: "query", value: "\(query.jsonValue)")) } - if areThereVariables(in: body), - let serializedVariables = try? serializationFormat.serialize(value: body.jsonObject["variables"]) { - queryStringItems.append( - URLQueryItem(name: "variables", value: getEncodedString(of: serializedVariables)) - ) + if let variables = optionalValue(for: body, key: "variables"), + let serializedVariables = try? serializationFormat.serialize(value: variables) { + queryStringItems.append(URLQueryItem(name: "variables", value: getEncodedString(of: serializedVariables))) } - if areThereExtensions(in: body), - let serializedExtensions = try? serializationFormat.serialize(value: body.jsonObject["extensions"]) { - queryStringItems.append( - URLQueryItem(name: "extensions", value: getEncodedString(of: serializedExtensions)) - ) + if let extensions = optionalValue(for: body, key: "extensions"), + let serializedExtensions = try? serializationFormat.serialize(value: extensions) { + queryStringItems.append(URLQueryItem(name: "extensions", value: getEncodedString(of: serializedExtensions))) } guard !queryStringItems.isEmpty, @@ -223,19 +220,13 @@ public class HTTPNetworkTransport: NetworkTransport { return URL(string: "\(self.url.absoluteString)?\(queryString)") } - - private func areThereVariables(in map: GraphQLMap) -> Bool { - if let variables = map.jsonObject["variables"], "\(variables)" != "" { - return true - } - return false - } - private func areThereExtensions(in map: GraphQLMap) -> Bool { - if let extensions = map.jsonObject["extensions"], "\(extensions)" != "" { - return true + private func optionalValue(for body: GraphQLMap, key: String) -> JSONEncodable? { + let maybeValue = body[key] + if let value = body[key] { + return value } - return false + return nil } private func getEncodedString(of data: Data) -> String { diff --git a/Tests/ApolloTestSupport/MockNetworkTransport.swift b/Tests/ApolloTestSupport/MockNetworkTransport.swift index c1d73c1620..71178b9ad1 100644 --- a/Tests/ApolloTestSupport/MockNetworkTransport.swift +++ b/Tests/ApolloTestSupport/MockNetworkTransport.swift @@ -8,7 +8,13 @@ public final class MockNetworkTransport: NetworkTransport { self.body = body } - public func send(operation: Operation, fetchHTTPMethod: FetchHTTPMethod, completionHandler: @escaping (_ response: GraphQLResponse?, _ error: Error?) -> Void) -> Cancellable { + public func send( + operation: Operation, + fetchHTTPMethod: FetchHTTPMethod, + includeQuery: Bool, + extensions: GraphQLMap?, + completionHandler: @escaping (_ response: GraphQLResponse?, _ error: Error?) -> Void + ) -> Cancellable { DispatchQueue.global(qos: .default).async { completionHandler(GraphQLResponse(operation: operation, body: self.body), nil) }