Add support for interfaces, part 3: automatically add __typename (#56)

## Summary:
In #52, I added support for interface types, but with the simplifying
restriction (among others) that the user must request the field
`__typename`.  In this commit, I remove this restriction.

The basic idea is simple: we preprocess the query to add `__typename`.
The implementation isn't much more complicated!  Although it required
some new wiring in a few places.

Issue: https://github.com/Khan/genqlient/issues/8

## Test plan:
make check


Author: benjaminjkraft

Reviewers: dnerdy, aberkan, MiguelCastillo

Required Reviewers: 

Approved by: dnerdy

Checks:  Test (1.17),  Test (1.16),  Test (1.15),  Test (1.14),  Test (1.13),  Lint,  Test (1.17),  Test (1.16),  Test (1.15),  Test (1.14),  Test (1.13),  Lint

Pull request URL: https://github.com/Khan/genqlient/pull/56
This commit is contained in:
Ben Kraft
2021-08-25 11:59:45 -07:00
committed by GitHub
parent 1e87553788
commit 65f9e90f2b
16 changed files with 189 additions and 62 deletions
-5
View File
@@ -1,5 +0,0 @@
package errors
const _ = `# @genqlient
query MyQuery { i { f } }
`
-1
View File
@@ -1 +0,0 @@
query MyQuery { i { f } }
-11
View File
@@ -1,11 +0,0 @@
type Query {
i: I
}
type T implements I {
f: String!
}
interface I {
f: String!
}
-2
View File
@@ -3,7 +3,6 @@ query InterfaceListField {
id
name
children {
__typename
id
name
}
@@ -13,7 +12,6 @@ query InterfaceListField {
id
name
children {
__typename
id
name
}
@@ -1,5 +1,5 @@
query InterfaceListOfListOfListsField {
listOfListsOfListsOfContent { __typename id name }
listOfListsOfListsOfContent { id name }
# @genqlient(pointer: true)
withPointer: listOfListsOfListsOfContent { __typename id name }
withPointer: listOfListsOfListsOfContent { id name }
}
-3
View File
@@ -2,13 +2,10 @@ query InterfaceNesting {
root {
id
children {
__typename
id
parent {
__typename
id
children {
__typename
id
}
}
+3 -2
View File
@@ -1,6 +1,7 @@
query InterfaceNoFragmentsQuery {
root { id name } # (make sure sibling fields work)
randomItem { __typename id name }
randomItem { id name }
randomItemWithTypeName: randomItem { __typename id name }
# @genqlient(pointer: true)
withPointer: randomItem { __typename id name }
withPointer: randomItem { id name }
}
@@ -160,7 +160,7 @@ func (v *InterfaceListOfListOfListsFieldResponse) UnmarshalJSON(b []byte) error
// InterfaceListOfListOfListsFieldWithPointerArticle includes the requested fields of the GraphQL type Article.
type InterfaceListOfListOfListsFieldWithPointerArticle struct {
Typename *string `json:"__typename"`
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id *testutil.ID `json:"id"`
Name *string `json:"name"`
@@ -212,7 +212,7 @@ func __unmarshalInterfaceListOfListOfListsFieldWithPointerContent(v *InterfaceLi
// InterfaceListOfListOfListsFieldWithPointerTopic includes the requested fields of the GraphQL type Topic.
type InterfaceListOfListOfListsFieldWithPointerTopic struct {
Typename *string `json:"__typename"`
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id *testutil.ID `json:"id"`
Name *string `json:"name"`
@@ -220,7 +220,7 @@ type InterfaceListOfListOfListsFieldWithPointerTopic struct {
// InterfaceListOfListOfListsFieldWithPointerVideo includes the requested fields of the GraphQL type Video.
type InterfaceListOfListOfListsFieldWithPointerVideo struct {
Typename *string `json:"__typename"`
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id *testutil.ID `json:"id"`
Name *string `json:"name"`
@@ -65,7 +65,6 @@ type InterfaceNestingRootTopicChildrenArticle struct {
// InterfaceNestingRootTopicChildrenArticleParentTopic includes the requested fields of the GraphQL type Topic.
type InterfaceNestingRootTopicChildrenArticleParentTopic struct {
Typename string `json:"__typename"`
// ID is documented in the Content interface.
Id testutil.ID `json:"id"`
Children []InterfaceNestingRootTopicChildrenArticleParentTopicChildrenContent `json:"-"`
@@ -223,7 +222,6 @@ type InterfaceNestingRootTopicChildrenTopic struct {
// InterfaceNestingRootTopicChildrenTopicParentTopic includes the requested fields of the GraphQL type Topic.
type InterfaceNestingRootTopicChildrenTopicParentTopic struct {
Typename string `json:"__typename"`
// ID is documented in the Content interface.
Id testutil.ID `json:"id"`
Children []InterfaceNestingRootTopicChildrenTopicParentTopicChildrenContent `json:"-"`
@@ -337,7 +335,6 @@ type InterfaceNestingRootTopicChildrenVideo struct {
// InterfaceNestingRootTopicChildrenVideoParentTopic includes the requested fields of the GraphQL type Topic.
type InterfaceNestingRootTopicChildrenVideoParentTopic struct {
Typename string `json:"__typename"`
// ID is documented in the Content interface.
Id testutil.ID `json:"id"`
Children []InterfaceNestingRootTopicChildrenVideoParentTopicChildrenContent `json:"-"`
@@ -456,7 +453,6 @@ query InterfaceNesting {
__typename
id
parent {
__typename
id
children {
__typename
@@ -2,7 +2,7 @@
"operations": [
{
"operationName": "InterfaceNesting",
"query": "\nquery InterfaceNesting {\n\troot {\n\t\tid\n\t\tchildren {\n\t\t\t__typename\n\t\t\tid\n\t\t\tparent {\n\t\t\t\t__typename\n\t\t\t\tid\n\t\t\t\tchildren {\n\t\t\t\t\t__typename\n\t\t\t\t\tid\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n",
"query": "\nquery InterfaceNesting {\n\troot {\n\t\tid\n\t\tchildren {\n\t\t\t__typename\n\t\t\tid\n\t\t\tparent {\n\t\t\t\tid\n\t\t\t\tchildren {\n\t\t\t\t\t__typename\n\t\t\t\t\tid\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n",
"sourceLocation": "testdata/queries/InterfaceNesting.graphql"
}
]
@@ -78,11 +78,80 @@ type InterfaceNoFragmentsQueryRandomItemVideo struct {
Name string `json:"name"`
}
// InterfaceNoFragmentsQueryRandomItemWithTypeNameArticle includes the requested fields of the GraphQL type Article.
type InterfaceNoFragmentsQueryRandomItemWithTypeNameArticle struct {
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id testutil.ID `json:"id"`
Name string `json:"name"`
}
// InterfaceNoFragmentsQueryRandomItemWithTypeNameContent includes the requested fields of the GraphQL type Content.
// The GraphQL type's documentation follows.
//
// Content is implemented by various types like Article, Video, and Topic.
type InterfaceNoFragmentsQueryRandomItemWithTypeNameContent interface {
implementsGraphQLInterfaceInterfaceNoFragmentsQueryRandomItemWithTypeNameContent()
}
func (v *InterfaceNoFragmentsQueryRandomItemWithTypeNameArticle) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRandomItemWithTypeNameContent() {
}
func (v *InterfaceNoFragmentsQueryRandomItemWithTypeNameVideo) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRandomItemWithTypeNameContent() {
}
func (v *InterfaceNoFragmentsQueryRandomItemWithTypeNameTopic) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRandomItemWithTypeNameContent() {
}
func __unmarshalInterfaceNoFragmentsQueryRandomItemWithTypeNameContent(v *InterfaceNoFragmentsQueryRandomItemWithTypeNameContent, m json.RawMessage) error {
if string(m) == "null" {
return nil
}
var tn struct {
TypeName string `json:"__typename"`
}
err := json.Unmarshal(m, &tn)
if err != nil {
return err
}
switch tn.TypeName {
case "Article":
*v = new(InterfaceNoFragmentsQueryRandomItemWithTypeNameArticle)
return json.Unmarshal(m, *v)
case "Video":
*v = new(InterfaceNoFragmentsQueryRandomItemWithTypeNameVideo)
return json.Unmarshal(m, *v)
case "Topic":
*v = new(InterfaceNoFragmentsQueryRandomItemWithTypeNameTopic)
return json.Unmarshal(m, *v)
default:
return fmt.Errorf(
`Unexpected concrete type for InterfaceNoFragmentsQueryRandomItemWithTypeNameContent: "%v"`, tn.TypeName)
}
}
// InterfaceNoFragmentsQueryRandomItemWithTypeNameTopic includes the requested fields of the GraphQL type Topic.
type InterfaceNoFragmentsQueryRandomItemWithTypeNameTopic struct {
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id testutil.ID `json:"id"`
Name string `json:"name"`
}
// InterfaceNoFragmentsQueryRandomItemWithTypeNameVideo includes the requested fields of the GraphQL type Video.
type InterfaceNoFragmentsQueryRandomItemWithTypeNameVideo struct {
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id testutil.ID `json:"id"`
Name string `json:"name"`
}
// InterfaceNoFragmentsQueryResponse is returned by InterfaceNoFragmentsQuery on success.
type InterfaceNoFragmentsQueryResponse struct {
Root InterfaceNoFragmentsQueryRootTopic `json:"root"`
RandomItem InterfaceNoFragmentsQueryRandomItemContent `json:"-"`
WithPointer *InterfaceNoFragmentsQueryWithPointerContent `json:"-"`
Root InterfaceNoFragmentsQueryRootTopic `json:"root"`
RandomItem InterfaceNoFragmentsQueryRandomItemContent `json:"-"`
RandomItemWithTypeName InterfaceNoFragmentsQueryRandomItemWithTypeNameContent `json:"-"`
WithPointer *InterfaceNoFragmentsQueryWithPointerContent `json:"-"`
}
func (v *InterfaceNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
@@ -91,8 +160,9 @@ func (v *InterfaceNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
var firstPass struct {
*InterfaceNoFragmentsQueryResponseWrapper
RandomItem json.RawMessage `json:"randomItem"`
WithPointer json.RawMessage `json:"withPointer"`
RandomItem json.RawMessage `json:"randomItem"`
RandomItemWithTypeName json.RawMessage `json:"randomItemWithTypeName"`
WithPointer json.RawMessage `json:"withPointer"`
}
firstPass.InterfaceNoFragmentsQueryResponseWrapper = (*InterfaceNoFragmentsQueryResponseWrapper)(v)
@@ -110,6 +180,15 @@ func (v *InterfaceNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
return err
}
}
{
target := &v.RandomItemWithTypeName
raw := firstPass.RandomItemWithTypeName
err = __unmarshalInterfaceNoFragmentsQueryRandomItemWithTypeNameContent(
target, raw)
if err != nil {
return err
}
}
{
target := &v.WithPointer
raw := firstPass.WithPointer
@@ -132,7 +211,7 @@ type InterfaceNoFragmentsQueryRootTopic struct {
// InterfaceNoFragmentsQueryWithPointerArticle includes the requested fields of the GraphQL type Article.
type InterfaceNoFragmentsQueryWithPointerArticle struct {
Typename *string `json:"__typename"`
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id *testutil.ID `json:"id"`
Name *string `json:"name"`
@@ -184,7 +263,7 @@ func __unmarshalInterfaceNoFragmentsQueryWithPointerContent(v *InterfaceNoFragme
// InterfaceNoFragmentsQueryWithPointerTopic includes the requested fields of the GraphQL type Topic.
type InterfaceNoFragmentsQueryWithPointerTopic struct {
Typename *string `json:"__typename"`
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id *testutil.ID `json:"id"`
Name *string `json:"name"`
@@ -192,7 +271,7 @@ type InterfaceNoFragmentsQueryWithPointerTopic struct {
// InterfaceNoFragmentsQueryWithPointerVideo includes the requested fields of the GraphQL type Video.
type InterfaceNoFragmentsQueryWithPointerVideo struct {
Typename *string `json:"__typename"`
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id *testutil.ID `json:"id"`
Name *string `json:"name"`
@@ -216,6 +295,11 @@ query InterfaceNoFragmentsQuery {
id
name
}
randomItemWithTypeName: randomItem {
__typename
id
name
}
withPointer: randomItem {
__typename
id
@@ -2,7 +2,7 @@
"operations": [
{
"operationName": "InterfaceNoFragmentsQuery",
"query": "\nquery InterfaceNoFragmentsQuery {\n\troot {\n\t\tid\n\t\tname\n\t}\n\trandomItem {\n\t\t__typename\n\t\tid\n\t\tname\n\t}\n\twithPointer: randomItem {\n\t\t__typename\n\t\tid\n\t\tname\n\t}\n}\n",
"query": "\nquery InterfaceNoFragmentsQuery {\n\troot {\n\t\tid\n\t\tname\n\t}\n\trandomItem {\n\t\t__typename\n\t\tid\n\t\tname\n\t}\n\trandomItemWithTypeName: randomItem {\n\t\t__typename\n\t\tid\n\t\tname\n\t}\n\twithPointer: randomItem {\n\t\t__typename\n\t\tid\n\t\tname\n\t}\n}\n",
"sourceLocation": "testdata/queries/InterfaceNoFragments.graphql"
}
]