Skip to content

Commit 520532e

Browse files
Add tests to check that genqlient handles covariance (#203)
I didn't realize until today that implementations of GraphQL interfaces are actually allowed to be covariant: if the interface has a field `f: T`, then the implementations may have fields `f: U` where `U` is a subtype of `T` (for example `U` may be an implementation of the interface `T`, or `U` may be `T!` if `T` is non-nullable. (I thought it had to be `f: T` exactly.) So I figured I'd add a test and see what breaks. Surprisingly, and despite the fact that Go interfaces do *not* allow covariance, everything... worked? There's at least one place where it's possible we could ideally use a more specific type [1], but for now I just wanted to make sure we at least write something that builds and is vaguely reasonable. Of course I'm not sure if there's anything I've missed (some day I need to find a fuzzing engine that can fuzz GraphQL). [1] Specifically, the field `CovariantInterfaceImplementationRandomItemTopic.Next` might ideally have type `...NextContentTopic`, not `...NextContent`; we know it's a topic. This doesn't directly cause covariance problems in Go: the method `GetNext` still returns `...NextContent` so the interface matches. But that trick doesn't work for the sibling field `.Related` which is slice-typed: or rather, we'd need the method to copy the slice to the correct type. (Not to mention the implemention of any change here would require a bunch of plumbing because the AST doesn't quite have what we want.) So it's probably best to just keep this as-is for simplicity and consistency. Test plan: make check
1 parent 37fa3d6 commit 520532e

4 files changed

+2452
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
query CovariantInterfaceImplementation {
2+
randomItem {
3+
id
4+
next { ...ContentFields }
5+
related { ...ContentFields }
6+
}
7+
root {
8+
...ContentFields
9+
...TopicFields
10+
next { ...TopicFields }
11+
related { ...TopicFields }
12+
}
13+
}
14+
15+
fragment ContentFields on Content {
16+
next { id }
17+
related { id }
18+
}
19+
20+
fragment TopicFields on Topic {
21+
next { id }
22+
related { id }
23+
}

generate/testdata/queries/schema.graphql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ interface Content {
8484
name: String!
8585
parent: Topic
8686
url: String!
87+
next: Content
88+
related: [Content!]
8789
}
8890

8991
"""An object with a duration, like a video."""
@@ -102,6 +104,8 @@ type Article implements Content {
102104
url: String!
103105
text: String!
104106
thumbnail: StuffThumbnail
107+
next: Content
108+
related: [Content!]
105109
}
106110

107111
type StuffThumbnail { # for articles, but let's give the name-generator a hard time.
@@ -117,6 +121,8 @@ type Video implements Content & HasDuration {
117121
url: String!
118122
duration: Int!
119123
thumbnail: Thumbnail
124+
next: Content
125+
related: [Content!]
120126
}
121127

122128
type Thumbnail { # for videos, but let's give the name-generator a hard time.
@@ -133,6 +139,8 @@ type Topic implements Content {
133139
children: [Content!]!
134140
videoChildren: [Video!]!
135141
schoolGrade: String
142+
next: Topic
143+
related: [Topic!]
136144
}
137145

138146
input RecursiveInput {

0 commit comments

Comments
 (0)