Skip to content

Instantly share code, notes, and snippets.

@cerupcat
Created November 5, 2017 20:20
Show Gist options
  • Save cerupcat/e3b04a523f023813db3f6aad8a667201 to your computer and use it in GitHub Desktop.
Save cerupcat/e3b04a523f023813db3f6aad8a667201 to your computer and use it in GitHub Desktop.
Apollo HTTPNetworkClient with Mock
//
// AuthHTTPNetworkTransport.swift
//
// Created by Seth Sandler on 9/13/17.
// Copyright © 2017 Workpop. All rights reserved.
//
import Foundation
import Apollo
/// Based on HTTPNetworkTransport from Apollo to add Authentication header dynamically
public class AuthHTTPNetworkTransport: NetworkTransport {
enum GQLError: Error {
case errorResponse(body: Data?, response: HTTPURLResponse)
case invalidResponse(body: Data?, response: HTTPURLResponse)
}
let url: URL
let session: URLSession
let serializationFormat = JSONSerializationFormat.self
public var useMockData: Bool
public init(url: URL, configuration: URLSessionConfiguration = URLSessionConfiguration.default, sendOperationIdentifiers: Bool = false) {
self.url = url
self.session = URLSession(configuration: configuration)
self.sendOperationIdentifiers = sendOperationIdentifiers
self.useMockData = false
}
fileprivate func handleRequest<Operation: GraphQLOperation>(operation: Operation, completionHandler: @escaping (GraphQLResponse<Operation>?, Error?) -> Void) -> Cancellable {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
// set meteor resume token
// if let currentResumeToken = MeteorClient().resumeToken() {
// request.setValue(currentResumeToken, forHTTPHeaderField: "meteor-login-token")
// }
let body = requestBody(for: operation)
request.httpBody = try! serializationFormat.serialize(value: body)
let task = session.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil {
completionHandler(nil, error)
return
}
guard let httpResponse = response as? HTTPURLResponse else {
fatalError("Response should be an HTTPURLResponse")
}
if (httpResponse.statusCode != 200) {
completionHandler(nil, GQLError.errorResponse(body: data, response: httpResponse))
return
}
guard let data = data else {
completionHandler(nil, GQLError.invalidResponse(body: nil, response: httpResponse))
return
}
do {
guard let body = try self.serializationFormat.deserialize(data: data) as? JSONObject else {
completionHandler(nil, GQLError.invalidResponse(body: data, response: httpResponse))
return
}
let response = GraphQLResponse(operation: operation, body: body)
completionHandler(response, nil)
} catch {
completionHandler(nil, error)
}
}
task.resume()
return task
}
public func send<Operation: GraphQLOperation>(operation: Operation, completionHandler: @escaping (GraphQLResponse<Operation>?, Error?) -> Void) -> Cancellable {
// if using mock data
if useMockData {
// get mock json file
if let mockJSON = getMockJSON(forOperationString: type(of: operation).operationString, variables: operation.variables) {
DispatchQueue.global(qos: .default).async {
// skip network call and return mock json file
completionHandler(GraphQLResponse(operation: operation, body: mockJSON), nil)
}
return MockTask()
} else {
// send request over network
return handleRequest(operation: operation, completionHandler: completionHandler)
}
} else {
// send request over network
return handleRequest(operation: operation, completionHandler: completionHandler)
}
}
private let sendOperationIdentifiers: Bool
private func requestBody<Operation: GraphQLOperation>(for operation: Operation) -> GraphQLMap {
if sendOperationIdentifiers {
guard let operationIdentifier = type(of: operation).operationIdentifier else {
preconditionFailure("To send operation identifiers, Apollo types must be generated with operationIdentifiers")
}
return ["id": operationIdentifier, "variables": operation.variables]
}
return ["query": type(of: operation).requestString, "variables": operation.variables]
}
// MARK: - Mock Data
fileprivate func getMockJSON(forOperationString operationString: String, variables: GraphQLMap?) -> JSONObject? {
var body: JSONObject?
switch operationString {
// if the operation string mattaches GraphQLQuery.operationString
case ExampleQuery.operationString:
if let json = "exampleQuery_1".loadJsonSchema() {
body = json
}
case ExampleQuery2.operationString:
guard let exampleId: String = variables!["exampleId"].jsonValue as? String else {
break
}
if exampleId == "1" {
if let json = "exampleQuery2_1".loadJsonSchema() {
body = json
}
} else if exampleId == "2" {
if let json = "exampleQuery2_2".loadJsonSchema() {
body = json
}
}
default:
break
}
// return json body
return body
}
private final class MockTask: Cancellable {
func cancel() {
}
}
}
public extension String {
func loadJsonSchema() -> JSONObject? {
// loading from cocoapod bundle we use
let bundle = Bundle(identifier: "org.cocoapods.NameOfCocoapod")
if let url = bundle?.url(forResource: self, withExtension: "json") {
if let data = NSData(contentsOf: url) {
do {
let jsonObject = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as? JSONObject
return jsonObject
} catch {
print("Error!! Unable to parse \(self).json")
}
}
print("Error!! Unable to load \(self).json")
}
return nil
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment