Skip to content

Commit

Permalink
Selection set inititializer generated with all field merging always
Browse files Browse the repository at this point in the history
  • Loading branch information
AnthonyMDev committed Jul 16, 2024
1 parent 844959a commit 4a17e7d
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,18 @@ class SelectionSetTemplate_FieldMerging_Tests: XCTestCase {
inflectionRules: [ApolloCodegenLib.InflectionRule] = [],
schemaDocumentation: ApolloCodegenConfiguration.Composition = .exclude,
fieldMerging: ApolloCodegenConfiguration.FieldMerging = .all,
selectionSetInitializers: Bool = false,
warningsOnDeprecatedUsage: ApolloCodegenConfiguration.Composition = .exclude,
conversionStrategies: ApolloCodegenConfiguration.ConversionStrategies = .init(),
cocoapodsImportStatements: Bool = false
) async throws {
ir = try await IRBuilderTestWrapper(.mock(schema: schemaSDL, document: document))
let operationDefinition = try XCTUnwrap(ir.compilationResult[operation: operationName])
operation = await ir.build(operation: operationDefinition, mergingStrategy: fieldMerging.options)
operation = await ir.build(
operation: operationDefinition,
mergingStrategy: fieldMerging.options
)

let config = ApolloCodegen.ConfigurationContext(config: .mock(
schemaNamespace: "TestSchema",
output: configOutput,
Expand All @@ -59,7 +64,7 @@ class SelectionSetTemplate_FieldMerging_Tests: XCTestCase {
)
subject = SelectionSetTemplate(
definition: self.operation.irObject,
generateInitializers: false,
generateInitializers: selectionSetInitializers,
config: config,
nonFatalErrorRecorder: .init(),
renderAccessControl: mockTemplateRenderer.accessControlModifier(for: .member)
Expand Down Expand Up @@ -509,4 +514,85 @@ class SelectionSetTemplate_FieldMerging_Tests: XCTestCase {
// then
expect(actual).to(equalLineByLine(expected, atLine: 12, ignoringExtraLines: true))
}

// MARK: - SelectionSetInitializers

func test__render_selectionSetInitializer__givenFieldMerging_none_withMergedSelections_rendersInitializerWithAllMergedSelections() async throws {
// given
schemaSDL = """
type Query {
allAnimals: [Animal!]
}
interface Animal {
species: String!
age: Int!
}
interface Pet implements Animal {
species: String!
age: Int!
}
type Dog implements Animal & Pet {
species: String!
age: Int!
bark: Boolean!
}
"""

document = """
query TestOperation {
allAnimals {
age
... on Pet {
species
}
... on Dog {
bark
}
}
}
"""

let expected =
"""
public init(
bark: Bool,
age: Int,
species: String
) {
self.init(_dataDict: DataDict(
data: [
"__typename": TestSchema.Objects.Dog.typename,
"bark": bark,
"age": age,
"species": species,
],
fulfilledFragments: [
ObjectIdentifier(TestOperationQuery.Data.AllAnimal.self),
ObjectIdentifier(TestOperationQuery.Data.AllAnimal.AsDog.self),
ObjectIdentifier(TestOperationQuery.Data.AllAnimal.AsPet.self)
]
))
}
"""

// when
try await buildSubjectAndOperation(
fieldMerging: .none,
selectionSetInitializers: true
)

/*
We only want to test the initializer for TestOperationQuery.Data.AllAnimal.AsDog. But we must
render the entire operation because the IRTestWrappers only allow one merge strategy to be
used at a time. However the `SelectionSetTemplate` is actually determining which merge
strategy to use for each child selection set dynamically. We need to test this behavior.
*/
let actual = subject.renderBody().description

// then
expect(actual).to(equalLineByLine(expected, atLine: 103, ignoringExtraLines: true))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1177,7 +1177,6 @@ class SelectionSetTemplate_Initializers_Tests: XCTestCase {
expect(actual).to(equalLineByLine(expected, atLine: 16, ignoringExtraLines: true))
}


func test__render_given_mergedSelection_rendersInitializer() async throws {
// given
schemaSDL = """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ struct SelectionSetValidationContext {
// Check for type conflicts resulting from singularization/pluralization of fields
var typeNamesForEntityFields = [String: String]()

let entityFields = selections.makeFieldIterator { field in
let entityFields = selections.makeFieldIterator(mergingStrategy: .all) { field in
field is IR.EntityField
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ extension IR.ComputedSelectionSet {
SelectionsIterator<OrderedDictionary<String, IR.NamedFragmentSpread>.Values>

func makeFieldIterator(
mergingStrategy: MergedSelections.MergingStrategy = .all,
filter: ((IR.Field) -> Bool)? = nil
) -> FieldIterator {
SelectionsIterator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,47 +44,51 @@ struct SelectionSetTemplate {
for selectionSet: IR.SelectionSet,
inParent context: SelectionSetContext
) -> SelectionSetContext {
let mergingStrategies: Set<MergedSelections.MergingStrategy> = {
if config.options.fieldMerging.options == .all {
return [.all]
}

/*
There are a number of situations in which we will need to calculate all merged
fields, even if the `fieldMerging` option is not set to `.all`.

1. When a selection set is a CompositeInlineFragment, we use all merged fields to calculate
the `__mergedSources` of the fragment.
2. When using `selectionSetInitializers`, the initializers must include all merged fields
for the type in order to initialize a fully valid and functional object.

In these situations, we should calculate both strategies. We still use the strategy provided
by the `fieldMerging` configuration option for generating property accessors. We only use the
`.all` merged fields in the necessary areas.
*/
if selectionSet.isCompositeInlineFragment {
return [.all, config.options.fieldMerging.options]
}

return [config.options.fieldMerging.options]
}()

let computedSelectionSet = ComputedSelectionSet.Builder(
selectionSet,
mergingStrategies: mergingStrategies,
mergingStrategy: self.config.experimentalFeatures.fieldMerging.options,
entityStorage: definition.entityStorage
).build()

var validationContext = context.validationContext
validationContext.runTypeValidationFor(
computedSelectionSet,
recordingErrorsTo: nonFatalErrorRecorder
)

return SelectionSetContext(
selectionSet: computedSelectionSet,
validationContext: validationContext
)
}

func mergingStrategies(
for selectionSet: IR.SelectionSet
) -> Set<MergedSelections.MergingStrategy> {
if self.config.options.fieldMerging.options == .all {
return [.all]
}

/*
There are a number of situations in which we will need to calculate all merged
fields, even if the `fieldMerging` option is not set to `.all`.

1. When using `selectionSetInitializers`, the initializers must include all merged fields
for the type in order to initialize a fully valid and functional object.
2. When a selection set is a CompositeInlineFragment, we use all merged fields to calculate
the `__mergedSources` of the fragment.

In these situations, we should calculate both strategies. We still use the strategy provided
by the `fieldMerging` configuration option for generating property accessors. We only use the
`.all` merged fields in the necessary areas.
*/
if self.generateInitializers || selectionSet.isCompositeInlineFragment {
return [.all, config.options.fieldMerging.options]
}

return [config.options.fieldMerging.options]
}

/// MARK: - Render Body

/// Renders the body of the SelectionSet template for the entire `definition` including all
Expand All @@ -97,9 +101,10 @@ struct SelectionSetTemplate {
///
/// - Returns: The `TemplateString` for the body of the `SelectionSetTemplate`.
func renderBody() -> TemplateString {
let selectionSet = definition.rootField.selectionSet
let computedRootSelectionSet = IR.ComputedSelectionSet.Builder(
definition.rootField.selectionSet,
mergingStrategies: [.all],
selectionSet,
mergingStrategies: self.mergingStrategies(for: selectionSet),
entityStorage: definition.entityStorage
).build()

Expand Down Expand Up @@ -209,7 +214,7 @@ struct SelectionSetTemplate {
\(RootEntityTypealias(selectionSet))
\(ParentTypeTemplate(selectionSet.parentType))
\(ifLet: selectionSet.direct, { DirectSelectionsMetadataTemplate($0, scope: selectionSet.scope) })
\(if: selectionSet.isCompositeInlineFragment, MergedSourcesTemplate(selectionSet.merged[.all]!.mergedSources))
\(if: selectionSet.isCompositeInlineFragment, MergedSourcesTemplate(selectionSet.merged.mergedSources))
\(section: FieldAccessorsTemplate(selectionSet))
Expand Down Expand Up @@ -732,11 +737,12 @@ struct SelectionSetTemplate {
}

// MARK: - Nested Selection Sets

private func ChildEntityFieldSelectionSets(
_ context: SelectionSetContext
) -> TemplateString {
let selectionSet = context.selectionSet
let allFields = selectionSet.makeFieldIterator { field in
let allFields = selectionSet.makeFieldIterator(mergingStrategy: .all) { field in
field is IR.EntityField
}

Expand Down

0 comments on commit 4a17e7d

Please sign in to comment.