diff --git a/generate/generate_test.go b/generate/generate_test.go index a9cd28b..186e320 100644 --- a/generate/generate_test.go +++ b/generate/generate_test.go @@ -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") diff --git a/generate/template.go b/generate/template.go index bca9415..e8887e4 100644 --- a/generate/template.go +++ b/generate/template.go @@ -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) diff --git a/generate/testdata/queries/InterfaceListField.graphql b/generate/testdata/queries/InterfaceListField.graphql index 55fd02a..dc57f93 100644 --- a/generate/testdata/queries/InterfaceListField.graphql +++ b/generate/testdata/queries/InterfaceListField.graphql @@ -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 + } + } } diff --git a/generate/testdata/queries/InterfaceListOfListsOfListsField.graphql b/generate/testdata/queries/InterfaceListOfListsOfListsField.graphql new file mode 100644 index 0000000..69ec1d3 --- /dev/null +++ b/generate/testdata/queries/InterfaceListOfListsOfListsField.graphql @@ -0,0 +1,5 @@ +query InterfaceListOfListOfListsField { + listOfListsOfListsOfContent { __typename id name } + # @genqlient(pointer: true) + withPointer: listOfListsOfListsOfContent { __typename id name } +} diff --git a/generate/testdata/queries/InterfaceNoFragments.graphql b/generate/testdata/queries/InterfaceNoFragments.graphql index 88d0bb3..f9d552a 100644 --- a/generate/testdata/queries/InterfaceNoFragments.graphql +++ b/generate/testdata/queries/InterfaceNoFragments.graphql @@ -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 } } diff --git a/generate/testdata/queries/schema.graphql b/generate/testdata/queries/schema.graphql index 31f7c2a..dc872c8 100644 --- a/generate/testdata/queries/schema.graphql +++ b/generate/testdata/queries/schema.graphql @@ -111,6 +111,7 @@ type Query { getJunk: Junk getComplexJunk: ComplexJunk listOfListsOfLists: [[[String!]!]!]! + listOfListsOfListsOfContent: [[[Content!]!]!]! } type Mutation { diff --git a/generate/testdata/snapshots/TestGenerate-InterfaceListField.graphql-InterfaceListField.graphql.go b/generate/testdata/snapshots/TestGenerate-InterfaceListField.graphql-InterfaceListField.graphql.go index 5c14d93..828fd6e 100644 --- a/generate/testdata/snapshots/TestGenerate-InterfaceListField.graphql-InterfaceListField.graphql.go +++ b/generate/testdata/snapshots/TestGenerate-InterfaceListField.graphql-InterfaceListField.graphql.go @@ -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, diff --git a/generate/testdata/snapshots/TestGenerate-InterfaceListField.graphql-InterfaceListField.graphql.json b/generate/testdata/snapshots/TestGenerate-InterfaceListField.graphql-InterfaceListField.graphql.json index f6665e9..e2500e1 100644 --- a/generate/testdata/snapshots/TestGenerate-InterfaceListField.graphql-InterfaceListField.graphql.json +++ b/generate/testdata/snapshots/TestGenerate-InterfaceListField.graphql-InterfaceListField.graphql.json @@ -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" } ] diff --git a/generate/testdata/snapshots/TestGenerate-InterfaceListOfListsOfListsField.graphql-InterfaceListOfListsOfListsField.graphql.go b/generate/testdata/snapshots/TestGenerate-InterfaceListOfListsOfListsField.graphql-InterfaceListOfListsOfListsField.graphql.go new file mode 100644 index 0000000..2c56192 --- /dev/null +++ b/generate/testdata/snapshots/TestGenerate-InterfaceListOfListsOfListsField.graphql-InterfaceListOfListsOfListsField.graphql.go @@ -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 +} + diff --git a/generate/testdata/snapshots/TestGenerate-InterfaceListOfListsOfListsField.graphql-InterfaceListOfListsOfListsField.graphql.json b/generate/testdata/snapshots/TestGenerate-InterfaceListOfListsOfListsField.graphql-InterfaceListOfListsOfListsField.graphql.json new file mode 100644 index 0000000..178e872 --- /dev/null +++ b/generate/testdata/snapshots/TestGenerate-InterfaceListOfListsOfListsField.graphql-InterfaceListOfListsOfListsField.graphql.json @@ -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" + } + ] +} diff --git a/generate/testdata/snapshots/TestGenerate-InterfaceNesting.graphql-InterfaceNesting.graphql.go b/generate/testdata/snapshots/TestGenerate-InterfaceNesting.graphql-InterfaceNesting.graphql.go index cb47b03..e94a80c 100644 --- a/generate/testdata/snapshots/TestGenerate-InterfaceNesting.graphql-InterfaceNesting.graphql.go +++ b/generate/testdata/snapshots/TestGenerate-InterfaceNesting.graphql-InterfaceNesting.graphql.go @@ -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. diff --git a/generate/testdata/snapshots/TestGenerate-InterfaceNoFragments.graphql-InterfaceNoFragments.graphql.go b/generate/testdata/snapshots/TestGenerate-InterfaceNoFragments.graphql-InterfaceNoFragments.graphql.go index e182548..75eb36a 100644 --- a/generate/testdata/snapshots/TestGenerate-InterfaceNoFragments.graphql-InterfaceNoFragments.graphql.go +++ b/generate/testdata/snapshots/TestGenerate-InterfaceNoFragments.graphql-InterfaceNoFragments.graphql.go @@ -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, diff --git a/generate/testdata/snapshots/TestGenerate-InterfaceNoFragments.graphql-InterfaceNoFragments.graphql.json b/generate/testdata/snapshots/TestGenerate-InterfaceNoFragments.graphql-InterfaceNoFragments.graphql.json index 48994c1..0d1b8be 100644 --- a/generate/testdata/snapshots/TestGenerate-InterfaceNoFragments.graphql-InterfaceNoFragments.graphql.json +++ b/generate/testdata/snapshots/TestGenerate-InterfaceNoFragments.graphql-InterfaceNoFragments.graphql.json @@ -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" } ] diff --git a/generate/testdata/snapshots/TestGenerate-UnionNoFragments.graphql-UnionNoFragments.graphql.go b/generate/testdata/snapshots/TestGenerate-UnionNoFragments.graphql-UnionNoFragments.graphql.go index b8060db..ac214be 100644 --- a/generate/testdata/snapshots/TestGenerate-UnionNoFragments.graphql-UnionNoFragments.graphql.go +++ b/generate/testdata/snapshots/TestGenerate-UnionNoFragments.graphql-UnionNoFragments.graphql.go @@ -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 } diff --git a/generate/types.go b/generate/types.go index b0f4df7..e90c5c5 100644 --- a/generate/types.go +++ b/generate/types.go @@ -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 diff --git a/generate/unmarshal.go.tmpl b/generate/unmarshal.go.tmpl index ed3eb58..980d3b6 100644 --- a/generate/unmarshal.go.tmpl +++ b/generate/unmarshal.go.tmpl @@ -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 } diff --git a/go.sum b/go.sum index d51696f..e53dd37 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/integration/generated.go b/internal/integration/generated.go index aa50bef..025ece0 100644 --- a/internal/integration/generated.go +++ b/internal/integration/generated.go @@ -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 +} diff --git a/internal/integration/integration_test.go b/internal/integration/integration_test.go index 9f55aeb..274a0ea 100644 --- a/internal/integration/integration_test.go +++ b/internal/integration/integration_test.go @@ -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 diff --git a/internal/integration/schema.graphql b/internal/integration/schema.graphql index 2520059..5c42e46 100644 --- a/internal/integration/schema.graphql +++ b/internal/integration/schema.graphql @@ -2,6 +2,7 @@ type Query { me: User user(id: ID!): User being(id: ID!): Being + beings(ids: [ID!]!): [Being]! } type User implements Being { diff --git a/internal/integration/server/gqlgen_exec.go b/internal/integration/server/gqlgen_exec.go index 0df4e78..d50db5a 100644 --- a/internal/integration/server/gqlgen_exec.go +++ b/internal/integration/server/gqlgen_exec.go @@ -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) diff --git a/internal/integration/server/server.go b/internal/integration/server/server.go index f24b491..8032326 100644 --- a/internal/integration/server/server.go +++ b/internal/integration/server/server.go @@ -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{})