Skip to content

[v2] [8/X] Add OperationResponseFormat #677

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

Merged
merged 24 commits into from
Jun 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
062943b
Added associatedtype ResponseFormat to GraphQLOperation
AnthonyMDev May 27, 2025
9003eff
WIP
AnthonyMDev May 28, 2025
e3f92aa
WIP
AnthonyMDev Jun 24, 2025
c79edc2
Finish implementing RequestChain
AnthonyMDev Jun 24, 2025
c91c4db
Make HTTPInterceptor operate on URLRequest
AnthonyMDev Jun 25, 2025
1077a2f
Remove ApolloErrorInterceptor
AnthonyMDev Jun 25, 2025
1f8ad0e
Remove RequestContext
AnthonyMDev Jun 26, 2025
b9aa2b1
Create ClientContext for ClientAwareness
AnthonyMDev Jun 26, 2025
4794b49
WIP: Setting up FetchBehavior and RequestConfiguration
AnthonyMDev Jun 27, 2025
a3d8114
Add FetchBehavior back to GraphQLRequest
AnthonyMDev Jun 27, 2025
311bcf9
Define new CachePolicy API
AnthonyMDev Jun 27, 2025
1a62c2f
Refactor GraphQLQueryWatcher
AnthonyMDev Jun 27, 2025
c0e7a1d
Improve CachePolicy APIs
AnthonyMDev Jun 27, 2025
4ce6838
Fix deprecated init recursion
AnthonyMDev Jun 27, 2025
d51f2d6
Mutation and Upload APIs
AnthonyMDev Jun 27, 2025
e274358
Update NetworkTransport for upload requests and minor fixes
AnthonyMDev Jun 27, 2025
2dc7a6d
Reimagined SplitNetworkTransport
AnthonyMDev Jun 27, 2025
00778e8
Cleanup and Documentation
AnthonyMDev Jun 27, 2025
be27f68
[v2] [14/X] NetworkTransport and Client Cleanup (#686)
AnthonyMDev Jun 28, 2025
8a78118
[v2] [13/X] Refactor GraphQLQueryWatcher (#685)
AnthonyMDev Jun 28, 2025
945d77b
[v2] [12/X] ApolloClient Fetch APIs (#684)
AnthonyMDev Jun 28, 2025
7494c0e
[v2] [11/X] Create ClientContext for ClientAwareness (#681)
AnthonyMDev Jun 28, 2025
655bbf2
[v2] [10/X] Remove RequestContext (#680)
AnthonyMDev Jun 28, 2025
01f7207
[v2] [9/X] Interceptor/RequestChain improvements (#678)
AnthonyMDev Jun 28, 2025
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
8 changes: 2 additions & 6 deletions Tests/ApolloInternalTestHelpers/MockNetworkTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,20 @@ public final class MockNetworkTransport: NetworkTransport {
public func send<Query>(
query: Query,
cachePolicy: CachePolicy,
context: (any RequestContext)?
) throws -> AsyncThrowingStream<GraphQLResult<Query.Data>, any Error> where Query: GraphQLQuery {
try requestChainTransport.send(
query: query,
cachePolicy: cachePolicy,
context: context
cachePolicy: cachePolicy
)
}

public func send<Mutation>(
mutation: Mutation,
cachePolicy: CachePolicy,
context: (any RequestContext)?
) throws -> AsyncThrowingStream<GraphQLResult<Mutation.Data>, any Error> where Mutation: GraphQLMutation {
try requestChainTransport.send(
mutation: mutation,
cachePolicy: cachePolicy,
context: context
cachePolicy: cachePolicy
)
}

Expand Down
3 changes: 0 additions & 3 deletions Tests/ApolloInternalTestHelpers/MockOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ open class MockOperation<SelectionSet: RootSelectionSet>: GraphQLOperation, @unc
open var __variables: Variables?

public init() {}

open class var deferredFragments: [DeferredFragmentIdentifier : any ApolloAPI.SelectionSet.Type]? { return nil }

}

open class MockQuery<SelectionSet: RootSelectionSet>: MockOperation<SelectionSet>, GraphQLQuery, @unchecked Sendable {
Expand Down
42 changes: 23 additions & 19 deletions Tests/ApolloTests/Cache/CacheDependentInterceptorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,31 @@ class CacheDependentInterceptorTests: XCTestCase, CacheDependentTesting, MockRes
])

/// This interceptor will reroute anything that fails with a response code error to retry hitting only the cache
final class RerouteToCacheErrorInterceptor: ApolloErrorInterceptor {
final class RerouteToCacheErrorInterceptor: ApolloInterceptor {
nonisolated(unsafe) var handledError: (any Error)?

func intercept<Request>(
error: any Error,
func intercept<Request: GraphQLRequest>(
request: Request,
result: InterceptorResult<Request.Operation>?
) async throws -> GraphQLResult<Request.Operation.Data> where Request: GraphQLRequest {
self.handledError = error

guard error is ResponseCodeInterceptor.ResponseCodeError else {
throw error
next: NextInterceptorFunction<Request>
) async throws -> InterceptorResultStream<GraphQLResponse<Request.Operation>> {

do {
return try await next(request)

} catch {
self.handledError = error
guard error is ResponseCodeInterceptor.ResponseCodeError else {
throw error
}
var request = request
request.fetchBehavior = FetchBehavior(
shouldAttemptCacheRead: true,
shouldAttemptCacheWrite: true,
shouldAttemptNetworkFetch: .never
)

throw RequestChain.Retry(request: request)
}
var request = request
request.cachePolicy = .returnCacheDataDontFetch
throw RequestChainRetry(request: request)
}
}

Expand All @@ -86,13 +95,8 @@ class CacheDependentInterceptorTests: XCTestCase, CacheDependentTesting, MockRes

func interceptors<Operation>(for operation: Operation) -> [any ApolloInterceptor]
where Operation: GraphQLOperation {
[]
}

func errorInterceptor<Operation>(for operation: Operation) -> (any ApolloErrorInterceptor)?
where Operation: GraphQLOperation {
self.errorInterceptor
}
[self.errorInterceptor]
}
}

await CacheDependentInterceptorTests.registerRequestHandler(for: TestURL.mockServer.url) { _ in
Expand Down
27 changes: 17 additions & 10 deletions Tests/ApolloTests/DeferTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,22 @@ final class DeferTests: XCTestCase, MockResponseProvider {
}
}

private final class TVShowQuery: MockQuery<TVShowQuery.Data>, @unchecked Sendable {
private struct TVShowQuery: GraphQLQuery, @unchecked Sendable {
static var operationName: String { "TVShowQuery" }

static var operationDocument: OperationDocument {
.init(definition: .init("Mock Operation Definition"))
}

static var responseFormat: IncrementalDeferredResponseFormat {
IncrementalDeferredResponseFormat(deferredFragments: [
.init(label: "deferredGenres", fieldPath: ["show"]): Data.Show.DeferredGenres.self,
.init(label: "deferredFriend", fieldPath: ["show", "characters"]): Data.Show.Character.DeferredFriend.self,
])
}

public var __variables: Variables?

final class Data: MockSelectionSet, @unchecked Sendable {
override class var __selections: [Selection] {
[
Expand Down Expand Up @@ -118,14 +133,6 @@ final class DeferTests: XCTestCase, MockResponseProvider {
}
}
}

override class var deferredFragments: [DeferredFragmentIdentifier: any SelectionSet.Type]? {
[
DeferredFragmentIdentifier(label: "deferredGenres", fieldPath: ["show"]): Data.Show.DeferredGenres.self,
DeferredFragmentIdentifier(label: "deferredFriend", fieldPath: ["show", "characters"]): Data.Show.Character
.DeferredFriend.self,
]
}
}

let defaultTimeout = 0.5
Expand Down Expand Up @@ -185,7 +192,7 @@ final class DeferTests: XCTestCase, MockResponseProvider {

let response = results.first
let data = response?.data

expect(data?.__data._fulfilledFragments).to(
equal([
ObjectIdentifier(TVShowQuery.Data.self)
Expand Down
26 changes: 16 additions & 10 deletions Tests/ApolloTests/IncrementalGraphQLResponseTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,24 @@ import Nimble

final class IncrementalGraphQLResponseTests: XCTestCase {

class DeferredQuery: MockQuery<DeferredQuery.Data> {
class Data: MockSelectionSet {
class DeferredQuery: GraphQLQuery, @unchecked Sendable {
static var operationName: String { "DeferredQuery" }

static var operationDocument: ApolloAPI.OperationDocument { .init(definition: .init("Mock Operation Definition")) }

static var responseFormat: IncrementalDeferredResponseFormat {
IncrementalDeferredResponseFormat(deferredFragments: [
DeferredFragmentIdentifier(label: "deferredFriend", fieldPath: ["animal"]): Data.Animal.DeferredFriend.self,
// Data.Animal.DeliberatelyMissing is intentionally not here for error testing
])
}

class Data: MockSelectionSet, @unchecked Sendable {
override class var __selections: [Selection] {[
.field("animal", Animal.self),
]}

class Animal: AbstractMockSelectionSet<Animal.Fragments, MockSchemaMetadata> {
class Animal: AbstractMockSelectionSet<Animal.Fragments, MockSchemaMetadata>, @unchecked Sendable {
override class var __selections: [Selection] {[
.field("__typename", String.self),
.field("name", String.self),
Expand All @@ -33,24 +44,19 @@ final class IncrementalGraphQLResponseTests: XCTestCase {
@Deferred var deliberatelyMissing: DeliberatelyMissing?
}

class DeferredFriend: MockTypeCase {
class DeferredFriend: MockTypeCase, @unchecked Sendable {
override class var __selections: [Selection] {[
.field("friend", String.self),
]}
}

class DeliberatelyMissing: MockTypeCase {
class DeliberatelyMissing: MockTypeCase, @unchecked Sendable {
override class var __selections: [Selection] {[
.field("key", String.self),
]}
}
}
}

override class var deferredFragments: [DeferredFragmentIdentifier : any SelectionSet.Type]? {[
DeferredFragmentIdentifier(label: "deferredFriend", fieldPath: ["animal"]): Data.Animal.DeferredFriend.self,
// Data.Animal.DeliberatelyMissing is intentionally not here for error testing
]}
}

// MARK: - Initialization Tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,21 @@ final class JSONResponseParsingInterceptorTests_IncrementalItems: XCTestCase {
}
}

final class AnimalQuery: MockQuery<AnimalQuery.AnAnimal>, @unchecked Sendable {
struct AnimalQuery: GraphQLQuery, @unchecked Sendable {
static var operationName: String { "AnimalQuery" }

static var operationDocument: OperationDocument {
.init(definition: .init("Mock Operation Definition"))
}

static var responseFormat: IncrementalDeferredResponseFormat {
IncrementalDeferredResponseFormat(deferredFragments: [
DeferredFragmentIdentifier(label: "deferredGenus", fieldPath: ["animal"]): AnAnimal.Animal.DeferredGenus.self,
DeferredFragmentIdentifier(label: "deferredFriend", fieldPath: ["animal"]): AnAnimal.Animal.DeferredFriend.self,
])
}

typealias Data = AnAnimal
final class AnAnimal: MockSelectionSet, @unchecked Sendable {
typealias Schema = MockSchemaMetadata

Expand Down Expand Up @@ -498,12 +512,5 @@ final class JSONResponseParsingInterceptorTests_IncrementalItems: XCTestCase {
}
}
}

override class var deferredFragments: [DeferredFragmentIdentifier: any SelectionSet.Type]? {
[
DeferredFragmentIdentifier(label: "deferredGenus", fieldPath: ["animal"]): AnAnimal.Animal.DeferredGenus.self,
DeferredFragmentIdentifier(label: "deferredFriend", fieldPath: ["animal"]): AnAnimal.Animal.DeferredFriend.self,
]
}
}
}
1 change: 0 additions & 1 deletion Tests/ApolloTests/Network/ApolloURLSessionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ class ApolloURLSessionTests: XCTestCase, MockResponseProvider {
var operation = MockQuery<MockSelectionSet>.mock()
var additionalHeaders: [String: String] = [:]
var cachePolicy: Apollo.CachePolicy = .fetchIgnoringCacheCompletely
var context: (any RequestContext)? = nil
var clientAwarenessMetadata: ClientAwarenessMetadata = .none

var urlRequest: URLRequest
Expand Down
104 changes: 0 additions & 104 deletions apollo-ios/Sources/Apollo/AnyGraphQLResponse.swift

This file was deleted.

Loading
Loading