Skip to content

Commit

Permalink
feature: GraphQL execution for @defer support (apollographql/apollo…
Browse files Browse the repository at this point in the history
  • Loading branch information
calvincestari authored and gh-action-runner committed Jul 19, 2024
1 parent 22e0034 commit 90ab76b
Show file tree
Hide file tree
Showing 11 changed files with 683 additions and 82 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import GraphQLCompiler
import IR
import TemplateString

struct DeferredFragmentsMetadataTemplate {

let operation: IR.Operation
let config: ApolloCodegen.ConfigurationContext
let renderAccessControl: () -> String

init(
operation: IR.Operation,
config: ApolloCodegen.ConfigurationContext,
renderAccessControl: @autoclosure @escaping () -> String
) {
self.operation = operation
self.config = config
self.renderAccessControl = renderAccessControl
}

// MARK: Templates

/// Renders metadata definitions for the deferred fragments of an Operation.
///
/// - Returns: The `TemplateString` for the deferred fragments metadata definitions.
func render() -> TemplateString {
let deferredFragmentPathTypeInfo = DeferredFragmentsPathTypeInfo(
from: operation.rootField.selectionSet.selections
)
guard !deferredFragmentPathTypeInfo.isEmpty else { return "" }

return """
// MARK: Deferred Fragment Metadata
\(renderAccessControl())extension \(operation.generatedDefinitionName) {
\(DeferredFragmentIdentifiersTemplate(deferredFragmentPathTypeInfo))
\(DeferredFragmentsPropertyTemplate(deferredFragmentPathTypeInfo))
}
"""
}

fileprivate func DeferredFragmentIdentifiersTemplate(
_ deferredFragmentPathTypeInfo: [DeferredPathTypeInfo]
) -> TemplateString {
"""
enum DeferredFragmentIdentifiers {
\(deferredFragmentPathTypeInfo.map {
return """
static let \($0.deferCondition.label) = DeferredFragmentIdentifier(label: \"\($0.deferCondition.label)\", fieldPath: [\
\($0.path.map { "\"\($0)\"" }, separator: ", ")\
])
"""
}, separator: "\n")
}
"""
}

fileprivate func DeferredFragmentsPropertyTemplate(
_ deferredFragmentPathTypeInfo: [DeferredPathTypeInfo]
) -> TemplateString {
"""
static var deferredFragments: [DeferredFragmentIdentifier: any \(config.ApolloAPITargetName).SelectionSet.Type]? {[
\(deferredFragmentPathTypeInfo.map {
return """
DeferredFragmentIdentifiers.\($0.deferCondition.label): \($0.typeName).self,
"""
}, separator: "\n")
]}
"""
}

// MARK: Helpers

fileprivate struct DeferredPathTypeInfo {
let path: [String]
let deferCondition: CompilationResult.DeferCondition
let typeName: String
}

fileprivate func DeferredFragmentsPathTypeInfo(
from directSelections: DirectSelections?,
path: [String] = []
) -> [DeferredPathTypeInfo] {
guard let directSelections, !directSelections.isEmpty else { return [] }

var deferredPathTypeInfo: [DeferredPathTypeInfo] = []

for field in directSelections.fields.values {
if let field = field as? EntityField {
let fieldPath = path + [(field.alias ?? field.name)]
deferredPathTypeInfo.append(contentsOf:
DeferredFragmentsPathTypeInfo(from: field.selectionSet.selections, path: fieldPath)
)
}
}

for fragment in directSelections.inlineFragments.values {
if let deferCondition = fragment.typeInfo.deferCondition {
let selectionSetName = SelectionSetNameGenerator.generatedSelectionSetName(
for: fragment.typeInfo,
format: .omittingRoot,
pluralizer: config.pluralizer
)

deferredPathTypeInfo.append(DeferredPathTypeInfo(
path: path,
deferCondition: deferCondition,
typeName: "Data.\(selectionSetName)"
))
}

deferredPathTypeInfo.append(contentsOf:
DeferredFragmentsPathTypeInfo(from: fragment.selectionSet.selections, path: path)
)
}

for fragment in directSelections.namedFragments.values {
if let deferCondition = fragment.typeInfo.deferCondition {
deferredPathTypeInfo.append(DeferredPathTypeInfo(
path: path,
deferCondition: deferCondition,
typeName: fragment.definition.name.asFragmentName
))
}

deferredPathTypeInfo.append(contentsOf:
DeferredFragmentsPathTypeInfo(
from: fragment.fragment.rootField.selectionSet.selections,
path: path
)
)
}

return deferredPathTypeInfo
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ struct OperationDefinitionTemplate: OperationTemplateRenderer {
\(OperationDeclaration())
\(DocumentType())
\(section: DeferredProperties(operation.containsDeferredFragment))
\(section: VariableProperties(operation.definition.variables))
\(Initializer(operation.definition.variables))
Expand All @@ -44,7 +42,11 @@ struct OperationDefinitionTemplate: OperationTemplateRenderer {
).renderBody())
}
}
\(section: DeferredFragmentsMetadataTemplate(
operation: operation,
config: config,
renderAccessControl: { accessControlModifier(for: .parent) }()
).render())
""")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,4 @@ extension OperationTemplateRenderer {
"""
}

func DeferredProperties(
_ hasDeferredFragments: Bool
) -> TemplateString {
return """
\(if: hasDeferredFragments, """
public static let hasDeferredFragments: Bool = true
""")
"""
}

}
102 changes: 74 additions & 28 deletions Sources/ApolloCodegenLib/Templates/SelectionSetTemplate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ struct SelectionSetTemplate {
\(renderAccessControl())\
struct \(inlineFragment.renderedTypeName): \(SelectionSetType(asInlineFragment: true))\
\(if: inlineFragment.isCompositeInlineFragment, ", \(config.ApolloAPITargetName).CompositeInlineFragment")\
\(if: inlineFragment.isDeferred, ", \(config.ApolloAPITargetName).Deferrable")\
{
\(BodyTemplate(context))
}
Expand Down Expand Up @@ -208,7 +207,7 @@ struct SelectionSetTemplate {
\($0)
"""
},
},
else: " \(dataInitStatement) "
)}
"""
Expand Down Expand Up @@ -401,9 +400,17 @@ struct SelectionSetTemplate {
}

private func FragmentSelectionTemplate(_ fragment: IR.NamedFragmentSpread) -> TemplateString {
"""
.fragment(\(fragment.definition.name.asFragmentName).self)
"""
if let deferCondition = fragment.typeInfo.deferCondition {
return DeferredNamedFragmentSelectionTemplate(
deferCondition: deferCondition,
fragment: fragment
)

} else {
return """
.fragment(\(fragment.definition.name.asFragmentName).self)
"""
}
}

private func DeferredInlineFragmentSelectionTemplate(
Expand All @@ -416,6 +423,17 @@ struct SelectionSetTemplate {
"""
}

private func DeferredNamedFragmentSelectionTemplate(
deferCondition: CompilationResult.DeferCondition,
fragment: IR.NamedFragmentSpread
) -> TemplateString {
"""
.deferred(\
\(ifLet: deferCondition.variable, { "if: \"\($0)\", " })\
\(fragment.definition.name.asFragmentName).self, label: "\(deferCondition.label)")
"""
}

// MARK: - Accessors
private func FieldAccessorsTemplate(
_ selectionSet: ComputedSelectionSet
Expand Down Expand Up @@ -482,8 +500,8 @@ struct SelectionSetTemplate {
) -> TemplateString {
guard
!(selectionSet.direct?.namedFragments.isEmpty ?? true)
|| !selectionSet.merged.namedFragments.isEmpty
|| (selectionSet.direct?.inlineFragments.containsDeferredFragment ?? false)
|| !selectionSet.merged.namedFragments.isEmpty
|| (selectionSet.direct?.inlineFragments.containsDeferredFragment ?? false)
else {
return ""
}
Expand All @@ -501,26 +519,42 @@ struct SelectionSetTemplate {
\(selectionSet.merged.namedFragments.values.map {
NamedFragmentAccessorTemplate($0, in: scope)
}, separator: "\n")
\(forEachIn: selectionSet.direct?.inlineFragments.values.elements ?? [], {
"\(ifLet: $0.typeInfo.deferCondition, DeferredFragmentAccessorTemplate)"
})
\(
forEachIn: selectionSet.direct?.inlineFragments.values.elements ?? [],
separator: "\n", {
"""
\(ifLet: $0.typeInfo.deferCondition, {
DeferredFragmentAccessorTemplate(propertyName: $0.label, typeName: $0.renderedTypeName)
})
"""
}
)
}
"""
}

private func FragmentInitializerTemplate(
_ selectionSet: ComputedSelectionSet
) -> String {
if let inlineFragments = selectionSet.direct?.inlineFragments,
inlineFragments.containsDeferredFragment
if let directSelections = selectionSet.direct,
(directSelections.inlineFragments.containsDeferredFragment
|| directSelections.namedFragments.containsDeferredFragment)
{
return DesignatedInitializerTemplate(
"""
\(forEachIn: inlineFragments.values, {
guard let deferCondition = $0.typeInfo.deferCondition else {
return nil
\(forEachIn: directSelections.inlineFragments.values, separator: "\n", {
if let deferCondition = $0.typeInfo.deferCondition {
return DeferredPropertyInitializationStatement(deferCondition.label)
}
return DeferredPropertyInitializationStatement(deferCondition)

return ""
})
\(forEachIn: directSelections.namedFragments.values, separator: "\n", {
if let _ = $0.typeInfo.deferCondition {
return DeferredPropertyInitializationStatement($0.definition.name.firstLowercased)
}

return ""
})
"""
)
Expand All @@ -530,10 +564,8 @@ struct SelectionSetTemplate {
}
}

private func DeferredPropertyInitializationStatement(
_ deferCondition: CompilationResult.DeferCondition
) -> TemplateString {
"_\(deferCondition.label) = Deferred(_dataDict: _dataDict)"
private func DeferredPropertyInitializationStatement(_ propertyName: String) -> TemplateString {
"_\(propertyName) = Deferred(_dataDict: _dataDict)"
}

private func NamedFragmentAccessorTemplate(
Expand All @@ -546,30 +578,38 @@ struct SelectionSetTemplate {
let isOptional =
fragment.inclusionConditions != nil
&& !scope.matches(fragment.inclusionConditions.unsafelyUnwrapped)
let isDeferred = fragment.typeInfo.deferCondition != nil

return """
\(renderAccessControl())var \(propertyName): \(typeName)\
\(if: isOptional, "?") {\
\(if: isDeferred,
DeferredFragmentAccessorTemplate(
propertyName: fragment.definition.name.firstLowercased,
typeName: fragment.definition.name.asFragmentName
)
, else:
"""
\(renderAccessControl())var \(propertyName): \(typeName)\(if: isOptional, "?") {\
\(if: !isMutable && !isDeferred, " _toFragment() }")
"""
)
\(if: isMutable,
"""
get { _toFragment() }
_modify { var f = \(propertyName); yield &f; \(
if: isOptional,
"if let newData = f?.__data { __data = newData }",
else: "__data = f.__data"
) }
}
""",
else: " _toFragment() }"
)
""")
"""
}

private func DeferredFragmentAccessorTemplate(
_ deferCondition: CompilationResult.DeferCondition
propertyName: String,
typeName: String
) -> TemplateString {
"@Deferred public var \(deferCondition.label): \(deferCondition.renderedTypeName)?"
"@Deferred public var \(propertyName): \(typeName)?"
}

// MARK: - SelectionSet Initializer
Expand Down Expand Up @@ -1148,3 +1188,9 @@ extension OrderedDictionary<ScopeCondition, InlineFragmentSpread> {
keys.contains(where: { $0.isDeferred })
}
}

extension OrderedDictionary<String, NamedFragmentSpread> {
fileprivate var containsDeferredFragment: Bool {
values.contains(where: { $0.typeInfo.deferCondition != nil })
}
}
2 changes: 1 addition & 1 deletion Sources/GraphQLCompiler/ApolloCodegenFrontendBundle.swift

Large diffs are not rendered by default.

Loading

0 comments on commit 90ab76b

Please sign in to comment.