Add support for interfaces, part 2: list-of-interface (#54)

## Summary:
In this commit I remove one of the limitations of our support for
interfaces, from #52, by adding support for list-of-interface fields.
This was surprisingly complex!  The issue is that, as before, it's the
containing type that has to do all the glue work -- and it's that glue
work that is complicated by list-of-interface fields.

All in all, it's not that much new code, and by far the hard part is
just 20 lines in the UnmarshalJSON template (which come with almost
twice as many lines of comments to explain them).  It may be easiest to
start by reading some of the generated code, and then read the template.

I also added support for such fields with `pointer: true` specified,
such that the type is `[][]...[]*MyInterface`, although I don't know why
you would want that.  This does *not* allow e.g. `*[]*[][]*MyInterface`;
that would require a way to specify it (see #16) but also add some extra
complexity (as we'd have to actually walk the type-unwrap chain
properly, instead of just counting the number of slices and whether
there's a pointer).

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

## Test plan:
make check


Author: benjaminjkraft

Reviewers: dnerdy, benjaminjkraft, aberkan, csilvers, MiguelCastillo

Required Reviewers: 

Approved by: dnerdy

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

Pull request URL: https://github.com/Khan/genqlient/pull/54
This commit is contained in:
Ben Kraft
2021-08-25 11:57:24 -07:00
committed by GitHub
parent 4c38cb7759
commit 1e87553788
22 changed files with 1310 additions and 77 deletions
-4
View File
@@ -71,10 +71,6 @@ func TestGenerate(t *testing.T) {
t.Run("Build", func(t *testing.T) {
if testing.Short() {
t.Skip("skipping build due to -short")
} else if sourceFilename == "InterfaceNesting.graphql" ||
sourceFilename == "InterfaceListField.graphql" {
t.Skip("TODO: enable after fixing " +
"https://github.com/Khan/genqlient/issues/8")
} else if sourceFilename == "Omitempty.graphql" {
t.Skip("TODO: enable after fixing " +
"https://github.com/Khan/genqlient/issues/43")
+23 -1
View File
@@ -4,6 +4,7 @@ import (
"io"
"path/filepath"
"runtime"
"strings"
"text/template"
)
@@ -12,13 +13,34 @@ var (
thisDir = filepath.Dir(thisFilename)
)
func repeat(n int, s string) string {
var builder strings.Builder
for i := 0; i < n; i++ {
builder.WriteString(s)
}
return builder.String()
}
func intRange(n int) []int {
ret := make([]int, n)
for i := 0; i < n; i++ {
ret[i] = i
}
return ret
}
func sub(x, y int) int { return x - y }
// execute executes the given template with the funcs from this generator.
func (g *generator) execute(tmplRelFilename string, w io.Writer, data interface{}) error {
tmpl := g.templateCache[tmplRelFilename]
if tmpl == nil {
absFilename := filepath.Join(thisDir, tmplRelFilename)
funcMap := template.FuncMap{
"ref": g.ref,
"ref": g.ref,
"repeat": repeat,
"intRange": intRange,
"sub": sub,
}
var err error
tmpl, err = template.New(tmplRelFilename).Funcs(funcMap).ParseFiles(absFilename)
+11 -1
View File
@@ -1,4 +1,4 @@
query InterfaceNoFragmentsQuery {
query InterfaceListField {
root {
id
name
@@ -8,4 +8,14 @@ query InterfaceNoFragmentsQuery {
name
}
}
# @genqlient(pointer: true)
withPointer: root {
id
name
children {
__typename
id
name
}
}
}
@@ -0,0 +1,5 @@
query InterfaceListOfListOfListsField {
listOfListsOfListsOfContent { __typename id name }
# @genqlient(pointer: true)
withPointer: listOfListsOfListsOfContent { __typename id name }
}
@@ -1,4 +1,6 @@
query InterfaceNoFragmentsQuery {
root { id name } # (make sure sibling fields work)
randomItem { __typename id name }
# @genqlient(pointer: true)
withPointer: randomItem { __typename id name }
}
+1
View File
@@ -111,6 +111,7 @@ type Query {
getJunk: Junk
getComplexJunk: ComplexJunk
listOfListsOfLists: [[[String!]!]!]!
listOfListsOfListsOfContent: [[[Content!]!]!]!
}
type Mutation {
@@ -10,43 +10,77 @@ import (
"github.com/Khan/genqlient/internal/testutil"
)
// InterfaceNoFragmentsQueryResponse is returned by InterfaceNoFragmentsQuery on success.
type InterfaceNoFragmentsQueryResponse struct {
Root InterfaceNoFragmentsQueryRootTopic `json:"root"`
// InterfaceListFieldResponse is returned by InterfaceListField on success.
type InterfaceListFieldResponse struct {
Root InterfaceListFieldRootTopic `json:"root"`
WithPointer *InterfaceListFieldWithPointerTopic `json:"withPointer"`
}
// InterfaceNoFragmentsQueryRootTopic includes the requested fields of the GraphQL type Topic.
type InterfaceNoFragmentsQueryRootTopic struct {
// InterfaceListFieldRootTopic includes the requested fields of the GraphQL type Topic.
type InterfaceListFieldRootTopic struct {
// ID is documented in the Content interface.
Id testutil.ID `json:"id"`
Name string `json:"name"`
Children []InterfaceNoFragmentsQueryRootTopicChildrenContent `json:"children"`
Id testutil.ID `json:"id"`
Name string `json:"name"`
Children []InterfaceListFieldRootTopicChildrenContent `json:"-"`
}
// InterfaceNoFragmentsQueryRootTopicChildrenArticle includes the requested fields of the GraphQL type Article.
type InterfaceNoFragmentsQueryRootTopicChildrenArticle struct {
func (v *InterfaceListFieldRootTopic) UnmarshalJSON(b []byte) error {
type InterfaceListFieldRootTopicWrapper InterfaceListFieldRootTopic
var firstPass struct {
*InterfaceListFieldRootTopicWrapper
Children []json.RawMessage `json:"children"`
}
firstPass.InterfaceListFieldRootTopicWrapper = (*InterfaceListFieldRootTopicWrapper)(v)
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
{
target := &v.Children
raw := firstPass.Children
*target = make(
[]InterfaceListFieldRootTopicChildrenContent,
len(raw))
for i, raw := range raw {
target := &(*target)[i]
err = __unmarshalInterfaceListFieldRootTopicChildrenContent(
target, raw)
if err != nil {
return err
}
}
}
return nil
}
// InterfaceListFieldRootTopicChildrenArticle includes the requested fields of the GraphQL type Article.
type InterfaceListFieldRootTopicChildrenArticle struct {
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id testutil.ID `json:"id"`
Name string `json:"name"`
}
// InterfaceNoFragmentsQueryRootTopicChildrenContent includes the requested fields of the GraphQL type Content.
// InterfaceListFieldRootTopicChildrenContent 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 InterfaceNoFragmentsQueryRootTopicChildrenContent interface {
implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent()
type InterfaceListFieldRootTopicChildrenContent interface {
implementsGraphQLInterfaceInterfaceListFieldRootTopicChildrenContent()
}
func (v *InterfaceNoFragmentsQueryRootTopicChildrenArticle) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent() {
func (v *InterfaceListFieldRootTopicChildrenArticle) implementsGraphQLInterfaceInterfaceListFieldRootTopicChildrenContent() {
}
func (v *InterfaceNoFragmentsQueryRootTopicChildrenVideo) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent() {
func (v *InterfaceListFieldRootTopicChildrenVideo) implementsGraphQLInterfaceInterfaceListFieldRootTopicChildrenContent() {
}
func (v *InterfaceNoFragmentsQueryRootTopicChildrenTopic) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent() {
func (v *InterfaceListFieldRootTopicChildrenTopic) implementsGraphQLInterfaceInterfaceListFieldRootTopicChildrenContent() {
}
func __unmarshalInterfaceNoFragmentsQueryRootTopicChildrenContent(v *InterfaceNoFragmentsQueryRootTopicChildrenContent, m json.RawMessage) error {
func __unmarshalInterfaceListFieldRootTopicChildrenContent(v *InterfaceListFieldRootTopicChildrenContent, m json.RawMessage) error {
if string(m) == "null" {
return nil
}
@@ -61,45 +95,154 @@ func __unmarshalInterfaceNoFragmentsQueryRootTopicChildrenContent(v *InterfaceNo
switch tn.TypeName {
case "Article":
*v = new(InterfaceNoFragmentsQueryRootTopicChildrenArticle)
*v = new(InterfaceListFieldRootTopicChildrenArticle)
return json.Unmarshal(m, *v)
case "Video":
*v = new(InterfaceNoFragmentsQueryRootTopicChildrenVideo)
*v = new(InterfaceListFieldRootTopicChildrenVideo)
return json.Unmarshal(m, *v)
case "Topic":
*v = new(InterfaceNoFragmentsQueryRootTopicChildrenTopic)
*v = new(InterfaceListFieldRootTopicChildrenTopic)
return json.Unmarshal(m, *v)
default:
return fmt.Errorf(
`Unexpected concrete type for InterfaceNoFragmentsQueryRootTopicChildrenContent: "%v"`, tn.TypeName)
`Unexpected concrete type for InterfaceListFieldRootTopicChildrenContent: "%v"`, tn.TypeName)
}
}
// InterfaceNoFragmentsQueryRootTopicChildrenTopic includes the requested fields of the GraphQL type Topic.
type InterfaceNoFragmentsQueryRootTopicChildrenTopic struct {
// InterfaceListFieldRootTopicChildrenTopic includes the requested fields of the GraphQL type Topic.
type InterfaceListFieldRootTopicChildrenTopic struct {
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id testutil.ID `json:"id"`
Name string `json:"name"`
}
// InterfaceNoFragmentsQueryRootTopicChildrenVideo includes the requested fields of the GraphQL type Video.
type InterfaceNoFragmentsQueryRootTopicChildrenVideo struct {
// InterfaceListFieldRootTopicChildrenVideo includes the requested fields of the GraphQL type Video.
type InterfaceListFieldRootTopicChildrenVideo struct {
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id testutil.ID `json:"id"`
Name string `json:"name"`
}
func InterfaceNoFragmentsQuery(
// InterfaceListFieldWithPointerTopic includes the requested fields of the GraphQL type Topic.
type InterfaceListFieldWithPointerTopic struct {
// ID is documented in the Content interface.
Id testutil.ID `json:"id"`
Name string `json:"name"`
Children []InterfaceListFieldWithPointerTopicChildrenContent `json:"-"`
}
func (v *InterfaceListFieldWithPointerTopic) UnmarshalJSON(b []byte) error {
type InterfaceListFieldWithPointerTopicWrapper InterfaceListFieldWithPointerTopic
var firstPass struct {
*InterfaceListFieldWithPointerTopicWrapper
Children []json.RawMessage `json:"children"`
}
firstPass.InterfaceListFieldWithPointerTopicWrapper = (*InterfaceListFieldWithPointerTopicWrapper)(v)
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
{
target := &v.Children
raw := firstPass.Children
*target = make(
[]InterfaceListFieldWithPointerTopicChildrenContent,
len(raw))
for i, raw := range raw {
target := &(*target)[i]
err = __unmarshalInterfaceListFieldWithPointerTopicChildrenContent(
target, raw)
if err != nil {
return err
}
}
}
return nil
}
// InterfaceListFieldWithPointerTopicChildrenArticle includes the requested fields of the GraphQL type Article.
type InterfaceListFieldWithPointerTopicChildrenArticle struct {
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id testutil.ID `json:"id"`
Name string `json:"name"`
}
// InterfaceListFieldWithPointerTopicChildrenContent 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 InterfaceListFieldWithPointerTopicChildrenContent interface {
implementsGraphQLInterfaceInterfaceListFieldWithPointerTopicChildrenContent()
}
func (v *InterfaceListFieldWithPointerTopicChildrenArticle) implementsGraphQLInterfaceInterfaceListFieldWithPointerTopicChildrenContent() {
}
func (v *InterfaceListFieldWithPointerTopicChildrenVideo) implementsGraphQLInterfaceInterfaceListFieldWithPointerTopicChildrenContent() {
}
func (v *InterfaceListFieldWithPointerTopicChildrenTopic) implementsGraphQLInterfaceInterfaceListFieldWithPointerTopicChildrenContent() {
}
func __unmarshalInterfaceListFieldWithPointerTopicChildrenContent(v *InterfaceListFieldWithPointerTopicChildrenContent, 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(InterfaceListFieldWithPointerTopicChildrenArticle)
return json.Unmarshal(m, *v)
case "Video":
*v = new(InterfaceListFieldWithPointerTopicChildrenVideo)
return json.Unmarshal(m, *v)
case "Topic":
*v = new(InterfaceListFieldWithPointerTopicChildrenTopic)
return json.Unmarshal(m, *v)
default:
return fmt.Errorf(
`Unexpected concrete type for InterfaceListFieldWithPointerTopicChildrenContent: "%v"`, tn.TypeName)
}
}
// InterfaceListFieldWithPointerTopicChildrenTopic includes the requested fields of the GraphQL type Topic.
type InterfaceListFieldWithPointerTopicChildrenTopic struct {
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id testutil.ID `json:"id"`
Name string `json:"name"`
}
// InterfaceListFieldWithPointerTopicChildrenVideo includes the requested fields of the GraphQL type Video.
type InterfaceListFieldWithPointerTopicChildrenVideo struct {
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id testutil.ID `json:"id"`
Name string `json:"name"`
}
func InterfaceListField(
client graphql.Client,
) (*InterfaceNoFragmentsQueryResponse, error) {
var retval InterfaceNoFragmentsQueryResponse
) (*InterfaceListFieldResponse, error) {
var retval InterfaceListFieldResponse
err := client.MakeRequest(
nil,
"InterfaceNoFragmentsQuery",
"InterfaceListField",
`
query InterfaceNoFragmentsQuery {
query InterfaceListField {
root {
id
name
@@ -109,6 +252,15 @@ query InterfaceNoFragmentsQuery {
name
}
}
withPointer: root {
id
name
children {
__typename
id
name
}
}
}
`,
&retval,
@@ -1,8 +1,8 @@
{
"operations": [
{
"operationName": "InterfaceNoFragmentsQuery",
"query": "\nquery InterfaceNoFragmentsQuery {\n\troot {\n\t\tid\n\t\tname\n\t\tchildren {\n\t\t\t__typename\n\t\t\tid\n\t\t\tname\n\t\t}\n\t}\n}\n",
"operationName": "InterfaceListField",
"query": "\nquery InterfaceListField {\n\troot {\n\t\tid\n\t\tname\n\t\tchildren {\n\t\t\t__typename\n\t\t\tid\n\t\t\tname\n\t\t}\n\t}\n\twithPointer: root {\n\t\tid\n\t\tname\n\t\tchildren {\n\t\t\t__typename\n\t\t\tid\n\t\t\tname\n\t\t}\n\t}\n}\n",
"sourceLocation": "testdata/queries/InterfaceListField.graphql"
}
]
@@ -0,0 +1,255 @@
package test
// Code generated by github.com/Khan/genqlient, DO NOT EDIT.
import (
"encoding/json"
"fmt"
"github.com/Khan/genqlient/graphql"
"github.com/Khan/genqlient/internal/testutil"
)
// InterfaceListOfListOfListsFieldListOfListsOfListsOfContent 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 InterfaceListOfListOfListsFieldListOfListsOfListsOfContent interface {
implementsGraphQLInterfaceInterfaceListOfListOfListsFieldListOfListsOfListsOfContent()
}
func (v *InterfaceListOfListOfListsFieldListOfListsOfListsOfContentArticle) implementsGraphQLInterfaceInterfaceListOfListOfListsFieldListOfListsOfListsOfContent() {
}
func (v *InterfaceListOfListOfListsFieldListOfListsOfListsOfContentVideo) implementsGraphQLInterfaceInterfaceListOfListOfListsFieldListOfListsOfListsOfContent() {
}
func (v *InterfaceListOfListOfListsFieldListOfListsOfListsOfContentTopic) implementsGraphQLInterfaceInterfaceListOfListOfListsFieldListOfListsOfListsOfContent() {
}
func __unmarshalInterfaceListOfListOfListsFieldListOfListsOfListsOfContent(v *InterfaceListOfListOfListsFieldListOfListsOfListsOfContent, 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(InterfaceListOfListOfListsFieldListOfListsOfListsOfContentArticle)
return json.Unmarshal(m, *v)
case "Video":
*v = new(InterfaceListOfListOfListsFieldListOfListsOfListsOfContentVideo)
return json.Unmarshal(m, *v)
case "Topic":
*v = new(InterfaceListOfListOfListsFieldListOfListsOfListsOfContentTopic)
return json.Unmarshal(m, *v)
default:
return fmt.Errorf(
`Unexpected concrete type for InterfaceListOfListOfListsFieldListOfListsOfListsOfContent: "%v"`, tn.TypeName)
}
}
// InterfaceListOfListOfListsFieldListOfListsOfListsOfContentArticle includes the requested fields of the GraphQL type Article.
type InterfaceListOfListOfListsFieldListOfListsOfListsOfContentArticle struct {
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id testutil.ID `json:"id"`
Name string `json:"name"`
}
// InterfaceListOfListOfListsFieldListOfListsOfListsOfContentTopic includes the requested fields of the GraphQL type Topic.
type InterfaceListOfListOfListsFieldListOfListsOfListsOfContentTopic struct {
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id testutil.ID `json:"id"`
Name string `json:"name"`
}
// InterfaceListOfListOfListsFieldListOfListsOfListsOfContentVideo includes the requested fields of the GraphQL type Video.
type InterfaceListOfListOfListsFieldListOfListsOfListsOfContentVideo struct {
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id testutil.ID `json:"id"`
Name string `json:"name"`
}
// InterfaceListOfListOfListsFieldResponse is returned by InterfaceListOfListOfListsField on success.
type InterfaceListOfListOfListsFieldResponse struct {
ListOfListsOfListsOfContent [][][]InterfaceListOfListOfListsFieldListOfListsOfListsOfContent `json:"-"`
WithPointer [][][]*InterfaceListOfListOfListsFieldWithPointerContent `json:"-"`
}
func (v *InterfaceListOfListOfListsFieldResponse) UnmarshalJSON(b []byte) error {
type InterfaceListOfListOfListsFieldResponseWrapper InterfaceListOfListOfListsFieldResponse
var firstPass struct {
*InterfaceListOfListOfListsFieldResponseWrapper
ListOfListsOfListsOfContent [][][]json.RawMessage `json:"listOfListsOfListsOfContent"`
WithPointer [][][]json.RawMessage `json:"withPointer"`
}
firstPass.InterfaceListOfListOfListsFieldResponseWrapper = (*InterfaceListOfListOfListsFieldResponseWrapper)(v)
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
{
target := &v.ListOfListsOfListsOfContent
raw := firstPass.ListOfListsOfListsOfContent
*target = make(
[][][]InterfaceListOfListOfListsFieldListOfListsOfListsOfContent,
len(raw))
for i, raw := range raw {
target := &(*target)[i]
*target = make(
[][]InterfaceListOfListOfListsFieldListOfListsOfListsOfContent,
len(raw))
for i, raw := range raw {
target := &(*target)[i]
*target = make(
[]InterfaceListOfListOfListsFieldListOfListsOfListsOfContent,
len(raw))
for i, raw := range raw {
target := &(*target)[i]
err = __unmarshalInterfaceListOfListOfListsFieldListOfListsOfListsOfContent(
target, raw)
if err != nil {
return err
}
}
}
}
}
{
target := &v.WithPointer
raw := firstPass.WithPointer
*target = make(
[][][]*InterfaceListOfListOfListsFieldWithPointerContent,
len(raw))
for i, raw := range raw {
target := &(*target)[i]
*target = make(
[][]*InterfaceListOfListOfListsFieldWithPointerContent,
len(raw))
for i, raw := range raw {
target := &(*target)[i]
*target = make(
[]*InterfaceListOfListOfListsFieldWithPointerContent,
len(raw))
for i, raw := range raw {
target := &(*target)[i]
*target = new(InterfaceListOfListOfListsFieldWithPointerContent)
err = __unmarshalInterfaceListOfListOfListsFieldWithPointerContent(
*target, raw)
if err != nil {
return err
}
}
}
}
}
return nil
}
// InterfaceListOfListOfListsFieldWithPointerArticle includes the requested fields of the GraphQL type Article.
type InterfaceListOfListOfListsFieldWithPointerArticle struct {
Typename *string `json:"__typename"`
// ID is the identifier of the content.
Id *testutil.ID `json:"id"`
Name *string `json:"name"`
}
// InterfaceListOfListOfListsFieldWithPointerContent 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 InterfaceListOfListOfListsFieldWithPointerContent interface {
implementsGraphQLInterfaceInterfaceListOfListOfListsFieldWithPointerContent()
}
func (v *InterfaceListOfListOfListsFieldWithPointerArticle) implementsGraphQLInterfaceInterfaceListOfListOfListsFieldWithPointerContent() {
}
func (v *InterfaceListOfListOfListsFieldWithPointerVideo) implementsGraphQLInterfaceInterfaceListOfListOfListsFieldWithPointerContent() {
}
func (v *InterfaceListOfListOfListsFieldWithPointerTopic) implementsGraphQLInterfaceInterfaceListOfListOfListsFieldWithPointerContent() {
}
func __unmarshalInterfaceListOfListOfListsFieldWithPointerContent(v *InterfaceListOfListOfListsFieldWithPointerContent, 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(InterfaceListOfListOfListsFieldWithPointerArticle)
return json.Unmarshal(m, *v)
case "Video":
*v = new(InterfaceListOfListOfListsFieldWithPointerVideo)
return json.Unmarshal(m, *v)
case "Topic":
*v = new(InterfaceListOfListOfListsFieldWithPointerTopic)
return json.Unmarshal(m, *v)
default:
return fmt.Errorf(
`Unexpected concrete type for InterfaceListOfListOfListsFieldWithPointerContent: "%v"`, tn.TypeName)
}
}
// InterfaceListOfListOfListsFieldWithPointerTopic includes the requested fields of the GraphQL type Topic.
type InterfaceListOfListOfListsFieldWithPointerTopic struct {
Typename *string `json:"__typename"`
// ID is the identifier of the content.
Id *testutil.ID `json:"id"`
Name *string `json:"name"`
}
// InterfaceListOfListOfListsFieldWithPointerVideo includes the requested fields of the GraphQL type Video.
type InterfaceListOfListOfListsFieldWithPointerVideo struct {
Typename *string `json:"__typename"`
// ID is the identifier of the content.
Id *testutil.ID `json:"id"`
Name *string `json:"name"`
}
func InterfaceListOfListOfListsField(
client graphql.Client,
) (*InterfaceListOfListOfListsFieldResponse, error) {
var retval InterfaceListOfListOfListsFieldResponse
err := client.MakeRequest(
nil,
"InterfaceListOfListOfListsField",
`
query InterfaceListOfListOfListsField {
listOfListsOfListsOfContent {
__typename
id
name
}
withPointer: listOfListsOfListsOfContent {
__typename
id
name
}
}
`,
&retval,
nil,
)
return &retval, err
}
@@ -0,0 +1,9 @@
{
"operations": [
{
"operationName": "InterfaceListOfListOfListsField",
"query": "\nquery InterfaceListOfListOfListsField {\n\tlistOfListsOfListsOfContent {\n\t\t__typename\n\t\tid\n\t\tname\n\t}\n\twithPointer: listOfListsOfListsOfContent {\n\t\t__typename\n\t\tid\n\t\tname\n\t}\n}\n",
"sourceLocation": "testdata/queries/InterfaceListOfListsOfListsField.graphql"
}
]
}
@@ -19,7 +19,40 @@ type InterfaceNestingResponse struct {
type InterfaceNestingRootTopic struct {
// ID is documented in the Content interface.
Id testutil.ID `json:"id"`
Children []InterfaceNestingRootTopicChildrenContent `json:"children"`
Children []InterfaceNestingRootTopicChildrenContent `json:"-"`
}
func (v *InterfaceNestingRootTopic) UnmarshalJSON(b []byte) error {
type InterfaceNestingRootTopicWrapper InterfaceNestingRootTopic
var firstPass struct {
*InterfaceNestingRootTopicWrapper
Children []json.RawMessage `json:"children"`
}
firstPass.InterfaceNestingRootTopicWrapper = (*InterfaceNestingRootTopicWrapper)(v)
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
{
target := &v.Children
raw := firstPass.Children
*target = make(
[]InterfaceNestingRootTopicChildrenContent,
len(raw))
for i, raw := range raw {
target := &(*target)[i]
err = __unmarshalInterfaceNestingRootTopicChildrenContent(
target, raw)
if err != nil {
return err
}
}
}
return nil
}
// InterfaceNestingRootTopicChildrenArticle includes the requested fields of the GraphQL type Article.
@@ -35,7 +68,40 @@ type InterfaceNestingRootTopicChildrenArticleParentTopic struct {
Typename string `json:"__typename"`
// ID is documented in the Content interface.
Id testutil.ID `json:"id"`
Children []InterfaceNestingRootTopicChildrenArticleParentTopicChildrenContent `json:"children"`
Children []InterfaceNestingRootTopicChildrenArticleParentTopicChildrenContent `json:"-"`
}
func (v *InterfaceNestingRootTopicChildrenArticleParentTopic) UnmarshalJSON(b []byte) error {
type InterfaceNestingRootTopicChildrenArticleParentTopicWrapper InterfaceNestingRootTopicChildrenArticleParentTopic
var firstPass struct {
*InterfaceNestingRootTopicChildrenArticleParentTopicWrapper
Children []json.RawMessage `json:"children"`
}
firstPass.InterfaceNestingRootTopicChildrenArticleParentTopicWrapper = (*InterfaceNestingRootTopicChildrenArticleParentTopicWrapper)(v)
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
{
target := &v.Children
raw := firstPass.Children
*target = make(
[]InterfaceNestingRootTopicChildrenArticleParentTopicChildrenContent,
len(raw))
for i, raw := range raw {
target := &(*target)[i]
err = __unmarshalInterfaceNestingRootTopicChildrenArticleParentTopicChildrenContent(
target, raw)
if err != nil {
return err
}
}
}
return nil
}
// InterfaceNestingRootTopicChildrenArticleParentTopicChildrenArticle includes the requested fields of the GraphQL type Article.
@@ -160,7 +226,40 @@ type InterfaceNestingRootTopicChildrenTopicParentTopic struct {
Typename string `json:"__typename"`
// ID is documented in the Content interface.
Id testutil.ID `json:"id"`
Children []InterfaceNestingRootTopicChildrenTopicParentTopicChildrenContent `json:"children"`
Children []InterfaceNestingRootTopicChildrenTopicParentTopicChildrenContent `json:"-"`
}
func (v *InterfaceNestingRootTopicChildrenTopicParentTopic) UnmarshalJSON(b []byte) error {
type InterfaceNestingRootTopicChildrenTopicParentTopicWrapper InterfaceNestingRootTopicChildrenTopicParentTopic
var firstPass struct {
*InterfaceNestingRootTopicChildrenTopicParentTopicWrapper
Children []json.RawMessage `json:"children"`
}
firstPass.InterfaceNestingRootTopicChildrenTopicParentTopicWrapper = (*InterfaceNestingRootTopicChildrenTopicParentTopicWrapper)(v)
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
{
target := &v.Children
raw := firstPass.Children
*target = make(
[]InterfaceNestingRootTopicChildrenTopicParentTopicChildrenContent,
len(raw))
for i, raw := range raw {
target := &(*target)[i]
err = __unmarshalInterfaceNestingRootTopicChildrenTopicParentTopicChildrenContent(
target, raw)
if err != nil {
return err
}
}
}
return nil
}
// InterfaceNestingRootTopicChildrenTopicParentTopicChildrenArticle includes the requested fields of the GraphQL type Article.
@@ -241,7 +340,40 @@ type InterfaceNestingRootTopicChildrenVideoParentTopic struct {
Typename string `json:"__typename"`
// ID is documented in the Content interface.
Id testutil.ID `json:"id"`
Children []InterfaceNestingRootTopicChildrenVideoParentTopicChildrenContent `json:"children"`
Children []InterfaceNestingRootTopicChildrenVideoParentTopicChildrenContent `json:"-"`
}
func (v *InterfaceNestingRootTopicChildrenVideoParentTopic) UnmarshalJSON(b []byte) error {
type InterfaceNestingRootTopicChildrenVideoParentTopicWrapper InterfaceNestingRootTopicChildrenVideoParentTopic
var firstPass struct {
*InterfaceNestingRootTopicChildrenVideoParentTopicWrapper
Children []json.RawMessage `json:"children"`
}
firstPass.InterfaceNestingRootTopicChildrenVideoParentTopicWrapper = (*InterfaceNestingRootTopicChildrenVideoParentTopicWrapper)(v)
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
{
target := &v.Children
raw := firstPass.Children
*target = make(
[]InterfaceNestingRootTopicChildrenVideoParentTopicChildrenContent,
len(raw))
for i, raw := range raw {
target := &(*target)[i]
err = __unmarshalInterfaceNestingRootTopicChildrenVideoParentTopicChildrenContent(
target, raw)
if err != nil {
return err
}
}
}
return nil
}
// InterfaceNestingRootTopicChildrenVideoParentTopicChildrenArticle includes the requested fields of the GraphQL type Article.
@@ -80,8 +80,9 @@ type InterfaceNoFragmentsQueryRandomItemVideo struct {
// InterfaceNoFragmentsQueryResponse is returned by InterfaceNoFragmentsQuery on success.
type InterfaceNoFragmentsQueryResponse struct {
Root InterfaceNoFragmentsQueryRootTopic `json:"root"`
RandomItem InterfaceNoFragmentsQueryRandomItemContent `json:"-"`
Root InterfaceNoFragmentsQueryRootTopic `json:"root"`
RandomItem InterfaceNoFragmentsQueryRandomItemContent `json:"-"`
WithPointer *InterfaceNoFragmentsQueryWithPointerContent `json:"-"`
}
func (v *InterfaceNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
@@ -90,7 +91,8 @@ func (v *InterfaceNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
var firstPass struct {
*InterfaceNoFragmentsQueryResponseWrapper
RandomItem json.RawMessage `json:"randomItem"`
RandomItem json.RawMessage `json:"randomItem"`
WithPointer json.RawMessage `json:"withPointer"`
}
firstPass.InterfaceNoFragmentsQueryResponseWrapper = (*InterfaceNoFragmentsQueryResponseWrapper)(v)
@@ -99,12 +101,25 @@ func (v *InterfaceNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
return err
}
err = __unmarshalInterfaceNoFragmentsQueryRandomItemContent(
&v.RandomItem, firstPass.RandomItem)
if err != nil {
return err
{
target := &v.RandomItem
raw := firstPass.RandomItem
err = __unmarshalInterfaceNoFragmentsQueryRandomItemContent(
target, raw)
if err != nil {
return err
}
}
{
target := &v.WithPointer
raw := firstPass.WithPointer
*target = new(InterfaceNoFragmentsQueryWithPointerContent)
err = __unmarshalInterfaceNoFragmentsQueryWithPointerContent(
*target, raw)
if err != nil {
return err
}
}
return nil
}
@@ -115,6 +130,74 @@ type InterfaceNoFragmentsQueryRootTopic struct {
Name string `json:"name"`
}
// InterfaceNoFragmentsQueryWithPointerArticle includes the requested fields of the GraphQL type Article.
type InterfaceNoFragmentsQueryWithPointerArticle struct {
Typename *string `json:"__typename"`
// ID is the identifier of the content.
Id *testutil.ID `json:"id"`
Name *string `json:"name"`
}
// InterfaceNoFragmentsQueryWithPointerContent 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 InterfaceNoFragmentsQueryWithPointerContent interface {
implementsGraphQLInterfaceInterfaceNoFragmentsQueryWithPointerContent()
}
func (v *InterfaceNoFragmentsQueryWithPointerArticle) implementsGraphQLInterfaceInterfaceNoFragmentsQueryWithPointerContent() {
}
func (v *InterfaceNoFragmentsQueryWithPointerVideo) implementsGraphQLInterfaceInterfaceNoFragmentsQueryWithPointerContent() {
}
func (v *InterfaceNoFragmentsQueryWithPointerTopic) implementsGraphQLInterfaceInterfaceNoFragmentsQueryWithPointerContent() {
}
func __unmarshalInterfaceNoFragmentsQueryWithPointerContent(v *InterfaceNoFragmentsQueryWithPointerContent, 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(InterfaceNoFragmentsQueryWithPointerArticle)
return json.Unmarshal(m, *v)
case "Video":
*v = new(InterfaceNoFragmentsQueryWithPointerVideo)
return json.Unmarshal(m, *v)
case "Topic":
*v = new(InterfaceNoFragmentsQueryWithPointerTopic)
return json.Unmarshal(m, *v)
default:
return fmt.Errorf(
`Unexpected concrete type for InterfaceNoFragmentsQueryWithPointerContent: "%v"`, tn.TypeName)
}
}
// InterfaceNoFragmentsQueryWithPointerTopic includes the requested fields of the GraphQL type Topic.
type InterfaceNoFragmentsQueryWithPointerTopic struct {
Typename *string `json:"__typename"`
// ID is the identifier of the content.
Id *testutil.ID `json:"id"`
Name *string `json:"name"`
}
// InterfaceNoFragmentsQueryWithPointerVideo includes the requested fields of the GraphQL type Video.
type InterfaceNoFragmentsQueryWithPointerVideo struct {
Typename *string `json:"__typename"`
// ID is the identifier of the content.
Id *testutil.ID `json:"id"`
Name *string `json:"name"`
}
func InterfaceNoFragmentsQuery(
client graphql.Client,
) (*InterfaceNoFragmentsQueryResponse, error) {
@@ -133,6 +216,11 @@ query InterfaceNoFragmentsQuery {
id
name
}
withPointer: randomItem {
__typename
id
name
}
}
`,
&retval,
@@ -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}\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\twithPointer: randomItem {\n\t\t__typename\n\t\tid\n\t\tname\n\t}\n}\n",
"sourceLocation": "testdata/queries/InterfaceNoFragments.graphql"
}
]
@@ -78,12 +78,15 @@ func (v *UnionNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
return err
}
err = __unmarshalUnionNoFragmentsQueryRandomLeafLeafContent(
&v.RandomLeaf, firstPass.RandomLeaf)
if err != nil {
return err
{
target := &v.RandomLeaf
raw := firstPass.RandomLeaf
err = __unmarshalUnionNoFragmentsQueryRandomLeafLeafContent(
target, raw)
if err != nil {
return err
}
}
return nil
}
+40 -5
View File
@@ -22,6 +22,18 @@ type goType interface {
// Reference returns the Go name of this type, e.g. []*MyStruct, and may be
// used to refer to it in Go code.
Reference() string
// Remove slice/pointer wrappers, and return the underlying (named (or
// builtin)) type. For example, given []*MyStruct, return MyStruct.
Unwrap() goType
// Count the number of times Unwrap() will unwrap a slice type. For
// example, given [][][]*MyStruct (or []**[][]*MyStruct, but we never
// currently generate that), return 3.
SliceDepth() int
// True if Unwrap() will unwrap a pointer at least once.
IsPointer() bool
}
var (
@@ -106,6 +118,11 @@ type goStructField struct {
Description string
}
func isAbstract(typ goType) bool {
_, ok := typ.Unwrap().(*goInterfaceType)
return ok
}
func (typ *goStructType) WriteDefinition(w io.Writer, g *generator) error {
description := typ.Description
if typ.Incomplete {
@@ -117,7 +134,7 @@ func (typ *goStructType) WriteDefinition(w io.Writer, g *generator) error {
for _, field := range typ.Fields {
writeDescription(w, field.Description)
jsonName := field.JSONName
if _, ok := field.GoType.(*goInterfaceType); ok {
if isAbstract(field.GoType) {
// abstract types are handled in our UnmarshalJSON
jsonName = "-"
}
@@ -155,10 +172,7 @@ func (typ *goStructType) Reference() string { return typ.GoName }
func (typ *goStructType) AbstractFields() []*goStructField {
var ret []*goStructField
for _, field := range typ.Fields {
// TODO(benkraft): To handle list-of-interface fields, we should really
// be "unwrapping" any goSliceType/goPointerType wrappers to find the
// goInterfaceType.
if _, ok := field.GoType.(*goInterfaceType); ok {
if isAbstract(field.GoType) {
ret = append(ret, field)
}
}
@@ -211,6 +225,27 @@ func (typ *goInterfaceType) WriteDefinition(w io.Writer, g *generator) error {
func (typ *goInterfaceType) Reference() string { return typ.GoName }
func (typ *goOpaqueType) Unwrap() goType { return typ }
func (typ *goSliceType) Unwrap() goType { return typ.Elem.Unwrap() }
func (typ *goPointerType) Unwrap() goType { return typ.Elem.Unwrap() }
func (typ *goEnumType) Unwrap() goType { return typ }
func (typ *goStructType) Unwrap() goType { return typ }
func (typ *goInterfaceType) Unwrap() goType { return typ }
func (typ *goOpaqueType) SliceDepth() int { return 0 }
func (typ *goSliceType) SliceDepth() int { return typ.Elem.SliceDepth() + 1 }
func (typ *goPointerType) SliceDepth() int { return 0 }
func (typ *goEnumType) SliceDepth() int { return 0 }
func (typ *goStructType) SliceDepth() int { return 0 }
func (typ *goInterfaceType) SliceDepth() int { return 0 }
func (typ *goOpaqueType) IsPointer() bool { return false }
func (typ *goSliceType) IsPointer() bool { return typ.Elem.IsPointer() }
func (typ *goPointerType) IsPointer() bool { return true }
func (typ *goEnumType) IsPointer() bool { return false }
func (typ *goStructType) IsPointer() bool { return false }
func (typ *goInterfaceType) IsPointer() bool { return false }
func incompleteTypeDescription(goName, graphQLName, description string) string {
// For types where we only have some fields, note that, along with
// the GraphQL documentation (if any). We don't want to just use
+62 -8
View File
@@ -21,7 +21,7 @@ func (v *{{.GoName}}) UnmarshalJSON(b []byte) error {
var firstPass struct{
*{{.GoName}}Wrapper
{{range .AbstractFields -}}
{{.GoName}} {{ref "encoding/json.RawMessage"}} `json:"{{.JSONName}}"`
{{.GoName}} {{repeat .GoType.SliceDepth "[]"}}{{ref "encoding/json.RawMessage"}} `json:"{{.JSONName}}"`
{{end}}
}
firstPass.{{.GoName}}Wrapper = (*{{.GoName}}Wrapper)(v)
@@ -31,13 +31,67 @@ func (v *{{.GoName}}) UnmarshalJSON(b []byte) error {
return err
}
{{/* Now, for each field, call out to the unmarshal-helper. */}}
{{range .AbstractFields -}}
err = __unmarshal{{.GoType.Reference}}(
&v.{{.GoName}}, firstPass.{{.GoName}})
if err != nil {
return err
{{/* Now, for each field, call out to the unmarshal-helper.
This gets a little complicated because we may have a slice field.
So what we do is basically, for each field of type `[][]...[]MyType`:
target := &v.MyField // *[][]...[]MyType
raw := firstPass.MyField // [][]...[]json.RawMessage
// repeat the following three lines n times; each time, inside
// the loop we have one less layer of slice on raw and target
*target = make([][]...[]MyType, len(raw))
for i, raw := range raw {
// We need the &(*target)[i] because at each stage we want to
// keep target as a pointer. (It only really has to be a
// pointer at the innermost level, but it's easiest to be
// consistent.)
target := &(*target)[i]
// (now we have `target *MyType` and `raw json.RawMessage`)
__unmarshalMyType(target, raw)
} // (also n times)
Note that if the field also uses a pointer (`[][]...[]*MyType`), we
now pass around `*[][]...[]*MyType`; again in principle
`[][]...[]*MyType` would work but require more special-casing. Thus
in the innermost loop, `target` is of type `**MyType`, so we have to
pass `*target` to the unmarshal-helper. Of course, since MyType is an
interface, I'm not sure why you'd any of that anyway.
One additional trick is we wrap everything above in a block ({ ... }),
so that the variables target and raw may take on different types for
each field we are handling, which would otherwise conflict. (We could
instead suffix the names, but that makes things much harder to read.)
*/}}
{{range $field := .AbstractFields -}}
{
target := &v.{{$field.GoName}}
raw := firstPass.{{$field.GoName}}
{{range $i := intRange $field.GoType.SliceDepth -}}
*target = make(
{{repeat (sub $field.GoType.SliceDepth $i) "[]"}}{{if $field.GoType.IsPointer}}*{{end}}{{$field.GoType.Unwrap.Reference}},
len(raw))
for i, raw := range raw {
target := &(*target)[i]
{{end -}}
{{if $field.GoType.IsPointer -}}
{{/* In this case, the parent for loop did `make([]*MyType, ...)` and
we have a pointer into that list. But we actually still need to
initialize the *elements* of the list. */ -}}
*target = new({{$field.GoType.Unwrap.Reference}})
{{end -}}
err = __unmarshal{{$field.GoType.Unwrap.Reference}}(
{{if $field.GoType.IsPointer}}*{{end}}target, raw)
if err != nil {
return err
}
{{range $i := intRange $field.GoType.SliceDepth -}}
}
{{end -}}
}
{{end}}
{{end -}}
return nil
}
+7
View File
@@ -134,8 +134,10 @@ github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a/go.mod h1:F5haX7
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -612,7 +614,9 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryancurrah/gomodguard v1.2.3 h1:ww2fsjqocGCAFamzvv/b8IsRduuHHeK2MHTcTxZTQX8=
github.com/ryancurrah/gomodguard v1.2.3/go.mod h1:rYbA/4Tg5c54mV1sv4sQTP5WOPBcoLtnBZ7/TEhXAbg=
@@ -632,6 +636,7 @@ github.com/shirou/gopsutil/v3 v3.21.7/go.mod h1:RGl11Y7XMTQPmHh8F0ayC6haKNBgH4PX
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -710,7 +715,9 @@ github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lP
github.com/ultraware/whitespace v0.0.4 h1:If7Va4cM03mpgrNH9k49/VOicWpGoG70XPBFFODYDsg=
github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/uudashr/gocognit v1.0.5 h1:rrSex7oHr3/pPLQ0xoWq108XMU8s678FJcQ+aSfOHa4=
github.com/uudashr/gocognit v1.0.5/go.mod h1:wgYz0mitoKOTysqxTDMOUXg+Jb5SvtihkfmugIZYpEA=
+241 -5
View File
@@ -10,6 +10,183 @@ import (
"github.com/Khan/genqlient/graphql"
)
// queryWithInterfaceListFieldBeingsAnimal includes the requested fields of the GraphQL type Animal.
type queryWithInterfaceListFieldBeingsAnimal struct {
Typename string `json:"__typename"`
Id string `json:"id"`
Name string `json:"name"`
}
// queryWithInterfaceListFieldBeingsBeing includes the requested fields of the GraphQL type Being.
type queryWithInterfaceListFieldBeingsBeing interface {
implementsGraphQLInterfacequeryWithInterfaceListFieldBeingsBeing()
}
func (v *queryWithInterfaceListFieldBeingsUser) implementsGraphQLInterfacequeryWithInterfaceListFieldBeingsBeing() {
}
func (v *queryWithInterfaceListFieldBeingsAnimal) implementsGraphQLInterfacequeryWithInterfaceListFieldBeingsBeing() {
}
func __unmarshalqueryWithInterfaceListFieldBeingsBeing(v *queryWithInterfaceListFieldBeingsBeing, 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 "User":
*v = new(queryWithInterfaceListFieldBeingsUser)
return json.Unmarshal(m, *v)
case "Animal":
*v = new(queryWithInterfaceListFieldBeingsAnimal)
return json.Unmarshal(m, *v)
default:
return fmt.Errorf(
`Unexpected concrete type for queryWithInterfaceListFieldBeingsBeing: "%v"`, tn.TypeName)
}
}
// queryWithInterfaceListFieldBeingsUser includes the requested fields of the GraphQL type User.
type queryWithInterfaceListFieldBeingsUser struct {
Typename string `json:"__typename"`
Id string `json:"id"`
Name string `json:"name"`
}
// queryWithInterfaceListFieldResponse is returned by queryWithInterfaceListField on success.
type queryWithInterfaceListFieldResponse struct {
Beings []queryWithInterfaceListFieldBeingsBeing `json:"-"`
}
func (v *queryWithInterfaceListFieldResponse) UnmarshalJSON(b []byte) error {
type queryWithInterfaceListFieldResponseWrapper queryWithInterfaceListFieldResponse
var firstPass struct {
*queryWithInterfaceListFieldResponseWrapper
Beings []json.RawMessage `json:"beings"`
}
firstPass.queryWithInterfaceListFieldResponseWrapper = (*queryWithInterfaceListFieldResponseWrapper)(v)
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
{
target := &v.Beings
raw := firstPass.Beings
*target = make(
[]queryWithInterfaceListFieldBeingsBeing,
len(raw))
for i, raw := range raw {
target := &(*target)[i]
err = __unmarshalqueryWithInterfaceListFieldBeingsBeing(
target, raw)
if err != nil {
return err
}
}
}
return nil
}
// queryWithInterfaceListPointerFieldBeingsAnimal includes the requested fields of the GraphQL type Animal.
type queryWithInterfaceListPointerFieldBeingsAnimal struct {
Typename string `json:"__typename"`
Id string `json:"id"`
Name string `json:"name"`
}
// queryWithInterfaceListPointerFieldBeingsBeing includes the requested fields of the GraphQL type Being.
type queryWithInterfaceListPointerFieldBeingsBeing interface {
implementsGraphQLInterfacequeryWithInterfaceListPointerFieldBeingsBeing()
}
func (v *queryWithInterfaceListPointerFieldBeingsUser) implementsGraphQLInterfacequeryWithInterfaceListPointerFieldBeingsBeing() {
}
func (v *queryWithInterfaceListPointerFieldBeingsAnimal) implementsGraphQLInterfacequeryWithInterfaceListPointerFieldBeingsBeing() {
}
func __unmarshalqueryWithInterfaceListPointerFieldBeingsBeing(v *queryWithInterfaceListPointerFieldBeingsBeing, 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 "User":
*v = new(queryWithInterfaceListPointerFieldBeingsUser)
return json.Unmarshal(m, *v)
case "Animal":
*v = new(queryWithInterfaceListPointerFieldBeingsAnimal)
return json.Unmarshal(m, *v)
default:
return fmt.Errorf(
`Unexpected concrete type for queryWithInterfaceListPointerFieldBeingsBeing: "%v"`, tn.TypeName)
}
}
// queryWithInterfaceListPointerFieldBeingsUser includes the requested fields of the GraphQL type User.
type queryWithInterfaceListPointerFieldBeingsUser struct {
Typename string `json:"__typename"`
Id string `json:"id"`
Name string `json:"name"`
}
// queryWithInterfaceListPointerFieldResponse is returned by queryWithInterfaceListPointerField on success.
type queryWithInterfaceListPointerFieldResponse struct {
Beings []*queryWithInterfaceListPointerFieldBeingsBeing `json:"-"`
}
func (v *queryWithInterfaceListPointerFieldResponse) UnmarshalJSON(b []byte) error {
type queryWithInterfaceListPointerFieldResponseWrapper queryWithInterfaceListPointerFieldResponse
var firstPass struct {
*queryWithInterfaceListPointerFieldResponseWrapper
Beings []json.RawMessage `json:"beings"`
}
firstPass.queryWithInterfaceListPointerFieldResponseWrapper = (*queryWithInterfaceListPointerFieldResponseWrapper)(v)
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
{
target := &v.Beings
raw := firstPass.Beings
*target = make(
[]*queryWithInterfaceListPointerFieldBeingsBeing,
len(raw))
for i, raw := range raw {
target := &(*target)[i]
*target = new(queryWithInterfaceListPointerFieldBeingsBeing)
err = __unmarshalqueryWithInterfaceListPointerFieldBeingsBeing(
*target, raw)
if err != nil {
return err
}
}
}
return nil
}
// queryWithInterfaceNoFragmentsBeing includes the requested fields of the GraphQL type Being.
type queryWithInterfaceNoFragmentsBeing interface {
implementsGraphQLInterfacequeryWithInterfaceNoFragmentsBeing()
@@ -87,12 +264,15 @@ func (v *queryWithInterfaceNoFragmentsResponse) UnmarshalJSON(b []byte) error {
return err
}
err = __unmarshalqueryWithInterfaceNoFragmentsBeing(
&v.Being, firstPass.Being)
if err != nil {
return err
{
target := &v.Being
raw := firstPass.Being
err = __unmarshalqueryWithInterfaceNoFragmentsBeing(
target, raw)
if err != nil {
return err
}
}
return nil
}
@@ -202,3 +382,59 @@ query queryWithInterfaceNoFragments ($id: ID!) {
)
return &retval, err
}
func queryWithInterfaceListField(
ctx context.Context,
client graphql.Client,
ids []string,
) (*queryWithInterfaceListFieldResponse, error) {
variables := map[string]interface{}{
"ids": ids,
}
var retval queryWithInterfaceListFieldResponse
err := client.MakeRequest(
ctx,
"queryWithInterfaceListField",
`
query queryWithInterfaceListField ($ids: [ID!]!) {
beings(ids: $ids) {
__typename
id
name
}
}
`,
&retval,
variables,
)
return &retval, err
}
func queryWithInterfaceListPointerField(
ctx context.Context,
client graphql.Client,
ids []string,
) (*queryWithInterfaceListPointerFieldResponse, error) {
variables := map[string]interface{}{
"ids": ids,
}
var retval queryWithInterfaceListPointerFieldResponse
err := client.MakeRequest(
ctx,
"queryWithInterfaceListPointerField",
`
query queryWithInterfaceListPointerField ($ids: [ID!]!) {
beings(ids: $ids) {
__typename
id
name
}
}
`,
&retval,
variables,
)
return &retval, err
}
+63
View File
@@ -99,6 +99,69 @@ func TestInterfaceNoFragments(t *testing.T) {
assert.Nil(t, resp.Being)
}
func TestInterfaceListField(t *testing.T) {
_ = `# @genqlient
query queryWithInterfaceListField($ids: [ID!]!) {
beings(ids: $ids) { __typename id name }
}`
ctx := context.Background()
server := server.RunServer()
defer server.Close()
client := graphql.NewClient(server.URL, http.DefaultClient)
resp, err := queryWithInterfaceListField(ctx, client,
[]string{"1", "3", "12847394823"})
require.NoError(t, err)
require.Len(t, resp.Beings, 3)
user, ok := resp.Beings[0].(*queryWithInterfaceListFieldBeingsUser)
require.Truef(t, ok, "got %T, not User", resp.Beings[0])
assert.Equal(t, "1", user.Id)
assert.Equal(t, "Yours Truly", user.Name)
animal, ok := resp.Beings[1].(*queryWithInterfaceListFieldBeingsAnimal)
require.Truef(t, ok, "got %T, not Animal", resp.Beings[1])
assert.Equal(t, "3", animal.Id)
assert.Equal(t, "Fido", animal.Name)
assert.Nil(t, resp.Beings[2])
}
func TestInterfaceListPointerField(t *testing.T) {
_ = `# @genqlient
query queryWithInterfaceListPointerField($ids: [ID!]!) {
# @genqlient(pointer: true)
beings(ids: $ids) {
__typename id name
}
}`
ctx := context.Background()
server := server.RunServer()
defer server.Close()
client := graphql.NewClient(server.URL, http.DefaultClient)
resp, err := queryWithInterfaceListPointerField(ctx, client,
[]string{"1", "3", "12847394823"})
require.NoError(t, err)
require.Len(t, resp.Beings, 3)
user, ok := (*resp.Beings[0]).(*queryWithInterfaceListPointerFieldBeingsUser)
require.Truef(t, ok, "got %T, not User", resp.Beings[0])
assert.Equal(t, "1", user.Id)
assert.Equal(t, "Yours Truly", user.Name)
animal, ok := (*resp.Beings[1]).(*queryWithInterfaceListPointerFieldBeingsAnimal)
require.Truef(t, ok, "got %T, not Animal", resp.Beings[1])
assert.Equal(t, "3", animal.Id)
assert.Equal(t, "Fido", animal.Name)
assert.Nil(t, *resp.Beings[2])
}
func TestGeneratedCode(t *testing.T) {
// TODO(benkraft): Check that gqlgen is up to date too. In practice that's
// less likely to be a problem, since it should only change if you update
+1
View File
@@ -2,6 +2,7 @@ type Query {
me: User
user(id: ID!): User
being(id: ID!): Being
beings(ids: [ID!]!): [Being]!
}
type User implements Being {
+157 -3
View File
@@ -9,6 +9,7 @@ import (
"fmt"
"strconv"
"sync"
"sync/atomic"
"github.com/99designs/gqlgen/graphql"
"github.com/99designs/gqlgen/graphql/introspection"
@@ -49,9 +50,10 @@ type ComplexityRoot struct {
}
Query struct {
Being func(childComplexity int, id string) int
Me func(childComplexity int) int
User func(childComplexity int, id string) int
Being func(childComplexity int, id string) int
Beings func(childComplexity int, ids []string) int
Me func(childComplexity int) int
User func(childComplexity int, id string) int
}
User struct {
@@ -65,6 +67,7 @@ type QueryResolver interface {
Me(ctx context.Context) (*User, error)
User(ctx context.Context, id string) (*User, error)
Being(ctx context.Context, id string) (Being, error)
Beings(ctx context.Context, ids []string) ([]Being, error)
}
type executableSchema struct {
@@ -122,6 +125,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Query.Being(childComplexity, args["id"].(string)), true
case "Query.beings":
if e.complexity.Query.Beings == nil {
break
}
args, err := ec.field_Query_beings_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Query.Beings(childComplexity, args["ids"].([]string)), true
case "Query.me":
if e.complexity.Query.Me == nil {
break
@@ -216,6 +231,7 @@ var sources = []*ast.Source{
me: User
user(id: ID!): User
being(id: ID!): Being
beings(ids: [ID!]!): [Being]!
}
type User implements Being {
@@ -278,6 +294,21 @@ func (ec *executionContext) field_Query_being_args(ctx context.Context, rawArgs
return args, nil
}
func (ec *executionContext) field_Query_beings_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 []string
if tmp, ok := rawArgs["ids"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("ids"))
arg0, err = ec.unmarshalNID2ᚕstringᚄ(ctx, tmp)
if err != nil {
return nil, err
}
}
args["ids"] = arg0
return args, nil
}
func (ec *executionContext) field_Query_user_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@@ -578,6 +609,48 @@ func (ec *executionContext) _Query_being(ctx context.Context, field graphql.Coll
return ec.marshalOBeing2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐBeing(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_beings(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Query",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Query_beings_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Query().Beings(rctx, args["ids"].([]string))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.([]Being)
fc.Result = res
return ec.marshalNBeing2ᚕgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐBeing(ctx, field.Selections, res)
}
func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -1956,6 +2029,20 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
res = ec._Query_being(ctx, field)
return res
})
case "beings":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Query_beings(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "__type":
out.Values[i] = ec._Query___type(ctx, field)
case "__schema":
@@ -2250,6 +2337,43 @@ func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, o
// region ***************************** type.gotpl *****************************
func (ec *executionContext) marshalNBeing2ᚕgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐBeing(ctx context.Context, sel ast.SelectionSet, v []Being) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup
isLen1 := len(v) == 1
if !isLen1 {
wg.Add(len(v))
}
for i := range v {
i := i
fc := &graphql.FieldContext{
Index: &i,
Result: &v[i],
}
ctx := graphql.WithFieldContext(ctx, fc)
f := func(i int) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = nil
}
}()
if !isLen1 {
defer wg.Done()
}
ret[i] = ec.marshalOBeing2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐBeing(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
}
}
wg.Wait()
return ret
}
func (ec *executionContext) unmarshalNBoolean2bool(ctx context.Context, v interface{}) (bool, error) {
res, err := graphql.UnmarshalBoolean(v)
return res, graphql.ErrorOnPath(ctx, err)
@@ -2280,6 +2404,36 @@ func (ec *executionContext) marshalNID2string(ctx context.Context, sel ast.Selec
return res
}
func (ec *executionContext) unmarshalNID2ᚕstringᚄ(ctx context.Context, v interface{}) ([]string, error) {
var vSlice []interface{}
if v != nil {
if tmp1, ok := v.([]interface{}); ok {
vSlice = tmp1
} else {
vSlice = []interface{}{v}
}
}
var err error
res := make([]string, len(vSlice))
for i := range vSlice {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i))
res[i], err = ec.unmarshalNID2string(ctx, vSlice[i])
if err != nil {
return nil, err
}
}
return res, nil
}
func (ec *executionContext) marshalNID2ᚕstringᚄ(ctx context.Context, sel ast.SelectionSet, v []string) graphql.Marshaler {
ret := make(graphql.Array, len(v))
for i := range v {
ret[i] = ec.marshalNID2string(ctx, sel, v[i])
}
return ret
}
func (ec *executionContext) unmarshalNSpecies2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐSpecies(ctx context.Context, v interface{}) (Species, error) {
var res Species
err := res.UnmarshalGQL(v)
+8
View File
@@ -55,6 +55,14 @@ func (r *queryResolver) Being(ctx context.Context, id string) (Being, error) {
return beingByID(id), nil
}
func (r *queryResolver) Beings(ctx context.Context, ids []string) ([]Being, error) {
ret := make([]Being, len(ids))
for i, id := range ids {
ret[i] = beingByID(id)
}
return ret, nil
}
func RunServer() *httptest.Server {
gqlgenServer := handler.New(NewExecutableSchema(Config{Resolvers: &resolver{}}))
gqlgenServer.AddTransport(transport.POST{})