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
This commit is contained in:
Ben Kraft
2022-06-03 22:53:44 -07:00
committed by GitHub
parent 37fa3d6e7c
commit 520532eb65
4 changed files with 2452 additions and 0 deletions
@@ -0,0 +1,23 @@
query CovariantInterfaceImplementation {
randomItem {
id
next { ...ContentFields }
related { ...ContentFields }
}
root {
...ContentFields
...TopicFields
next { ...TopicFields }
related { ...TopicFields }
}
}
fragment ContentFields on Content {
next { id }
related { id }
}
fragment TopicFields on Topic {
next { id }
related { id }
}
+8
View File
@@ -84,6 +84,8 @@ interface Content {
name: String!
parent: Topic
url: String!
next: Content
related: [Content!]
}
"""An object with a duration, like a video."""
@@ -102,6 +104,8 @@ type Article implements Content {
url: String!
text: String!
thumbnail: StuffThumbnail
next: Content
related: [Content!]
}
type StuffThumbnail { # for articles, but let's give the name-generator a hard time.
@@ -117,6 +121,8 @@ type Video implements Content & HasDuration {
url: String!
duration: Int!
thumbnail: Thumbnail
next: Content
related: [Content!]
}
type Thumbnail { # for videos, but let's give the name-generator a hard time.
@@ -133,6 +139,8 @@ type Topic implements Content {
children: [Content!]!
videoChildren: [Video!]!
schoolGrade: String
next: Topic
related: [Topic!]
}
input RecursiveInput {
@@ -0,0 +1,9 @@
{
"operations": [
{
"operationName": "CovariantInterfaceImplementation",
"query": "\nquery CovariantInterfaceImplementation {\n\trandomItem {\n\t\t__typename\n\t\tid\n\t\tnext {\n\t\t\t__typename\n\t\t\t... ContentFields\n\t\t}\n\t\trelated {\n\t\t\t__typename\n\t\t\t... ContentFields\n\t\t}\n\t}\n\troot {\n\t\t... ContentFields\n\t\t... TopicFields\n\t\tnext {\n\t\t\t... TopicFields\n\t\t}\n\t\trelated {\n\t\t\t... TopicFields\n\t\t}\n\t}\n}\nfragment ContentFields on Content {\n\tnext {\n\t\t__typename\n\t\tid\n\t}\n\trelated {\n\t\t__typename\n\t\tid\n\t}\n}\nfragment TopicFields on Topic {\n\tnext {\n\t\tid\n\t}\n\trelated {\n\t\tid\n\t}\n}\n",
"sourceLocation": "testdata/queries/CovariantInterfaceImplementation.graphql"
}
]
}