Skip to content

Commit 79144a4

Browse files
calvincestarigh-action-runner
authored andcommitted
fix: Duplicate deferred fragment identifiers (#700)
1 parent 9897b10 commit 79144a4

File tree

2 files changed

+79
-2
lines changed

2 files changed

+79
-2
lines changed

Tests/ApolloCodegenTests/CodeGeneration/Templates/DeferredFragmentsMetadataTemplateTests.swift

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,5 +608,59 @@ class DeferredFragmentsMetadataTemplateTests: XCTestCase {
608608
ignoringExtraLines: false)
609609
)
610610
}
611-
611+
612+
func test__render__givenDeferredFragmentsWithDifferentSelectionSets_doesNotRenderDuplicateIdentifiers() async throws {
613+
schemaSDL = """
614+
type Query {
615+
allAnimals: [Animal!]
616+
}
617+
618+
interface Animal {
619+
id: String
620+
species: String
621+
}
622+
623+
type Dog implements Animal {
624+
id: String
625+
species: String
626+
}
627+
""".appendingDeferDirective()
628+
629+
document = """
630+
query TestOperation {
631+
allAnimals {
632+
__typename
633+
... on Dog @defer(label: "deferredDog") {
634+
id
635+
...DogFragment
636+
}
637+
}
638+
}
639+
640+
fragment DogFragment on Dog {
641+
species
642+
}
643+
"""
644+
645+
// when
646+
try await buildSubjectAndOperation()
647+
648+
// then
649+
let rendered = renderSubject()
650+
651+
expect(rendered).to(equalLineByLine("""
652+
enum DeferredFragmentIdentifiers {
653+
static let deferredDog = DeferredFragmentIdentifier(label: "deferredDog", fieldPath: ["allAnimals"])
654+
}
655+
656+
public static var deferredFragments: [DeferredFragmentIdentifier: any ApolloAPI.SelectionSet.Type]? {[
657+
DeferredFragmentIdentifiers.deferredDog: Data.AllAnimal.AsDog.DeferredDog.self,
658+
DeferredFragmentIdentifiers.deferredDog: DogFragment.self,
659+
]}
660+
""",
661+
atLine: 4,
662+
ignoringExtraLines: false)
663+
)
664+
}
665+
612666
}

apollo-ios-codegen/Sources/ApolloCodegenLib/Templates/DeferredFragmentsMetadataTemplate.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ struct DeferredFragmentsMetadataTemplate {
4444
) -> TemplateString {
4545
"""
4646
enum DeferredFragmentIdentifiers {
47-
\(deferredFragmentPathTypeInfo.map {
47+
\(deferredFragmentPathTypeInfo.unique(by: { $0.pathDeferConditionHash }).map {
4848
return """
4949
static let \($0.deferCondition.label) = DeferredFragmentIdentifier(label: \"\($0.deferCondition.label)\", fieldPath: [\
5050
\($0.path.map { "\"\($0)\"" }, separator: ", ")\
@@ -75,6 +75,22 @@ struct DeferredFragmentsMetadataTemplate {
7575
let path: [String]
7676
let deferCondition: CompilationResult.DeferCondition
7777
let typeName: String
78+
79+
/// Provides a hash value that is a combination of `path` and `deferCondition` values only, `typeName` is not
80+
/// included.
81+
///
82+
/// This is intended to be used when the selection set type does not matter, such as when generating a deferred
83+
/// fragment identifier which is shared amongst all child selection set types within a deferred fragment.
84+
///
85+
/// - Returns: Hash value of `path` and `deferCondition`.
86+
var pathDeferConditionHash: Int {
87+
var hasher = Hasher()
88+
89+
hasher.combine(path)
90+
hasher.combine(deferCondition)
91+
92+
return hasher.finalize()
93+
}
7894
}
7995

8096
fileprivate func DeferredFragmentsPathTypeInfo(
@@ -134,3 +150,10 @@ struct DeferredFragmentsMetadataTemplate {
134150
return deferredPathTypeInfo
135151
}
136152
}
153+
154+
fileprivate extension Sequence where Element == DeferredFragmentsMetadataTemplate.DeferredPathTypeInfo {
155+
func unique<T: Hashable>(by keyForValue: (Iterator.Element) throws -> T) rethrows -> [Iterator.Element] {
156+
var seen: Set<T> = []
157+
return try filter { try seen.insert(keyForValue($0)).inserted }
158+
}
159+
}

0 commit comments

Comments
 (0)