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:
-5
@@ -1,5 +0,0 @@
|
||||
package errors
|
||||
|
||||
const _ = `# @genqlient
|
||||
query MyQuery { i { f } }
|
||||
`
|
||||
@@ -1 +0,0 @@
|
||||
query MyQuery { i { f } }
|
||||
@@ -1,11 +0,0 @@
|
||||
type Query {
|
||||
i: I
|
||||
}
|
||||
|
||||
type T implements I {
|
||||
f: String!
|
||||
}
|
||||
|
||||
interface I {
|
||||
f: String!
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -2,13 +2,10 @@ query InterfaceNesting {
|
||||
root {
|
||||
id
|
||||
children {
|
||||
__typename
|
||||
id
|
||||
parent {
|
||||
__typename
|
||||
id
|
||||
children {
|
||||
__typename
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -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 }
|
||||
}
|
||||
|
||||
+3
-3
@@ -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"`
|
||||
|
||||
Vendored
-4
@@ -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
|
||||
|
||||
Vendored
+1
-1
@@ -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"
|
||||
}
|
||||
]
|
||||
|
||||
+92
-8
@@ -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
|
||||
|
||||
+1
-1
@@ -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"
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user