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

feature(defer): Partial and Incremental Parsing #217

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
93eda59
Label is required on deferred selection
calvincestari Nov 28, 2023
3943275
Adds Deferrable conformance to InlineFragment
calvincestari Dec 7, 2023
67a55fb
Replace hasDeferredFragments with deferredFragments
calvincestari Dec 8, 2023
0e7f9d2
Multipart response defer parser hands off whole response
calvincestari Dec 11, 2023
ef93ed7
Adds incremental JSON response parser
calvincestari Dec 11, 2023
0f3379e
Adds incremental GraphQL result type
calvincestari Dec 11, 2023
a4f96d4
Separates DataDictMapper from GraphQLSelectionSetMapper
calvincestari Dec 12, 2023
d531495
Refactor GraphQLResponse into abstract and incremental variants
calvincestari Dec 12, 2023
e9c34f8
Refactor for partial and incremental execution
calvincestari Dec 12, 2023
416aca4
Add note to deferred field collection
calvincestari Dec 15, 2023
c698368
Multipart defer parsing to JSON serializer
calvincestari Dec 15, 2023
c715940
Implements selection set lookup by incremental response label and path
calvincestari Dec 18, 2023
c9d534d
Implements DataDict incremental result merging
calvincestari Dec 18, 2023
ac325b3
Compute root key for incremental responses
calvincestari Dec 18, 2023
93b42a5
Regenerate JS bundle (enable @defer directive)
calvincestari Dec 18, 2023
ebd7f57
Mutable fulfilled and deferred fragment storage in DataDict
calvincestari Dec 19, 2023
a4713d6
Collect deferred fragments during execution
calvincestari Dec 19, 2023
e92a041
Merge fulled and deferred fragments of incremental results
calvincestari Dec 19, 2023
111457f
Naming
calvincestari Dec 19, 2023
faccaf5
Refactor DataDict merging
calvincestari Dec 21, 2023
f08f5d8
Disable cache writing for deferred queries
calvincestari Dec 21, 2023
08ae8da
Clean up from warnings
calvincestari Dec 21, 2023
82cbd65
Rollback unneccessary changes
calvincestari Dec 21, 2023
3d682e3
Warning cleanup
calvincestari Dec 21, 2023
7214dc4
Fix test codegen project build error
calvincestari Dec 21, 2023
f092499
Reorder imports
calvincestari Dec 22, 2023
c5f794a
A better check to handle a nil optional
calvincestari Dec 22, 2023
6907fd1
Rename property path to fieldPath
calvincestari Jan 10, 2024
92c0943
Remove redundant payload check
calvincestari Jan 10, 2024
12ca32c
Add convenience function to check for deferred fragments
calvincestari Jan 17, 2024
a92e0e6
Refactor variables order
calvincestari Jan 18, 2024
8b8db11
Adds precondition to fragment field collection
calvincestari Jan 18, 2024
292cc50
Refactor deferred selection set type lookup
calvincestari Jan 25, 2024
6d73739
Clarify error messages
calvincestari Jan 25, 2024
7bacc27
Update frontend bundle
calvincestari Feb 6, 2024
8fa1a40
Add defer directive to test schema
calvincestari Feb 6, 2024
dd6b435
Refactor DataDict merging
calvincestari Feb 12, 2024
4ee07f0
Shift order of functions and add documentation
calvincestari Feb 12, 2024
34c171e
Refactor GraphQLResult merging
calvincestari Feb 13, 2024
98e61d1
Further constrain the selection set type for incremental execution
calvincestari Feb 14, 2024
3e2dde9
Fix build warnings
calvincestari Feb 14, 2024
492dfd7
MultipartResponseDeferParser - additional logic + tests
calvincestari Feb 17, 2024
28f2939
Refactor HTTP subscription tests
calvincestari Feb 17, 2024
b7fcef6
Refactor multipart subscription parser tests
calvincestari Feb 17, 2024
e1a4f4c
Clean up data type error
calvincestari Feb 22, 2024
528ae7b
Refactor DataDict merging errors + tests
calvincestari Feb 23, 2024
2a96c30
Cleanup DataDict merge error tests
calvincestari Feb 23, 2024
872aa34
Refactor DataDict merge tests for shared subject
calvincestari Feb 24, 2024
9d9712c
Adds DataDict merge data tests
calvincestari Mar 4, 2024
7693468
Clean up GraphQLResultTests
calvincestari Mar 5, 2024
7e6ce98
Add incremental merging tests
calvincestari Mar 7, 2024
bfe5dd5
Merge branch 'feature/defer-execution-networking' into defer/partial-…
calvincestari Mar 13, 2024
02b0c42
Improve subscription parser tests
calvincestari Mar 13, 2024
a53c783
Adds incremental result parser error tests
calvincestari Mar 13, 2024
01ad1d6
Remove duplicated HTTP subscription tests
calvincestari Mar 14, 2024
552d43a
Adds execution tests
calvincestari Mar 14, 2024
ed4a103
Refactor InterceptorTester return
calvincestari Mar 14, 2024
5144e6d
Refactor deferred fragment on GraphQLOperation
calvincestari Mar 14, 2024
7b76db9
Adds JSONResponseParsingInterceptorTests
calvincestari Mar 14, 2024
57e8f7a
Adds DefaultInterceptorProviderTests
calvincestari Mar 15, 2024
a1bcba1
Refine IncrementalGraphQLResponse errors
calvincestari Mar 15, 2024
407e7eb
Adds tests for IncrementalGraphQLResponse
calvincestari Mar 15, 2024
d375245
Add e2e defer tests
calvincestari Mar 16, 2024
8559eb3
Fix spelling
calvincestari Mar 19, 2024
88dff62
Reduce property and function permission scope
calvincestari Mar 19, 2024
ffef781
Rename merge closure
calvincestari Mar 19, 2024
93bd99d
Add comment for @defer directive need in testing
calvincestari Mar 19, 2024
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
45 changes: 25 additions & 20 deletions Tests/ApolloCodegenTests/CodeGenIR/IRRootFieldBuilderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
// MARK: - Helpers

func buildSubjectRootField(operationName: String? = nil) async throws {
addDeferDirective()

ir = try await IRBuilderTestWrapper(.mock(schema: schemaSDL, document: document))
if let operationName {
operation = try XCTUnwrap(ir.compilationResult.operations.first(
Expand All @@ -52,6 +54,19 @@
subject = result.rootField
}

// This function will only be needed until @defer is merged into the GraphQL spec and is
// considered a first-class directive in graphql-js. Right now it is a valid directive but must
// be 'enabled' through explicit declaration in the schema.
fileprivate func addDeferDirective() {
calvincestari marked this conversation as resolved.
Show resolved Hide resolved
guard let schemaSDL = self.schemaSDL else { return }

self.schemaSDL = """
directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT

\(schemaSDL)
"""
}

// MARK: - Children Computation

// MARK: Children - Fragment Type
Expand Down Expand Up @@ -4384,9 +4399,11 @@
expect(Array(self.computedReferencedFragments)).to(equal(expected))
}

// MARK: - Deferred Fragments - hasDeferredFragments property
// MARK: - Deferred Fragments - containsDeferredFragment property

#warning("Need tests for deferredFragments property")

Check warning on line 4404 in Tests/ApolloCodegenTests/CodeGenIR/IRRootFieldBuilderTests.swift

View workflow job for this annotation

GitHub Actions / Codegen Lib Unit Tests - macOS

Need tests for deferredFragments property
calvincestari marked this conversation as resolved.
Show resolved Hide resolved

func test__deferredFragments__givenNoDeferredFragment_hasDeferredFragmentsFalse() async throws {
func test__deferredFragments__givenNoDeferredFragment_containsDeferredFragmentFalse() async throws {
// given
schemaSDL = """
type Query {
Expand Down Expand Up @@ -4426,9 +4443,7 @@
expect(self.result.containsDeferredFragment).to(beFalse())
}

func test__deferredFragments__givenDeferredInlineFragment_hasDeferredFragmentsTrue() async throws {
throw XCTSkip("Skipped in PR #235 - must be reverted when the feature/defer-execution-networking branch is merged into main!")

func test__deferredFragments__givenDeferredInlineFragment_containsDeferredFragmentTrue() async throws {
// given
schemaSDL = """
type Query {
Expand Down Expand Up @@ -4468,9 +4483,7 @@
expect(self.result.containsDeferredFragment).to(beTrue())
}

func test__deferredFragments__givenDeferredInlineFragmentWithCondition_hasDeferredFragmentsTrue() async throws {
throw XCTSkip("Skipped in PR #235 - must be reverted when the feature/defer-execution-networking branch is merged into main!")

func test__deferredFragments__givenDeferredInlineFragmentWithCondition_containsDeferredFragmentTrue() async throws {
// given
schemaSDL = """
type Query {
Expand Down Expand Up @@ -4510,9 +4523,7 @@
expect(self.result.containsDeferredFragment).to(beTrue())
}

func test__deferredFragments__givenDeferredInlineFragmentWithConditionFalse_hasDeferredFragmentsFalse() async throws {
throw XCTSkip("Skipped in PR #235 - must be reverted when the feature/defer-execution-networking branch is merged into main!")

func test__deferredFragments__givenDeferredInlineFragmentWithConditionFalse_containsDeferredFragmentFalse() async throws {
// given
schemaSDL = """
type Query {
Expand Down Expand Up @@ -4552,9 +4563,7 @@
expect(self.result.containsDeferredFragment).to(beFalse())
}

func test__deferredFragments__givenDeferredNamedFragment_onDifferentTypeCase_hasDeferredFragmentsTrue() async throws {
throw XCTSkip("Skipped in PR #235 - must be reverted when the feature/defer-execution-networking branch is merged into main!")

func test__deferredFragments__givenDeferredNamedFragment_onDifferentTypeCase_containsDeferredFragmentTrue() async throws {
// given
schemaSDL = """
type Query {
Expand Down Expand Up @@ -4593,9 +4602,7 @@
expect(self.result.containsDeferredFragment).to(beTrue())
}

func test__deferredFragments__givenDeferredInlineFragment_withinNamedFragment_hasDeferredFragmentsTrue() async throws {
throw XCTSkip("Skipped in PR #235 - must be reverted when the feature/defer-execution-networking branch is merged into main!")

func test__deferredFragments__givenDeferredInlineFragment_withinNamedFragment_containsDeferredFragmentTrue() async throws {
// given
schemaSDL = """
type Query {
Expand Down Expand Up @@ -4639,9 +4646,7 @@
expect(self.result.containsDeferredFragment).to(beTrue())
}

func test__deferredFragments__givenDeferredNamedFragment_withSelectionOnDifferentTypeCase_hasDeferredFragmentsTrue() async throws {
throw XCTSkip("Skipped in PR #235 - must be reverted when the feature/defer-execution-networking branch is merged into main!")

func test__deferredFragments__givenDeferredNamedFragment_withSelectionOnDifferentTypeCase_containsDeferredFragmentTrue() async throws {
// given
schemaSDL = """
type Query {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,131 +308,7 @@

// MARK: - Defer Properties

func test__generate__givenQueryWithDeferredInlineFragment_generatesDeferredPropertyTrue() async throws {
throw XCTSkip("Skipped in PR #235 - must be reverted when the feature/defer-execution-networking branch is merged into main!")

// given
schemaSDL = """
type Query {
allAnimals: [Animal!]
}

interface Animal {
species: String!
}

type Dog implements Animal {
species: String!
}
"""

document = """
query TestOperation {
allAnimals {
... on Dog @defer(label: "root") {
species
}
}
}
"""

let expected = """
public static let hasDeferredFragments: Bool = true
"""

// when
try await buildSubjectAndOperation()
let actual = renderSubject()

// then
expect(actual).to(equalLineByLine(expected, atLine: 8, ignoringExtraLines: true))
}

func test__generate__givenQueryWithDeferredNamedFragment_generatesDeferredPropertyTrue() async throws {
throw XCTSkip("Skipped in PR #235 - must be reverted when the feature/defer-execution-networking branch is merged into main!")

// given
schemaSDL = """
type Query {
allAnimals: [Animal!]
}

interface Animal {
species: String!
}

type Dog implements Animal {
species: String!
}
"""

document = """
query TestOperation {
allAnimals {
... DogFragment @defer(label: "root")
}
}

fragment DogFragment on Dog {
species
}
"""

let expected = """
public static let hasDeferredFragments: Bool = true
"""

// when
try await buildSubjectAndOperation()
let actual = renderSubject()

// then
expect(actual).to(equalLineByLine(expected, atLine: 9, ignoringExtraLines: true))
}

func test__generate__givenQueryWithNamedFragment_withDeferredTypeCase_generatesDeferredPropertyTrue() async throws {
throw XCTSkip("Skipped in PR #235 - must be reverted when the feature/defer-execution-networking branch is merged into main!")

// given
schemaSDL = """
type Query {
allAnimals: [Animal!]
}

interface Animal {
species: String!
}

type Dog implements Animal {
species: String!
}
"""

document = """
query TestOperation {
allAnimals {
... DogFragment
}
}

fragment DogFragment on Animal {
... on Dog @defer(label: "root") {
species
}
}
"""

let expected = """
public static let hasDeferredFragments: Bool = true
"""

// when
try await buildSubjectAndOperation()
let actual = renderSubject()

// then
expect(actual).to(equalLineByLine(expected, atLine: 9, ignoringExtraLines: true))
}
#warning("Need tests for deferredFragments property")

Check warning on line 311 in Tests/ApolloCodegenTests/CodeGeneration/Templates/OperationDefinitionTemplateTests.swift

View workflow job for this annotation

GitHub Actions / Codegen Lib Unit Tests - macOS

Need tests for deferredFragments property

// MARK: - Selection Set Declaration

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1716,11 +1716,11 @@
))
}

#warning("need more tests here - same test cases as IRRootFieldBuilderTests")

Check warning on line 1719 in Tests/ApolloCodegenTests/CodeGeneration/Templates/SelectionSet/SelectionSetTemplateTests.swift

View workflow job for this annotation

GitHub Actions / Codegen Lib Unit Tests - macOS

need more tests here - same test cases as IRRootFieldBuilderTests

// MARK: Selections - Deferred Named Fragment

#warning("need more tests here - same test cases as IRRootFieldBuilderTests")

Check warning on line 1723 in Tests/ApolloCodegenTests/CodeGeneration/Templates/SelectionSet/SelectionSetTemplateTests.swift

View workflow job for this annotation

GitHub Actions / Codegen Lib Unit Tests - macOS

need more tests here - same test cases as IRRootFieldBuilderTests

// MARK: Selections - Include/Skip

Expand Down Expand Up @@ -4664,11 +4664,11 @@
))
}

#warning("need more tests here - same test cases as IRRootFieldBuilderTests")

Check warning on line 4667 in Tests/ApolloCodegenTests/CodeGeneration/Templates/SelectionSet/SelectionSetTemplateTests.swift

View workflow job for this annotation

GitHub Actions / Codegen Lib Unit Tests - macOS

need more tests here - same test cases as IRRootFieldBuilderTests

// MARK: Field Accessors - Deferred Named Fragments

#warning("need more tests here - same test cases as IRRootFieldBuilderTests")

Check warning on line 4671 in Tests/ApolloCodegenTests/CodeGeneration/Templates/SelectionSet/SelectionSetTemplateTests.swift

View workflow job for this annotation

GitHub Actions / Codegen Lib Unit Tests - macOS

need more tests here - same test cases as IRRootFieldBuilderTests

// MARK: - Inline Fragment Accessors

Expand Down Expand Up @@ -5522,11 +5522,11 @@
))
}

#warning("need more tests here - same test cases as IRRootFieldBuilderTests")

Check warning on line 5525 in Tests/ApolloCodegenTests/CodeGeneration/Templates/SelectionSet/SelectionSetTemplateTests.swift

View workflow job for this annotation

GitHub Actions / Codegen Lib Unit Tests - macOS

need more tests here - same test cases as IRRootFieldBuilderTests

// MARK: Fragment Accessors - Deferred Named Fragment

#warning("need more tests here - same test cases as IRRootFieldBuilderTests")

Check warning on line 5529 in Tests/ApolloCodegenTests/CodeGeneration/Templates/SelectionSet/SelectionSetTemplateTests.swift

View workflow job for this annotation

GitHub Actions / Codegen Lib Unit Tests - macOS

need more tests here - same test cases as IRRootFieldBuilderTests

// MARK: - Nested Selection Sets

Expand Down Expand Up @@ -7407,7 +7407,7 @@
expect(rendered_allAnimals_deferredAsRoot).to(equalLineByLine(
"""
/// AllAnimal.Root
public struct Root: TestSchema.InlineFragment, ApolloAPI.Deferrable {
public struct Root: TestSchema.InlineFragment {
calvincestari marked this conversation as resolved.
Show resolved Hide resolved
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }

Expand Down Expand Up @@ -7461,7 +7461,7 @@
expect(rendered_allAnimals_deferredAsRoot).to(equalLineByLine(
"""
/// AllAnimal.Root
public struct Root: TestSchema.InlineFragment, ApolloAPI.Deferrable {
public struct Root: TestSchema.InlineFragment {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }

Expand Down Expand Up @@ -7520,7 +7520,7 @@
expect(rendered_allAnimals_asDog_deferredAsRoot).to(equalLineByLine(
"""
/// AllAnimal.AsDog.Root
public struct Root: TestSchema.InlineFragment, ApolloAPI.Deferrable {
public struct Root: TestSchema.InlineFragment {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }

Expand Down
15 changes: 8 additions & 7 deletions Tests/ApolloInternalTestHelpers/InterceptorTester.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Apollo
import ApolloAPI
import Foundation

/// Use this interceptor tester to isolate a single `ApolloInterceptor` vs. having to create an
Expand All @@ -14,9 +15,9 @@ public class InterceptorTester {
public func intercept<Operation>(
request: Apollo.HTTPRequest<Operation>,
response: Apollo.HTTPResponse<Operation>? = nil,
completion: @escaping (Result<Data?, Error>) -> Void
completion: @escaping (Result<HTTPResponse<Operation>?, Error>) -> Void
) {
let requestChain = ResponseCaptureRequestChain({ result in
let requestChain = ResponseCaptureRequestChain<Operation>({ result in
completion(result)
})

Expand All @@ -27,11 +28,11 @@ public class InterceptorTester {
}
}

fileprivate class ResponseCaptureRequestChain: RequestChain {
fileprivate class ResponseCaptureRequestChain<T: GraphQLOperation>: RequestChain {
var isCancelled: Bool = false
let completion: (Result<Data?, Error>) -> Void
let completion: (Result<HTTPResponse<T>?, Error>) -> Void

init(_ completion: @escaping (Result<Data?, Error>) -> Void) {
init(_ completion: @escaping (Result<HTTPResponse<T>?, Error>) -> Void) {
self.completion = completion
}

Expand All @@ -45,7 +46,7 @@ fileprivate class ResponseCaptureRequestChain: RequestChain {
response: Apollo.HTTPResponse<Operation>?,
completion: @escaping (Result<Apollo.GraphQLResult<Operation.Data>, Error>) -> Void
) {
self.completion(.success(response?.rawData))
self.completion(.success(response as? HTTPResponse<T>))
}

func proceedAsync<Operation>(
Expand All @@ -54,7 +55,7 @@ fileprivate class ResponseCaptureRequestChain: RequestChain {
interceptor: any ApolloInterceptor,
completion: @escaping (Result<GraphQLResult<Operation.Data>, Error>) -> Void
) {
self.completion(.success(response?.rawData))
self.completion(.success(response as? HTTPResponse<T>))
}

func cancel() {}
Expand Down
4 changes: 2 additions & 2 deletions Tests/ApolloInternalTestHelpers/MockOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ open class MockOperation<SelectionSet: RootSelectionSet>: GraphQLOperation {

open class var operationType: GraphQLOperationType { .query }

open class var hasDeferredFragments: Bool { false }

open class var operationName: String { "MockOperationName" }

open class var operationDocument: OperationDocument {
Expand All @@ -17,6 +15,8 @@ open class MockOperation<SelectionSet: RootSelectionSet>: GraphQLOperation {

public init() {}

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

}

open class MockQuery<SelectionSet: RootSelectionSet>: MockOperation<SelectionSet>, GraphQLQuery {
Expand Down
2 changes: 1 addition & 1 deletion Tests/ApolloPerformanceTests/ParsingPerformanceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class ParsingPerformanceTests: XCTestCase {
measure {
subject.intercept(request: request, response: response) { result in
XCTAssertSuccessResult(result)
XCTAssertEqual(try! result.get(), expectedData)
XCTAssertEqual(try! result.get()?.rawData, expectedData)
}
}
}
Expand Down
Loading
Loading