From 40d7e5116da657a74b6c5b25884b37a96d0c6f5f Mon Sep 17 00:00:00 2001 From: Ellen Shapiro Date: Sun, 13 Sep 2020 21:10:36 -0500 Subject: [PATCH 1/6] Make JSONRequest an open class to allow subclassing --- Sources/Apollo/JSONRequest.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Apollo/JSONRequest.swift b/Sources/Apollo/JSONRequest.swift index 59e7ec2a68..5374bfa79d 100644 --- a/Sources/Apollo/JSONRequest.swift +++ b/Sources/Apollo/JSONRequest.swift @@ -1,7 +1,7 @@ import Foundation /// A request which sends JSON related to a GraphQL operation. -public class JSONRequest: HTTPRequest { +open class JSONRequest: HTTPRequest { public let requestCreator: RequestCreator @@ -52,11 +52,11 @@ public class JSONRequest: HTTPRequest { cachePolicy: cachePolicy) } - public var sendOperationIdentifier: Bool { + open var sendOperationIdentifier: Bool { self.operation.operationIdentifier != nil } - public override func toURLRequest() throws -> URLRequest { + open override func toURLRequest() throws -> URLRequest { var request = try super.toURLRequest() let useGetMethod: Bool From c64e8c8fb1b6ed5ba0d868a8de0fbc007e6c43bc Mon Sep 17 00:00:00 2001 From: Ellen Shapiro Date: Sun, 13 Sep 2020 21:12:24 -0500 Subject: [PATCH 2/6] Fix docs from `ApolloInterceptorProvider` -> `InterceptorProvider` --- docs/source/initialization.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/initialization.md b/docs/source/initialization.md index 5ac1eb1f1a..221acb080b 100644 --- a/docs/source/initialization.md +++ b/docs/source/initialization.md @@ -59,18 +59,18 @@ The chain also includes a `retry` mechanism, which will go all the way back to t **IMPORTANT**: Do not call `retry` blindly. If your server is returning 500s or if the user has no internet, this will create an infinite loop of requests that are retrying (especially if you're not using something like the `MaxRetryInterceptor` to limit how many retries are made). This **will** kill your user's battery, and might also run up the bill on their data plan. Make sure to only request a retry when there's something your code can actually do about the problem! -In the `RequestChainNetworkTransport`, each request creates an individual request chain, and uses an `ApolloInterceptorProvider` +In the `RequestChainNetworkTransport`, each request creates an individual request chain, and uses an `InterceptorProvider` -### Setting up `ApolloInterceptor` chains with `ApolloInterceptorProvider` +### Setting up `ApolloInterceptor` chains with `InterceptorProvider` -Every operation sent through a `RequestChainNetworkTransport` will be passed into an `ApolloInterceptorProvider` before going to the network. This protocol creates an array of interceptors for use by a single request chain based on the provided operation. +Every operation sent through a `RequestChainNetworkTransport` will be passed into an `InterceptorProvider` before going to the network. This protocol creates an array of interceptors for use by a single request chain based on the provided operation. There are two default implementations for this protocol provided: - `LegacyInterceptorProvider` works with our existing parsing and caching system and tries to replicate the experience of using the old `HTTPNetworkTransport` as closely as possible. It takes a `URLSessionClient` and an `ApolloStore` to pass into the interceptors it uses. - `CodableInterceptorProvider` is a **work in progress**, which is going to be for use with our [Swift Codegen Rewrite](https://github.com/apollographql/apollo-ios/projects/2), (which, I swear, will eventually be finished). It is not suitable for use at this time. It takes a `URLSessionClient`, a `FlexibleDecoder` (something can decode anything that conforms to `Decodable`). It does not support caching yet. -If you wish to make your own `ApolloInterceptorProvider` instead of using the provided one, you can take advantage of several interceptors that are included in the library: +If you wish to make your own `InterceptorProvider` instead of using the provided one, you can take advantage of several interceptors that are included in the library: #### Pre-network - `MaxRetryInterceptor` checks to make sure a query has not been tried more than a maximum number of times. From 7c71edaa481d20528805f967b684fd17cbfa7309 Mon Sep 17 00:00:00 2001 From: Ellen Shapiro Date: Thu, 17 Sep 2020 15:27:05 -0500 Subject: [PATCH 3/6] Add test to make sure when `cancel` is called cancellable interceptors are also cancelled --- Apollo.xcodeproj/project.pbxproj | 6 +++- .../BlindRetryingTestInterceptor.swift | 8 ++++- .../CancellationHandlingInterceptor.swift | 35 +++++++++++++++++++ Tests/ApolloTests/RequestChainTests.swift | 29 +++++++++++++++ 4 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 Tests/ApolloTests/CancellationHandlingInterceptor.swift diff --git a/Apollo.xcodeproj/project.pbxproj b/Apollo.xcodeproj/project.pbxproj index 640c87a2b5..2b5d78307a 100644 --- a/Apollo.xcodeproj/project.pbxproj +++ b/Apollo.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ 9B260C04245A090600562176 /* RequestChainNetworkTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B260C03245A090600562176 /* RequestChainNetworkTransport.swift */; }; 9B260C08245A437400562176 /* InterceptorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B260C07245A437400562176 /* InterceptorProvider.swift */; }; 9B260C0A245A532500562176 /* LegacyParsingInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B260C09245A532500562176 /* LegacyParsingInterceptor.swift */; }; + 9B2B66F42513FAFE00B53ABF /* CancellationHandlingInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2B66F32513FAFE00B53ABF /* CancellationHandlingInterceptor.swift */; }; 9B2DFBBF24E1FA1A00ED3AE6 /* Apollo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9FC750441D2A532C00458D91 /* Apollo.framework */; }; 9B2DFBC024E1FA1A00ED3AE6 /* Apollo.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9FC750441D2A532C00458D91 /* Apollo.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9B2DFBC724E1FA4800ED3AE6 /* UploadAPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B2DFBC524E1FA3E00ED3AE6 /* UploadAPI.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -491,6 +492,7 @@ 9B260C03245A090600562176 /* RequestChainNetworkTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestChainNetworkTransport.swift; sourceTree = ""; }; 9B260C07245A437400562176 /* InterceptorProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterceptorProvider.swift; sourceTree = ""; }; 9B260C09245A532500562176 /* LegacyParsingInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyParsingInterceptor.swift; sourceTree = ""; }; + 9B2B66F32513FAFE00B53ABF /* CancellationHandlingInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancellationHandlingInterceptor.swift; sourceTree = ""; }; 9B2DFBB624E1FA0D00ED3AE6 /* UploadAPI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = UploadAPI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9B2DFBC524E1FA3E00ED3AE6 /* UploadAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UploadAPI.h; sourceTree = ""; }; 9B2DFBC624E1FA3E00ED3AE6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -627,10 +629,10 @@ 9BAEEC14234C132600808306 /* CLIExtractorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLIExtractorTests.swift; sourceTree = ""; }; 9BAEEC16234C275600808306 /* ApolloSchemaTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApolloSchemaTests.swift; sourceTree = ""; }; 9BAEEC18234C297800808306 /* ApolloCodegenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApolloCodegenTests.swift; sourceTree = ""; }; + 9BB1DAC624A66B2500396235 /* ApolloMacPlayground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = ApolloMacPlayground.playground; path = Playgrounds/ApolloMacPlayground.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 9BC139A224EDCA4400876D29 /* InterceptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterceptorTests.swift; sourceTree = ""; }; 9BC139A524EDCAD900876D29 /* BlindRetryingTestInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlindRetryingTestInterceptor.swift; sourceTree = ""; }; 9BC139A724EDCE4F00876D29 /* RetryToCountThenSucceedInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryToCountThenSucceedInterceptor.swift; sourceTree = ""; }; - 9BB1DAC624A66B2500396235 /* ApolloMacPlayground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = ApolloMacPlayground.playground; path = Playgrounds/ApolloMacPlayground.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 9BC2D9CE233C3531007BD083 /* Apollo-Target-ApolloCodegen.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Apollo-Target-ApolloCodegen.xcconfig"; sourceTree = ""; }; 9BC2D9D1233C6DC0007BD083 /* Basher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Basher.swift; sourceTree = ""; }; 9BC742AB24CFB2FF0029282C /* ApolloErrorInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApolloErrorInterceptor.swift; sourceTree = ""; }; @@ -932,6 +934,7 @@ 9B4F4540244A2A9200C2CF7D /* HTTPBinAPI.swift */, 9BC139A524EDCAD900876D29 /* BlindRetryingTestInterceptor.swift */, 9BC139A724EDCE4F00876D29 /* RetryToCountThenSucceedInterceptor.swift */, + 9B2B66F32513FAFE00B53ABF /* CancellationHandlingInterceptor.swift */, ); name = TestHelpers; sourceTree = ""; @@ -2526,6 +2529,7 @@ 9FE1C6E71E634C8D00C02284 /* PromiseTests.swift in Sources */, 9B64F6762354D219002D1BB5 /* URL+QueryDict.swift in Sources */, 9FADC8541E6B86D900C677E6 /* DataLoaderTests.swift in Sources */, + 9B2B66F42513FAFE00B53ABF /* CancellationHandlingInterceptor.swift in Sources */, 9B21FD772422C8CC00998B5C /* TestFileHelper.swift in Sources */, 9BC139A624EDCAD900876D29 /* BlindRetryingTestInterceptor.swift in Sources */, 9B96500A24BE62B7003C29C0 /* RequestChainTests.swift in Sources */, diff --git a/Tests/ApolloTests/BlindRetryingTestInterceptor.swift b/Tests/ApolloTests/BlindRetryingTestInterceptor.swift index 6e89f372be..5e03b9b434 100644 --- a/Tests/ApolloTests/BlindRetryingTestInterceptor.swift +++ b/Tests/ApolloTests/BlindRetryingTestInterceptor.swift @@ -12,7 +12,8 @@ import Apollo // An interceptor which blindly retries every time it receives a request. class BlindRetryingTestInterceptor: ApolloInterceptor { var hitCount = 0 - + private(set) var hasBeenCancelled = false + func interceptAsync( chain: RequestChain, request: HTTPRequest, @@ -22,4 +23,9 @@ class BlindRetryingTestInterceptor: ApolloInterceptor { chain.retry(request: request, completion: completion) } + + // Purposely not adhering to `Cancellable` here to make sure non `Cancellable` interceptors don't have this called. + func cancel() { + self.hasBeenCancelled = true + } } diff --git a/Tests/ApolloTests/CancellationHandlingInterceptor.swift b/Tests/ApolloTests/CancellationHandlingInterceptor.swift new file mode 100644 index 0000000000..726ce36349 --- /dev/null +++ b/Tests/ApolloTests/CancellationHandlingInterceptor.swift @@ -0,0 +1,35 @@ +// +// CancellationHandlingInterceptor.swift +// ApolloTests +// +// Created by Ellen Shapiro on 9/17/20. +// Copyright © 2020 Apollo GraphQL. All rights reserved. +// + +import Foundation +import Apollo + +class CancellationHandlingInterceptor: ApolloInterceptor, Cancellable { + private(set) var hasBeenCancelled = false + + func interceptAsync( + chain: RequestChain, + request: HTTPRequest, + response: HTTPResponse?, + completion: @escaping (Result, Error>) -> Void) { + + guard !self.hasBeenCancelled else { + return + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + chain.proceedAsync(request: request, + response: response, + completion: completion) + } + } + + func cancel() { + self.hasBeenCancelled = true + } +} diff --git a/Tests/ApolloTests/RequestChainTests.swift b/Tests/ApolloTests/RequestChainTests.swift index 64d8e4cbd2..1daeea7381 100644 --- a/Tests/ApolloTests/RequestChainTests.swift +++ b/Tests/ApolloTests/RequestChainTests.swift @@ -105,4 +105,33 @@ class RequestChainTests: XCTestCase { self.wait(for: [expectation], timeout: 1) } + + func testCancellingChainCallsCancelOnInterceptorsWhichImplementCancellableAndNotOnOnesThatDont() { + class TestProvider: InterceptorProvider { + let cancellationInterceptor = CancellationHandlingInterceptor() + let retryInterceptor = BlindRetryingTestInterceptor() + + func interceptors(for operation: Operation) -> [ApolloInterceptor] { + [ + self.cancellationInterceptor, + self.retryInterceptor + ] + } + } + + let provider = TestProvider() + let transport = RequestChainNetworkTransport(interceptorProvider: provider, + endpointURL: TestURL.mockServer.url) + let expectation = self.expectation(description: "Send succeeded") + expectation.isInverted = true + let cancellable = transport.send(operation: HeroNameQuery()) { _ in + XCTFail("This should not have gone through") + expectation.fulfill() + } + + cancellable.cancel() + XCTAssertTrue(provider.cancellationInterceptor.hasBeenCancelled) + XCTAssertFalse(provider.retryInterceptor.hasBeenCancelled) + self.wait(for: [expectation], timeout: 2) + } } From 8c3c3b602413b705ccbe60ee7236a6d08ff9989a Mon Sep 17 00:00:00 2001 From: Ellen Shapiro Date: Thu, 17 Sep 2020 15:59:02 -0500 Subject: [PATCH 4/6] Add and test method to interceptor provider to allow providing an additional error interceptor (with default provision of nil) --- Sources/Apollo/InterceptorProvider.swift | 14 ++++ .../Apollo/RequestChainNetworkTransport.swift | 1 + Tests/ApolloTests/RequestChainTests.swift | 70 +++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/Sources/Apollo/InterceptorProvider.swift b/Sources/Apollo/InterceptorProvider.swift index 9ad4856078..44a1e2502a 100644 --- a/Sources/Apollo/InterceptorProvider.swift +++ b/Sources/Apollo/InterceptorProvider.swift @@ -9,6 +9,20 @@ public protocol InterceptorProvider { /// /// - Parameter operation: The operation to provide interceptors for func interceptors(for operation: Operation) -> [ApolloInterceptor] + + /// Provides an additional error interceptor for any additional handling of errors + /// before returning to the UI, such as logging. + /// - Parameter operation: The oper + func additionalErrorInterceptor(for operation: Operation) -> ApolloErrorInterceptor? +} + +/// MARK: - Default Implementation + +public extension InterceptorProvider { + + func additionalErrorInterceptor(for operation: Operation) -> ApolloErrorInterceptor? { + return nil + } } // MARK: - Default implementation for typescript codegen diff --git a/Sources/Apollo/RequestChainNetworkTransport.swift b/Sources/Apollo/RequestChainNetworkTransport.swift index 3c8d9e5f9a..196e26761b 100644 --- a/Sources/Apollo/RequestChainNetworkTransport.swift +++ b/Sources/Apollo/RequestChainNetworkTransport.swift @@ -73,6 +73,7 @@ public class RequestChainNetworkTransport: NetworkTransport { let interceptors = self.interceptorProvider.interceptors(for: operation) let chain = RequestChain(interceptors: interceptors, callbackQueue: callbackQueue) + chain.additionalErrorHandler = self.interceptorProvider.additionalErrorInterceptor(for: operation) let request = self.constructJSONRequest(for: operation, cachePolicy: cachePolicy, contextIdentifier: contextIdentifier) diff --git a/Tests/ApolloTests/RequestChainTests.swift b/Tests/ApolloTests/RequestChainTests.swift index 1daeea7381..72e131e84d 100644 --- a/Tests/ApolloTests/RequestChainTests.swift +++ b/Tests/ApolloTests/RequestChainTests.swift @@ -134,4 +134,74 @@ class RequestChainTests: XCTestCase { XCTAssertFalse(provider.retryInterceptor.hasBeenCancelled) self.wait(for: [expectation], timeout: 2) } + + func testErrorInterceptorGetsCalledAfterAnErrorIsReceived() { + class ErrorInterceptor: ApolloErrorInterceptor { + var error: Error? = nil + + func handleErrorAsync( + error: Error, + chain: RequestChain, + request: HTTPRequest, + response: HTTPResponse?, + completion: @escaping (Result, Error>) -> Void) { + + self.error = error + completion(.failure(error)) + } + } + + class TestProvider: InterceptorProvider { + let errorInterceptor = ErrorInterceptor() + func interceptors(for operation: Operation) -> [ApolloInterceptor] { + return [ + // An interceptor which will error without a response + AutomaticPersistedQueryInterceptor() + ] + } + + func additionalErrorInterceptor(for operation: Operation) -> ApolloErrorInterceptor? { + return self.errorInterceptor + } + } + + let provider = TestProvider() + let transport = RequestChainNetworkTransport(interceptorProvider: provider, + endpointURL: TestURL.mockServer.url, + autoPersistQueries: true) + + let expectation = self.expectation(description: "Hero name query complete") + _ = transport.send(operation: HeroNameQuery()) { result in + defer { + expectation.fulfill() + } + switch result { + case .success: + XCTFail("This should not have succeeded") + case .failure(let error): + switch error { + case AutomaticPersistedQueryInterceptor.APQError.noParsedResponse: + // This is what we want. + break + default: + XCTFail("Unexpected error: \(error)") + } + } + } + + self.wait(for: [expectation], timeout: 1) + + switch provider.errorInterceptor.error { + case .some(let error): + switch error { + case AutomaticPersistedQueryInterceptor.APQError.noParsedResponse: + // Again, this is what we expect. + break + default: + XCTFail("Unexpected error on the interceptor: \(error)") + } + case .none: + XCTFail("Error interceptor did not receive an error!") + } + } } From 6c441f9779625db916a5dceda2a7b9b75a9fca5d Mon Sep 17 00:00:00 2001 From: Ellen Shapiro Date: Thu, 17 Sep 2020 16:18:56 -0500 Subject: [PATCH 5/6] Make the in-memory cache a default Cache parameter for the store, and the default store the default Store the default store parameter for Legacy interceptors --- Sources/Apollo/ApolloStore.swift | 4 +-- Sources/Apollo/InterceptorProvider.swift | 4 +-- .../AutomaticPersistedQueriesTests.swift | 33 +++++++------------ Tests/ApolloTests/RequestChainTests.swift | 4 +-- Tests/ApolloTests/UploadTests.swift | 3 +- .../SplitNetworkTransportTests.swift | 3 +- .../StarWarsSubscriptionTests.swift | 3 +- 7 files changed, 19 insertions(+), 35 deletions(-) diff --git a/Sources/Apollo/ApolloStore.swift b/Sources/Apollo/ApolloStore.swift index 1cc551a3a8..93f1fd7382 100644 --- a/Sources/Apollo/ApolloStore.swift +++ b/Sources/Apollo/ApolloStore.swift @@ -44,8 +44,8 @@ public final class ApolloStore { /// Designated initializer /// - /// - Parameter cache: An instance of `normalizedCache` to use to cache results. - public init(cache: NormalizedCache) { + /// - Parameter cache: An instance of `normalizedCache` to use to cache results. Defaults to an `InMemoryNormalizedCache`. + public init(cache: NormalizedCache = InMemoryNormalizedCache()) { self.cache = cache queue = DispatchQueue(label: "com.apollographql.ApolloStore", attributes: .concurrent) } diff --git a/Sources/Apollo/InterceptorProvider.swift b/Sources/Apollo/InterceptorProvider.swift index 44a1e2502a..d480eadf63 100644 --- a/Sources/Apollo/InterceptorProvider.swift +++ b/Sources/Apollo/InterceptorProvider.swift @@ -39,10 +39,10 @@ open class LegacyInterceptorProvider: InterceptorProvider { /// - Parameters: /// - client: The `URLSessionClient` to use. Defaults to the default setup. /// - shouldInvalidateClientOnDeinit: If the passed-in client should be invalidated when this interceptor provider is deinitialized. If you are recreating the `URLSessionClient` every time you create a new provider, you should do this to prevent memory leaks. Defaults to true, since by default we provide a `URLSessionClient` to new instances. - /// - store: The `ApolloStore` to use when reading from or writing to the cache. + /// - store: The `ApolloStore` to use when reading from or writing to the cache. Defaults to the default initializer for ApolloStore. public init(client: URLSessionClient = URLSessionClient(), shouldInvalidateClientOnDeinit: Bool = true, - store: ApolloStore) { + store: ApolloStore = ApolloStore()) { self.client = client self.shouldInvalidateClientOnDeinit = shouldInvalidateClientOnDeinit self.store = store diff --git a/Tests/ApolloTests/AutomaticPersistedQueriesTests.swift b/Tests/ApolloTests/AutomaticPersistedQueriesTests.swift index a3c55ae0e8..a591d5807a 100644 --- a/Tests/ApolloTests/AutomaticPersistedQueriesTests.swift +++ b/Tests/ApolloTests/AutomaticPersistedQueriesTests.swift @@ -231,8 +231,7 @@ class AutomaticPersistedQueriesTests: XCTestCase { func testRequestBody() throws { let mockClient = MockURLSessionClient() - let store = ApolloStore(cache: InMemoryNormalizedCache()) - let provider = LegacyInterceptorProvider(client: mockClient, store: store) + let provider = LegacyInterceptorProvider(client: mockClient) let network = RequestChainNetworkTransport(interceptorProvider: provider, endpointURL: self.endpoint) @@ -257,8 +256,7 @@ class AutomaticPersistedQueriesTests: XCTestCase { func testRequestBodyWithVariable() throws { let mockClient = MockURLSessionClient() - let store = ApolloStore(cache: InMemoryNormalizedCache()) - let provider = LegacyInterceptorProvider(client: mockClient, store: store) + let provider = LegacyInterceptorProvider(client: mockClient) let network = RequestChainNetworkTransport(interceptorProvider: provider, endpointURL: self.endpoint) @@ -283,8 +281,7 @@ class AutomaticPersistedQueriesTests: XCTestCase { func testRequestBodyForAPQsWithVariable() throws { let mockClient = MockURLSessionClient() - let store = ApolloStore(cache: InMemoryNormalizedCache()) - let provider = LegacyInterceptorProvider(client: mockClient, store: store) + let provider = LegacyInterceptorProvider(client: mockClient) let network = RequestChainNetworkTransport(interceptorProvider: provider, endpointURL: self.endpoint, autoPersistQueries: true) @@ -310,8 +307,7 @@ class AutomaticPersistedQueriesTests: XCTestCase { func testMutationRequestBodyForAPQs() throws { let mockClient = MockURLSessionClient() - let store = ApolloStore(cache: InMemoryNormalizedCache()) - let provider = LegacyInterceptorProvider(client: mockClient, store: store) + let provider = LegacyInterceptorProvider(client: mockClient) let network = RequestChainNetworkTransport(interceptorProvider: provider, endpointURL: self.endpoint, autoPersistQueries: true) @@ -337,8 +333,7 @@ class AutomaticPersistedQueriesTests: XCTestCase { func testQueryStringForAPQsUseGetMethod() throws { let mockClient = MockURLSessionClient() - let store = ApolloStore(cache: InMemoryNormalizedCache()) - let provider = LegacyInterceptorProvider(client: mockClient, store: store) + let provider = LegacyInterceptorProvider(client: mockClient) let network = RequestChainNetworkTransport(interceptorProvider: provider, endpointURL: self.endpoint, autoPersistQueries: true, @@ -363,8 +358,7 @@ class AutomaticPersistedQueriesTests: XCTestCase { func testQueryStringForAPQsUseGetMethodWithVariable() throws { let mockClient = MockURLSessionClient() - let store = ApolloStore(cache: InMemoryNormalizedCache()) - let provider = LegacyInterceptorProvider(client: mockClient, store: store) + let provider = LegacyInterceptorProvider(client: mockClient) let network = RequestChainNetworkTransport(interceptorProvider: provider, endpointURL: self.endpoint, autoPersistQueries: true, @@ -391,8 +385,7 @@ class AutomaticPersistedQueriesTests: XCTestCase { func testUseGETForQueriesRequest() throws { let mockClient = MockURLSessionClient() - let store = ApolloStore(cache: InMemoryNormalizedCache()) - let provider = LegacyInterceptorProvider(client: mockClient, store: store) + let provider = LegacyInterceptorProvider(client: mockClient) let network = RequestChainNetworkTransport(interceptorProvider: provider, endpointURL: self.endpoint, useGETForQueries: true) @@ -418,8 +411,7 @@ class AutomaticPersistedQueriesTests: XCTestCase { func testNotUseGETForQueriesRequest() throws { let mockClient = MockURLSessionClient() - let store = ApolloStore(cache: InMemoryNormalizedCache()) - let provider = LegacyInterceptorProvider(client: mockClient, store: store) + let provider = LegacyInterceptorProvider(client: mockClient) let network = RequestChainNetworkTransport(interceptorProvider: provider, endpointURL: self.endpoint) @@ -444,8 +436,7 @@ class AutomaticPersistedQueriesTests: XCTestCase { func testNotUseGETForQueriesAPQsRequest() throws { let mockClient = MockURLSessionClient() - let store = ApolloStore(cache: InMemoryNormalizedCache()) - let provider = LegacyInterceptorProvider(client: mockClient, store: store) + let provider = LegacyInterceptorProvider(client: mockClient) let network = RequestChainNetworkTransport(interceptorProvider: provider, endpointURL: self.endpoint, autoPersistQueries: true) @@ -471,8 +462,7 @@ class AutomaticPersistedQueriesTests: XCTestCase { func testUseGETForQueriesAPQsRequest() throws { let mockClient = MockURLSessionClient() - let store = ApolloStore(cache: InMemoryNormalizedCache()) - let provider = LegacyInterceptorProvider(client: mockClient, store: store) + let provider = LegacyInterceptorProvider(client: mockClient) let network = RequestChainNetworkTransport(interceptorProvider: provider, endpointURL: self.endpoint, autoPersistQueries: true, @@ -499,8 +489,7 @@ class AutomaticPersistedQueriesTests: XCTestCase { func testNotUseGETForQueriesAPQsGETRequest() throws { let mockClient = MockURLSessionClient() - let store = ApolloStore(cache: InMemoryNormalizedCache()) - let provider = LegacyInterceptorProvider(client: mockClient, store: store) + let provider = LegacyInterceptorProvider(client: mockClient) let network = RequestChainNetworkTransport(interceptorProvider: provider, endpointURL: self.endpoint, autoPersistQueries: true, diff --git a/Tests/ApolloTests/RequestChainTests.swift b/Tests/ApolloTests/RequestChainTests.swift index 72e131e84d..4f8dbb9449 100644 --- a/Tests/ApolloTests/RequestChainTests.swift +++ b/Tests/ApolloTests/RequestChainTests.swift @@ -15,9 +15,7 @@ class RequestChainTests: XCTestCase { lazy var legacyClient: ApolloClient = { let url = TestURL.starWarsServer.url - - let store = ApolloStore(cache: InMemoryNormalizedCache()) - let provider = LegacyInterceptorProvider(store: store) + let provider = LegacyInterceptorProvider() let transport = RequestChainNetworkTransport(interceptorProvider: provider, endpointURL: url) diff --git a/Tests/ApolloTests/UploadTests.swift b/Tests/ApolloTests/UploadTests.swift index 58a0ad7448..bdb961cbf0 100644 --- a/Tests/ApolloTests/UploadTests.swift +++ b/Tests/ApolloTests/UploadTests.swift @@ -8,8 +8,7 @@ class UploadTests: XCTestCase { let uploadClientURL = TestURL.uploadServer.url lazy var client: ApolloClient = { - let store = ApolloStore(cache: InMemoryNormalizedCache()) - let provider = LegacyInterceptorProvider(store: store) + let provider = LegacyInterceptorProvider() let transport = RequestChainNetworkTransport(interceptorProvider: provider, endpointURL: self.uploadClientURL) diff --git a/Tests/ApolloWebsocketTests/SplitNetworkTransportTests.swift b/Tests/ApolloWebsocketTests/SplitNetworkTransportTests.swift index 1b3ed4aced..83a72c79f7 100644 --- a/Tests/ApolloWebsocketTests/SplitNetworkTransportTests.swift +++ b/Tests/ApolloWebsocketTests/SplitNetworkTransportTests.swift @@ -20,9 +20,8 @@ class SplitNetworkTransportTests: XCTestCase { private let webSocketVersion = "TestWebSocketTransportVersion" private lazy var mockTransport: MockNetworkTransport = { - let store = ApolloStore(cache: InMemoryNormalizedCache()) let transport = MockNetworkTransport(body: JSONObject(), - store: store) + store: ApolloStore()) transport.clientName = self.mockTransportName transport.clientVersion = self.mockTransportVersion diff --git a/Tests/ApolloWebsocketTests/StarWarsSubscriptionTests.swift b/Tests/ApolloWebsocketTests/StarWarsSubscriptionTests.swift index 028ffc32d0..8c0451c44c 100644 --- a/Tests/ApolloWebsocketTests/StarWarsSubscriptionTests.swift +++ b/Tests/ApolloWebsocketTests/StarWarsSubscriptionTests.swift @@ -416,8 +416,7 @@ class StarWarsSubscriptionTests: XCTestCase { let reviewMutation = CreateAwesomeReviewMutation() // Send the mutations via a separate transport so they can still be sent when the websocket is disconnected - let store = ApolloStore(cache: InMemoryNormalizedCache()) - let interceptorProvider = LegacyInterceptorProvider(store: store) + let interceptorProvider = LegacyInterceptorProvider() let alternateTransport = RequestChainNetworkTransport(interceptorProvider: interceptorProvider, endpointURL: TestURL.starWarsServer.url) let alternateClient = ApolloClient(networkTransport: alternateTransport) From 69da90a6ac0223b6e640f19b4eb4d2533bcaecc8 Mon Sep 17 00:00:00 2001 From: Ellen Shapiro Date: Thu, 17 Sep 2020 16:21:59 -0500 Subject: [PATCH 6/6] update SQLite and Subscription examples in playground to use new networking stack --- .../SQLiteCache.xcplaygroundpage/Contents.swift | 12 ++++++------ .../Subscriptions.xcplaygroundpage/Contents.swift | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Playgrounds/ApolloMacPlayground.playground/Pages/SQLiteCache.xcplaygroundpage/Contents.swift b/Playgrounds/ApolloMacPlayground.playground/Pages/SQLiteCache.xcplaygroundpage/Contents.swift index e7f7600311..c9c08c10f6 100644 --- a/Playgrounds/ApolloMacPlayground.playground/Pages/SQLiteCache.xcplaygroundpage/Contents.swift +++ b/Playgrounds/ApolloMacPlayground.playground/Pages/SQLiteCache.xcplaygroundpage/Contents.swift @@ -6,10 +6,6 @@ import PlaygroundSupport //: # Setting up a client with a SQLite cache -//: First, you'll need to set up a network transport, since you will also need that to set up the client: -let serverURL = URL(string: "http://localhost:8080/graphql")! -let networkTransport = HTTPNetworkTransport(url: serverURL) - //: You'll need to make sure you import the ApolloSQLite library IF you are not using CocoaPods (CocoaPods will automatically flatten everything down to a single Apollo import): import ApolloSQLite @@ -26,12 +22,16 @@ let sqliteCache = try SQLiteNormalizedCache(fileURL: sqliteFileURL) //: And then instantiate an instance of `ApolloStore` with the cache you've just created: let store = ApolloStore(cache: sqliteCache) +//: Next, you'll need to set up a network transport, since you will also need that to set up the client: +let serverURL = URL(string: "http://localhost:8080/graphql")! +let networkTransport = RequestChainNetworkTransport(interceptorProvider: LegacyInterceptorProvider(store: store), endpointURL: serverURL) + //: Finally, pass that into your `ApolloClient` initializer, and you're now set up to use the SQLite cache for persistent storage: let apolloClient = ApolloClient(networkTransport: networkTransport, store: store) - -//: Now, let's test +//: Now, let's test it out against the Star Wars API! import StarWarsAPI + let query = HeroDetailsQuery(episode: .newhope) apolloClient.fetch(query: query) { result in // This is the outer Result, which has either a `GraphQLResult` or an `Error` diff --git a/Playgrounds/ApolloMacPlayground.playground/Pages/Subscriptions.xcplaygroundpage/Contents.swift b/Playgrounds/ApolloMacPlayground.playground/Pages/Subscriptions.xcplaygroundpage/Contents.swift index a188659b7b..4e274452c7 100644 --- a/Playgrounds/ApolloMacPlayground.playground/Pages/Subscriptions.xcplaygroundpage/Contents.swift +++ b/Playgrounds/ApolloMacPlayground.playground/Pages/Subscriptions.xcplaygroundpage/Contents.swift @@ -17,15 +17,15 @@ Your web backend must declare support for subscriptions in the Schema just like To use subscriptions, you need to have a `NetworkTransport` implementation which supports them. Fortunately, with the `ApolloWebSocket` package, there are two! -The first is the `WebSocketTransport`, which works with the web socket, and the second is the `SplitNetworkTransport`, which uses a web socket for subscriptions but a normal `HTTPNetworkTransport` for everything else. +The first is the `WebSocketTransport`, which works with the web socket, and the second is the `SplitNetworkTransport`, which uses a web socket for subscriptions but a normal `RequestChainNetworkTransport` for everything else. In this instance, we'll use a `SplitNetworkTransport` since we want to demonstrate subscribing to changes, but we need to also be able to send changes for that subscription to come through. */ -//:First, setup the `HTTPNetworkTransport`: +//:First, setup the `RequestChainNetworkTransport` that will handle your HTTP requests: let url = URL(string: "http://localhost:8080/graphql")! -let normalTransport = HTTPNetworkTransport(url: url) +let normalTransport = RequestChainNetworkTransport(interceptorProvider: LegacyInterceptorProvider(), endpointURL: url) //: Next, set up the `WebSocketTransport` to talk to the websocket endpoint. Note that this may take a different URL, sometimes with a `ws` prefix, than your normal http endpoint: @@ -34,7 +34,7 @@ let webSocketTransport = WebSocketTransport(request: URLRequest(url: webSocketUR //: Then, set up the split transport with the two transports you've just created: -let splitTransport = SplitNetworkTransport(httpNetworkTransport: normalTransport, webSocketNetworkTransport: webSocketTransport) +let splitTransport = SplitNetworkTransport(uploadingNetworkTransport: normalTransport, webSocketNetworkTransport: webSocketTransport) //: Finally, instantiate your client with the split transport: