Add support for interfaces, part 1: the simplest cases (#52)
## Summary: In this commit I begin the journey to add the long-awaited support for interfaces (part of #8). Well, it's not the beginning: I already had some half-written broken code around. But it's the first fully functional support, and especially, the first *tested* support; it's probably best to review the nontrivially-changed code as if it were new. Conceptually, the code so far is pretty simple: we generate an interface type, and the implementations. (That code is in fact mostly unchanged.) The complexity comes in because encoding/json doesn't know how to unmarshal that. So we have to add an UnmarshalJSON method, which actually has to be on the types with interface-type fields, that knows how. I factored it into two methods, such that that UnmarshalJSON method is just glue, and then there's a separate function, corresponding to each interface-type, that actually does all the work. (If only one could just write it as an actual method!) The method uses the same trick suggested to me by a few others in another context to deserialize all but one field, then handle that field specially, which is discussed in the code. This still has some limitations, which will be lifted in future commits: - it doesn't allow for list-of-interface fields - it requires that you manually ask for `__typename` - it doesn't support fragments, i.e. you can only query for interface fields, not concrete-type-specific ones But it works, even in integration tests, which is progress! As a part of this, I added a proper config option for the "allow broken features" flag, since I need to be able to set it from the integration tests which are in a separate package (and actually shell out via `go generate`). I also renamed what was to be the first case (InterfaceNoFragments), and replaced it with a further-simplified version (avoiding list-of-interface fields. [1] https://github.com/benjaminjkraft/notes/blob/master/go-json-interfaces.md Issue: https://github.com/Khan/genqlient/issues/8 ## Test plan: make tesc 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, ✅ Test (1.17), ⌛ Test (1.16), ⌛ Test (1.15), ⌛ Test (1.14), ⌛ Test (1.13), ✅ Lint Pull request URL: https://github.com/Khan/genqlient/pull/52
This commit is contained in:
@@ -73,6 +73,13 @@ type Config struct {
|
||||
// and UnmarshalJSON methods, or otherwise be convertible to JSON.
|
||||
Scalars map[string]string `yaml:"scalars"`
|
||||
|
||||
// Set to true to use features that aren't fully ready to use.
|
||||
//
|
||||
// This is primarily intended for genqlient's own tests. These features
|
||||
// are likely BROKEN and come with NO EXPECTATION OF COMPATIBBILITY. Use
|
||||
// them at your own risk!
|
||||
AllowBrokenFeatures bool `yaml:"allow_broken_features"`
|
||||
|
||||
// Set automatically to the filename of the config file itself.
|
||||
configFilename string
|
||||
}
|
||||
|
||||
+15
-2
@@ -25,7 +25,7 @@ func (g *generator) baseTypeForOperation(operation ast.Operation) (*ast.Definiti
|
||||
case ast.Mutation:
|
||||
return g.schema.Mutation, nil
|
||||
case ast.Subscription:
|
||||
if !allowBrokenFeatures {
|
||||
if !g.Config.AllowBrokenFeatures {
|
||||
return nil, errorf(nil, "genqlient does not yet support subscriptions")
|
||||
}
|
||||
return g.schema.Subscription, nil
|
||||
@@ -255,10 +255,23 @@ func (g *generator) convertDefinition(
|
||||
return goType, nil
|
||||
|
||||
case ast.Interface, ast.Union:
|
||||
if !allowBrokenFeatures {
|
||||
if !g.Config.AllowBrokenFeatures {
|
||||
return nil, errorf(pos, "not implemented: %v", def.Kind)
|
||||
}
|
||||
|
||||
// We need to request __typename so we know which concrete type to use.
|
||||
hasTypename := false
|
||||
for _, selection := range selectionSet {
|
||||
field, ok := selection.(*ast.Field)
|
||||
if ok && field.Name == "__typename" {
|
||||
hasTypename = true
|
||||
}
|
||||
}
|
||||
if !hasTypename {
|
||||
// TODO(benkraft): Instead, modify the query to add __typename.
|
||||
return nil, errorf(pos, "union/interface type %s must request __typename", def.Name)
|
||||
}
|
||||
|
||||
implementationTypes := g.schema.GetPossibleTypes(def)
|
||||
goType := &goInterfaceType{
|
||||
GoName: name,
|
||||
|
||||
@@ -17,9 +17,6 @@ import (
|
||||
"golang.org/x/tools/imports"
|
||||
)
|
||||
|
||||
// Set to true to test features that aren't yet really ready.
|
||||
var allowBrokenFeatures = false
|
||||
|
||||
// generator is the context for the codegen process (and ends up getting passed
|
||||
// to the template).
|
||||
type generator struct {
|
||||
@@ -230,7 +227,7 @@ func Generate(config *Config) (map[string][]byte, error) {
|
||||
strings.Join(config.Operations, ", "))
|
||||
}
|
||||
|
||||
if len(document.Fragments) > 0 && !allowBrokenFeatures {
|
||||
if len(document.Fragments) > 0 && !config.AllowBrokenFeatures {
|
||||
return nil, errorf(document.Fragments[0].Position,
|
||||
"genqlient does not yet support fragments")
|
||||
}
|
||||
|
||||
@@ -30,9 +30,6 @@ const (
|
||||
// update the snapshots. Make sure to check that the output is sensible; the
|
||||
// snapshots don't even get compiled!
|
||||
func TestGenerate(t *testing.T) {
|
||||
// we can test parts of features even if they're not done yet!
|
||||
allowBrokenFeatures = true
|
||||
|
||||
files, err := ioutil.ReadDir(dataDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -59,6 +56,7 @@ func TestGenerate(t *testing.T) {
|
||||
"Junk": "interface{}",
|
||||
"ComplexJunk": "[]map[string]*[]*map[string]interface{}",
|
||||
},
|
||||
AllowBrokenFeatures: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -74,9 +72,12 @@ func TestGenerate(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping build due to -short")
|
||||
} else if sourceFilename == "InterfaceNesting.graphql" ||
|
||||
sourceFilename == "InterfaceNoFragments.graphql" ||
|
||||
sourceFilename == "Omitempty.graphql" {
|
||||
t.Skip("TODO: enable these once they build")
|
||||
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")
|
||||
}
|
||||
|
||||
goContent := generated[goFilename]
|
||||
@@ -109,9 +110,6 @@ func TestGenerate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGenerateErrors(t *testing.T) {
|
||||
// we can test parts of features even if they're not done yet!
|
||||
allowBrokenFeatures = true
|
||||
|
||||
files, err := ioutil.ReadDir(errorsDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -138,6 +136,7 @@ func TestGenerateErrors(t *testing.T) {
|
||||
"ValidScalar": "string",
|
||||
"InvalidScalar": "bogus",
|
||||
},
|
||||
AllowBrokenFeatures: true,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected an error")
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package errors
|
||||
|
||||
const _ = `# @genqlient
|
||||
query MyQuery { i { f } }
|
||||
`
|
||||
@@ -0,0 +1 @@
|
||||
query MyQuery { i { f } }
|
||||
@@ -0,0 +1,11 @@
|
||||
type Query {
|
||||
i: I
|
||||
}
|
||||
|
||||
type T implements I {
|
||||
f: String!
|
||||
}
|
||||
|
||||
interface I {
|
||||
f: String!
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
query InterfaceNoFragmentsQuery {
|
||||
root {
|
||||
id
|
||||
name
|
||||
children {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,13 @@ query InterfaceNesting {
|
||||
root {
|
||||
id
|
||||
children {
|
||||
__typename
|
||||
id
|
||||
parent {
|
||||
__typename
|
||||
id
|
||||
children {
|
||||
__typename
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
+2
-8
@@ -1,10 +1,4 @@
|
||||
query InterfaceNoFragmentsQuery {
|
||||
root {
|
||||
id
|
||||
name
|
||||
children {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
root { id name } # (make sure sibling fields work)
|
||||
randomItem { __typename id name }
|
||||
}
|
||||
|
||||
+1
@@ -104,6 +104,7 @@ type Query {
|
||||
"""usersWithRole looks a user up by role."""
|
||||
usersWithRole(role: Role!): [User!]!
|
||||
root: Topic!
|
||||
randomItem: Content!
|
||||
randomLeaf: LeafContent!
|
||||
convert(dt: DateTime!, tz: String): DateTime!
|
||||
maybeConvert(dt: DateTime, tz: String): DateTime
|
||||
|
||||
Vendored
+32
@@ -3,6 +3,9 @@ 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"
|
||||
)
|
||||
@@ -43,6 +46,35 @@ func (v *InterfaceNoFragmentsQueryRootTopicChildrenVideo) implementsGraphQLInter
|
||||
func (v *InterfaceNoFragmentsQueryRootTopicChildrenTopic) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent() {
|
||||
}
|
||||
|
||||
func __unmarshalInterfaceNoFragmentsQueryRootTopicChildrenContent(v *InterfaceNoFragmentsQueryRootTopicChildrenContent, 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(InterfaceNoFragmentsQueryRootTopicChildrenArticle)
|
||||
return json.Unmarshal(m, *v)
|
||||
case "Video":
|
||||
*v = new(InterfaceNoFragmentsQueryRootTopicChildrenVideo)
|
||||
return json.Unmarshal(m, *v)
|
||||
case "Topic":
|
||||
*v = new(InterfaceNoFragmentsQueryRootTopicChildrenTopic)
|
||||
return json.Unmarshal(m, *v)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
`Unexpected concrete type for InterfaceNoFragmentsQueryRootTopicChildrenContent: "%v"`, tn.TypeName)
|
||||
}
|
||||
}
|
||||
|
||||
// InterfaceNoFragmentsQueryRootTopicChildrenTopic includes the requested fields of the GraphQL type Topic.
|
||||
type InterfaceNoFragmentsQueryRootTopicChildrenTopic struct {
|
||||
Typename string `json:"__typename"`
|
||||
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"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",
|
||||
"sourceLocation": "testdata/queries/InterfaceListField.graphql"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+149
-12
@@ -3,6 +3,9 @@ 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"
|
||||
)
|
||||
@@ -21,6 +24,7 @@ type InterfaceNestingRootTopic struct {
|
||||
|
||||
// InterfaceNestingRootTopicChildrenArticle includes the requested fields of the GraphQL type Article.
|
||||
type InterfaceNestingRootTopicChildrenArticle struct {
|
||||
Typename string `json:"__typename"`
|
||||
// ID is the identifier of the content.
|
||||
Id testutil.ID `json:"id"`
|
||||
Parent InterfaceNestingRootTopicChildrenArticleParentTopic `json:"parent"`
|
||||
@@ -28,6 +32,7 @@ type InterfaceNestingRootTopicChildrenArticle struct {
|
||||
|
||||
// InterfaceNestingRootTopicChildrenArticleParentTopic includes the requested fields of the GraphQL type Topic.
|
||||
type InterfaceNestingRootTopicChildrenArticleParentTopic struct {
|
||||
Typename string `json:"__typename"`
|
||||
// ID is documented in the Content interface.
|
||||
Id testutil.ID `json:"id"`
|
||||
Children []InterfaceNestingRootTopicChildrenArticleParentTopicChildrenContent `json:"children"`
|
||||
@@ -35,6 +40,7 @@ type InterfaceNestingRootTopicChildrenArticleParentTopic struct {
|
||||
|
||||
// InterfaceNestingRootTopicChildrenArticleParentTopicChildrenArticle includes the requested fields of the GraphQL type Article.
|
||||
type InterfaceNestingRootTopicChildrenArticleParentTopicChildrenArticle struct {
|
||||
Typename string `json:"__typename"`
|
||||
// ID is the identifier of the content.
|
||||
Id testutil.ID `json:"id"`
|
||||
}
|
||||
@@ -47,21 +53,52 @@ type InterfaceNestingRootTopicChildrenArticleParentTopicChildrenContent interfac
|
||||
implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenArticleParentTopicChildrenContent()
|
||||
}
|
||||
|
||||
func (v InterfaceNestingRootTopicChildrenArticleParentTopicChildrenArticle) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenArticleParentTopicChildrenContent() {
|
||||
func (v *InterfaceNestingRootTopicChildrenArticleParentTopicChildrenArticle) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenArticleParentTopicChildrenContent() {
|
||||
}
|
||||
func (v InterfaceNestingRootTopicChildrenArticleParentTopicChildrenVideo) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenArticleParentTopicChildrenContent() {
|
||||
func (v *InterfaceNestingRootTopicChildrenArticleParentTopicChildrenVideo) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenArticleParentTopicChildrenContent() {
|
||||
}
|
||||
func (v InterfaceNestingRootTopicChildrenArticleParentTopicChildrenTopic) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenArticleParentTopicChildrenContent() {
|
||||
func (v *InterfaceNestingRootTopicChildrenArticleParentTopicChildrenTopic) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenArticleParentTopicChildrenContent() {
|
||||
}
|
||||
|
||||
func __unmarshalInterfaceNestingRootTopicChildrenArticleParentTopicChildrenContent(v *InterfaceNestingRootTopicChildrenArticleParentTopicChildrenContent, 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(InterfaceNestingRootTopicChildrenArticleParentTopicChildrenArticle)
|
||||
return json.Unmarshal(m, *v)
|
||||
case "Video":
|
||||
*v = new(InterfaceNestingRootTopicChildrenArticleParentTopicChildrenVideo)
|
||||
return json.Unmarshal(m, *v)
|
||||
case "Topic":
|
||||
*v = new(InterfaceNestingRootTopicChildrenArticleParentTopicChildrenTopic)
|
||||
return json.Unmarshal(m, *v)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
`Unexpected concrete type for InterfaceNestingRootTopicChildrenArticleParentTopicChildrenContent: "%v"`, tn.TypeName)
|
||||
}
|
||||
}
|
||||
|
||||
// InterfaceNestingRootTopicChildrenArticleParentTopicChildrenTopic includes the requested fields of the GraphQL type Topic.
|
||||
type InterfaceNestingRootTopicChildrenArticleParentTopicChildrenTopic struct {
|
||||
Typename string `json:"__typename"`
|
||||
// ID is the identifier of the content.
|
||||
Id testutil.ID `json:"id"`
|
||||
}
|
||||
|
||||
// InterfaceNestingRootTopicChildrenArticleParentTopicChildrenVideo includes the requested fields of the GraphQL type Video.
|
||||
type InterfaceNestingRootTopicChildrenArticleParentTopicChildrenVideo struct {
|
||||
Typename string `json:"__typename"`
|
||||
// ID is the identifier of the content.
|
||||
Id testutil.ID `json:"id"`
|
||||
}
|
||||
@@ -74,15 +111,45 @@ type InterfaceNestingRootTopicChildrenContent interface {
|
||||
implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenContent()
|
||||
}
|
||||
|
||||
func (v InterfaceNestingRootTopicChildrenArticle) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenContent() {
|
||||
func (v *InterfaceNestingRootTopicChildrenArticle) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenContent() {
|
||||
}
|
||||
func (v InterfaceNestingRootTopicChildrenVideo) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenContent() {
|
||||
func (v *InterfaceNestingRootTopicChildrenVideo) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenContent() {
|
||||
}
|
||||
func (v InterfaceNestingRootTopicChildrenTopic) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenContent() {
|
||||
func (v *InterfaceNestingRootTopicChildrenTopic) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenContent() {
|
||||
}
|
||||
|
||||
func __unmarshalInterfaceNestingRootTopicChildrenContent(v *InterfaceNestingRootTopicChildrenContent, 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(InterfaceNestingRootTopicChildrenArticle)
|
||||
return json.Unmarshal(m, *v)
|
||||
case "Video":
|
||||
*v = new(InterfaceNestingRootTopicChildrenVideo)
|
||||
return json.Unmarshal(m, *v)
|
||||
case "Topic":
|
||||
*v = new(InterfaceNestingRootTopicChildrenTopic)
|
||||
return json.Unmarshal(m, *v)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
`Unexpected concrete type for InterfaceNestingRootTopicChildrenContent: "%v"`, tn.TypeName)
|
||||
}
|
||||
}
|
||||
|
||||
// InterfaceNestingRootTopicChildrenTopic includes the requested fields of the GraphQL type Topic.
|
||||
type InterfaceNestingRootTopicChildrenTopic struct {
|
||||
Typename string `json:"__typename"`
|
||||
// ID is the identifier of the content.
|
||||
Id testutil.ID `json:"id"`
|
||||
Parent InterfaceNestingRootTopicChildrenTopicParentTopic `json:"parent"`
|
||||
@@ -90,6 +157,7 @@ type InterfaceNestingRootTopicChildrenTopic struct {
|
||||
|
||||
// InterfaceNestingRootTopicChildrenTopicParentTopic includes the requested fields of the GraphQL type Topic.
|
||||
type InterfaceNestingRootTopicChildrenTopicParentTopic struct {
|
||||
Typename string `json:"__typename"`
|
||||
// ID is documented in the Content interface.
|
||||
Id testutil.ID `json:"id"`
|
||||
Children []InterfaceNestingRootTopicChildrenTopicParentTopicChildrenContent `json:"children"`
|
||||
@@ -97,6 +165,7 @@ type InterfaceNestingRootTopicChildrenTopicParentTopic struct {
|
||||
|
||||
// InterfaceNestingRootTopicChildrenTopicParentTopicChildrenArticle includes the requested fields of the GraphQL type Article.
|
||||
type InterfaceNestingRootTopicChildrenTopicParentTopicChildrenArticle struct {
|
||||
Typename string `json:"__typename"`
|
||||
// ID is the identifier of the content.
|
||||
Id testutil.ID `json:"id"`
|
||||
}
|
||||
@@ -109,27 +178,59 @@ type InterfaceNestingRootTopicChildrenTopicParentTopicChildrenContent interface
|
||||
implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenTopicParentTopicChildrenContent()
|
||||
}
|
||||
|
||||
func (v InterfaceNestingRootTopicChildrenTopicParentTopicChildrenArticle) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenTopicParentTopicChildrenContent() {
|
||||
func (v *InterfaceNestingRootTopicChildrenTopicParentTopicChildrenArticle) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenTopicParentTopicChildrenContent() {
|
||||
}
|
||||
func (v InterfaceNestingRootTopicChildrenTopicParentTopicChildrenVideo) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenTopicParentTopicChildrenContent() {
|
||||
func (v *InterfaceNestingRootTopicChildrenTopicParentTopicChildrenVideo) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenTopicParentTopicChildrenContent() {
|
||||
}
|
||||
func (v InterfaceNestingRootTopicChildrenTopicParentTopicChildrenTopic) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenTopicParentTopicChildrenContent() {
|
||||
func (v *InterfaceNestingRootTopicChildrenTopicParentTopicChildrenTopic) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenTopicParentTopicChildrenContent() {
|
||||
}
|
||||
|
||||
func __unmarshalInterfaceNestingRootTopicChildrenTopicParentTopicChildrenContent(v *InterfaceNestingRootTopicChildrenTopicParentTopicChildrenContent, 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(InterfaceNestingRootTopicChildrenTopicParentTopicChildrenArticle)
|
||||
return json.Unmarshal(m, *v)
|
||||
case "Video":
|
||||
*v = new(InterfaceNestingRootTopicChildrenTopicParentTopicChildrenVideo)
|
||||
return json.Unmarshal(m, *v)
|
||||
case "Topic":
|
||||
*v = new(InterfaceNestingRootTopicChildrenTopicParentTopicChildrenTopic)
|
||||
return json.Unmarshal(m, *v)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
`Unexpected concrete type for InterfaceNestingRootTopicChildrenTopicParentTopicChildrenContent: "%v"`, tn.TypeName)
|
||||
}
|
||||
}
|
||||
|
||||
// InterfaceNestingRootTopicChildrenTopicParentTopicChildrenTopic includes the requested fields of the GraphQL type Topic.
|
||||
type InterfaceNestingRootTopicChildrenTopicParentTopicChildrenTopic struct {
|
||||
Typename string `json:"__typename"`
|
||||
// ID is the identifier of the content.
|
||||
Id testutil.ID `json:"id"`
|
||||
}
|
||||
|
||||
// InterfaceNestingRootTopicChildrenTopicParentTopicChildrenVideo includes the requested fields of the GraphQL type Video.
|
||||
type InterfaceNestingRootTopicChildrenTopicParentTopicChildrenVideo struct {
|
||||
Typename string `json:"__typename"`
|
||||
// ID is the identifier of the content.
|
||||
Id testutil.ID `json:"id"`
|
||||
}
|
||||
|
||||
// InterfaceNestingRootTopicChildrenVideo includes the requested fields of the GraphQL type Video.
|
||||
type InterfaceNestingRootTopicChildrenVideo struct {
|
||||
Typename string `json:"__typename"`
|
||||
// ID is the identifier of the content.
|
||||
Id testutil.ID `json:"id"`
|
||||
Parent InterfaceNestingRootTopicChildrenVideoParentTopic `json:"parent"`
|
||||
@@ -137,6 +238,7 @@ type InterfaceNestingRootTopicChildrenVideo struct {
|
||||
|
||||
// InterfaceNestingRootTopicChildrenVideoParentTopic includes the requested fields of the GraphQL type Topic.
|
||||
type InterfaceNestingRootTopicChildrenVideoParentTopic struct {
|
||||
Typename string `json:"__typename"`
|
||||
// ID is documented in the Content interface.
|
||||
Id testutil.ID `json:"id"`
|
||||
Children []InterfaceNestingRootTopicChildrenVideoParentTopicChildrenContent `json:"children"`
|
||||
@@ -144,6 +246,7 @@ type InterfaceNestingRootTopicChildrenVideoParentTopic struct {
|
||||
|
||||
// InterfaceNestingRootTopicChildrenVideoParentTopicChildrenArticle includes the requested fields of the GraphQL type Article.
|
||||
type InterfaceNestingRootTopicChildrenVideoParentTopicChildrenArticle struct {
|
||||
Typename string `json:"__typename"`
|
||||
// ID is the identifier of the content.
|
||||
Id testutil.ID `json:"id"`
|
||||
}
|
||||
@@ -156,21 +259,52 @@ type InterfaceNestingRootTopicChildrenVideoParentTopicChildrenContent interface
|
||||
implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenVideoParentTopicChildrenContent()
|
||||
}
|
||||
|
||||
func (v InterfaceNestingRootTopicChildrenVideoParentTopicChildrenArticle) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenVideoParentTopicChildrenContent() {
|
||||
func (v *InterfaceNestingRootTopicChildrenVideoParentTopicChildrenArticle) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenVideoParentTopicChildrenContent() {
|
||||
}
|
||||
func (v InterfaceNestingRootTopicChildrenVideoParentTopicChildrenVideo) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenVideoParentTopicChildrenContent() {
|
||||
func (v *InterfaceNestingRootTopicChildrenVideoParentTopicChildrenVideo) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenVideoParentTopicChildrenContent() {
|
||||
}
|
||||
func (v InterfaceNestingRootTopicChildrenVideoParentTopicChildrenTopic) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenVideoParentTopicChildrenContent() {
|
||||
func (v *InterfaceNestingRootTopicChildrenVideoParentTopicChildrenTopic) implementsGraphQLInterfaceInterfaceNestingRootTopicChildrenVideoParentTopicChildrenContent() {
|
||||
}
|
||||
|
||||
func __unmarshalInterfaceNestingRootTopicChildrenVideoParentTopicChildrenContent(v *InterfaceNestingRootTopicChildrenVideoParentTopicChildrenContent, 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(InterfaceNestingRootTopicChildrenVideoParentTopicChildrenArticle)
|
||||
return json.Unmarshal(m, *v)
|
||||
case "Video":
|
||||
*v = new(InterfaceNestingRootTopicChildrenVideoParentTopicChildrenVideo)
|
||||
return json.Unmarshal(m, *v)
|
||||
case "Topic":
|
||||
*v = new(InterfaceNestingRootTopicChildrenVideoParentTopicChildrenTopic)
|
||||
return json.Unmarshal(m, *v)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
`Unexpected concrete type for InterfaceNestingRootTopicChildrenVideoParentTopicChildrenContent: "%v"`, tn.TypeName)
|
||||
}
|
||||
}
|
||||
|
||||
// InterfaceNestingRootTopicChildrenVideoParentTopicChildrenTopic includes the requested fields of the GraphQL type Topic.
|
||||
type InterfaceNestingRootTopicChildrenVideoParentTopicChildrenTopic struct {
|
||||
Typename string `json:"__typename"`
|
||||
// ID is the identifier of the content.
|
||||
Id testutil.ID `json:"id"`
|
||||
}
|
||||
|
||||
// InterfaceNestingRootTopicChildrenVideoParentTopicChildrenVideo includes the requested fields of the GraphQL type Video.
|
||||
type InterfaceNestingRootTopicChildrenVideoParentTopicChildrenVideo struct {
|
||||
Typename string `json:"__typename"`
|
||||
// ID is the identifier of the content.
|
||||
Id testutil.ID `json:"id"`
|
||||
}
|
||||
@@ -187,10 +321,13 @@ query InterfaceNesting {
|
||||
root {
|
||||
id
|
||||
children {
|
||||
__typename
|
||||
id
|
||||
parent {
|
||||
__typename
|
||||
id
|
||||
children {
|
||||
__typename
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -2,7 +2,7 @@
|
||||
"operations": [
|
||||
{
|
||||
"operationName": "InterfaceNesting",
|
||||
"query": "\nquery InterfaceNesting {\n\troot {\n\t\tid\n\t\tchildren {\n\t\t\tid\n\t\t\tparent {\n\t\t\t\tid\n\t\t\t\tchildren {\n\t\t\t\t\tid\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n",
|
||||
"query": "\nquery InterfaceNesting {\n\troot {\n\t\tid\n\t\tchildren {\n\t\t\t__typename\n\t\t\tid\n\t\t\tparent {\n\t\t\t\t__typename\n\t\t\t\tid\n\t\t\t\tchildren {\n\t\t\t\t\t__typename\n\t\t\t\t\tid\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n",
|
||||
"sourceLocation": "testdata/queries/InterfaceNesting.graphql"
|
||||
}
|
||||
]
|
||||
|
||||
+102
-42
@@ -3,55 +3,114 @@ 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"
|
||||
)
|
||||
|
||||
// InterfaceNoFragmentsQueryRandomItemArticle includes the requested fields of the GraphQL type Article.
|
||||
type InterfaceNoFragmentsQueryRandomItemArticle struct {
|
||||
Typename string `json:"__typename"`
|
||||
// ID is the identifier of the content.
|
||||
Id testutil.ID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// InterfaceNoFragmentsQueryRandomItemContent 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 InterfaceNoFragmentsQueryRandomItemContent interface {
|
||||
implementsGraphQLInterfaceInterfaceNoFragmentsQueryRandomItemContent()
|
||||
}
|
||||
|
||||
func (v *InterfaceNoFragmentsQueryRandomItemArticle) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRandomItemContent() {
|
||||
}
|
||||
func (v *InterfaceNoFragmentsQueryRandomItemVideo) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRandomItemContent() {
|
||||
}
|
||||
func (v *InterfaceNoFragmentsQueryRandomItemTopic) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRandomItemContent() {
|
||||
}
|
||||
|
||||
func __unmarshalInterfaceNoFragmentsQueryRandomItemContent(v *InterfaceNoFragmentsQueryRandomItemContent, 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(InterfaceNoFragmentsQueryRandomItemArticle)
|
||||
return json.Unmarshal(m, *v)
|
||||
case "Video":
|
||||
*v = new(InterfaceNoFragmentsQueryRandomItemVideo)
|
||||
return json.Unmarshal(m, *v)
|
||||
case "Topic":
|
||||
*v = new(InterfaceNoFragmentsQueryRandomItemTopic)
|
||||
return json.Unmarshal(m, *v)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
`Unexpected concrete type for InterfaceNoFragmentsQueryRandomItemContent: "%v"`, tn.TypeName)
|
||||
}
|
||||
}
|
||||
|
||||
// InterfaceNoFragmentsQueryRandomItemTopic includes the requested fields of the GraphQL type Topic.
|
||||
type InterfaceNoFragmentsQueryRandomItemTopic struct {
|
||||
Typename string `json:"__typename"`
|
||||
// ID is the identifier of the content.
|
||||
Id testutil.ID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// InterfaceNoFragmentsQueryRandomItemVideo includes the requested fields of the GraphQL type Video.
|
||||
type InterfaceNoFragmentsQueryRandomItemVideo struct {
|
||||
Typename string `json:"__typename"`
|
||||
// ID is the identifier of the content.
|
||||
Id testutil.ID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// InterfaceNoFragmentsQueryResponse is returned by InterfaceNoFragmentsQuery on success.
|
||||
type InterfaceNoFragmentsQueryResponse struct {
|
||||
Root InterfaceNoFragmentsQueryRootTopic `json:"root"`
|
||||
Root InterfaceNoFragmentsQueryRootTopic `json:"root"`
|
||||
RandomItem InterfaceNoFragmentsQueryRandomItemContent `json:"-"`
|
||||
}
|
||||
|
||||
func (v *InterfaceNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
|
||||
|
||||
type InterfaceNoFragmentsQueryResponseWrapper InterfaceNoFragmentsQueryResponse
|
||||
|
||||
var firstPass struct {
|
||||
*InterfaceNoFragmentsQueryResponseWrapper
|
||||
RandomItem json.RawMessage `json:"randomItem"`
|
||||
}
|
||||
firstPass.InterfaceNoFragmentsQueryResponseWrapper = (*InterfaceNoFragmentsQueryResponseWrapper)(v)
|
||||
|
||||
err := json.Unmarshal(b, &firstPass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = __unmarshalInterfaceNoFragmentsQueryRandomItemContent(
|
||||
&v.RandomItem, firstPass.RandomItem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InterfaceNoFragmentsQueryRootTopic includes the requested fields of the GraphQL type Topic.
|
||||
type InterfaceNoFragmentsQueryRootTopic struct {
|
||||
// ID is documented in the Content interface.
|
||||
Id testutil.ID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Children []InterfaceNoFragmentsQueryRootTopicChildrenContent `json:"children"`
|
||||
}
|
||||
|
||||
// InterfaceNoFragmentsQueryRootTopicChildrenArticle includes the requested fields of the GraphQL type Article.
|
||||
type InterfaceNoFragmentsQueryRootTopicChildrenArticle struct {
|
||||
// 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.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
// Content is implemented by various types like Article, Video, and Topic.
|
||||
type InterfaceNoFragmentsQueryRootTopicChildrenContent interface {
|
||||
implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent()
|
||||
}
|
||||
|
||||
func (v InterfaceNoFragmentsQueryRootTopicChildrenArticle) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent() {
|
||||
}
|
||||
func (v InterfaceNoFragmentsQueryRootTopicChildrenVideo) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent() {
|
||||
}
|
||||
func (v InterfaceNoFragmentsQueryRootTopicChildrenTopic) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent() {
|
||||
}
|
||||
|
||||
// InterfaceNoFragmentsQueryRootTopicChildrenTopic includes the requested fields of the GraphQL type Topic.
|
||||
type InterfaceNoFragmentsQueryRootTopicChildrenTopic struct {
|
||||
// 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 {
|
||||
// ID is the identifier of the content.
|
||||
Id testutil.ID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
@@ -68,10 +127,11 @@ query InterfaceNoFragmentsQuery {
|
||||
root {
|
||||
id
|
||||
name
|
||||
children {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
randomItem {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"operations": [
|
||||
{
|
||||
"operationName": "InterfaceNoFragmentsQuery",
|
||||
"query": "\nquery InterfaceNoFragmentsQuery {\n\troot {\n\t\tid\n\t\tname\n\t\tchildren {\n\t\t\tid\n\t\t\tname\n\t\t}\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}\n",
|
||||
"sourceLocation": "testdata/queries/InterfaceNoFragments.graphql"
|
||||
}
|
||||
]
|
||||
|
||||
Vendored
+36
-26
@@ -4,6 +4,7 @@ package test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/Khan/genqlient/graphql"
|
||||
)
|
||||
@@ -21,9 +22,35 @@ type UnionNoFragmentsQueryRandomLeafLeafContent interface {
|
||||
implementsGraphQLInterfaceUnionNoFragmentsQueryRandomLeafLeafContent()
|
||||
}
|
||||
|
||||
func (v UnionNoFragmentsQueryRandomLeafArticle) implementsGraphQLInterfaceUnionNoFragmentsQueryRandomLeafLeafContent() {
|
||||
func (v *UnionNoFragmentsQueryRandomLeafArticle) implementsGraphQLInterfaceUnionNoFragmentsQueryRandomLeafLeafContent() {
|
||||
}
|
||||
func (v UnionNoFragmentsQueryRandomLeafVideo) implementsGraphQLInterfaceUnionNoFragmentsQueryRandomLeafLeafContent() {
|
||||
func (v *UnionNoFragmentsQueryRandomLeafVideo) implementsGraphQLInterfaceUnionNoFragmentsQueryRandomLeafLeafContent() {
|
||||
}
|
||||
|
||||
func __unmarshalUnionNoFragmentsQueryRandomLeafLeafContent(v *UnionNoFragmentsQueryRandomLeafLeafContent, 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(UnionNoFragmentsQueryRandomLeafArticle)
|
||||
return json.Unmarshal(m, *v)
|
||||
case "Video":
|
||||
*v = new(UnionNoFragmentsQueryRandomLeafVideo)
|
||||
return json.Unmarshal(m, *v)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
`Unexpected concrete type for UnionNoFragmentsQueryRandomLeafLeafContent: "%v"`, tn.TypeName)
|
||||
}
|
||||
}
|
||||
|
||||
// UnionNoFragmentsQueryRandomLeafVideo includes the requested fields of the GraphQL type Video.
|
||||
@@ -37,39 +64,22 @@ type UnionNoFragmentsQueryResponse struct {
|
||||
}
|
||||
|
||||
func (v *UnionNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
|
||||
|
||||
type UnionNoFragmentsQueryResponseWrapper UnionNoFragmentsQueryResponse
|
||||
|
||||
var firstPass struct {
|
||||
*UnionNoFragmentsQueryResponse
|
||||
*UnionNoFragmentsQueryResponseWrapper
|
||||
RandomLeaf json.RawMessage `json:"randomLeaf"`
|
||||
}
|
||||
firstPass.UnionNoFragmentsQueryResponse = v
|
||||
firstPass.UnionNoFragmentsQueryResponseWrapper = (*UnionNoFragmentsQueryResponseWrapper)(v)
|
||||
|
||||
err := json.Unmarshal(b, &firstPass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var tn struct {
|
||||
TypeName string `json:"__typename"`
|
||||
}
|
||||
err = json.Unmarshal(firstPass.RandomLeaf, &tn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch tn.TypeName {
|
||||
|
||||
case "Article":
|
||||
|
||||
v.RandomLeaf = UnionNoFragmentsQueryRandomLeafArticle{}
|
||||
err = json.Unmarshal(
|
||||
firstPass.RandomLeaf, &v.RandomLeaf)
|
||||
|
||||
case "Video":
|
||||
|
||||
v.RandomLeaf = UnionNoFragmentsQueryRandomLeafVideo{}
|
||||
err = json.Unmarshal(
|
||||
firstPass.RandomLeaf, &v.RandomLeaf)
|
||||
|
||||
}
|
||||
err = __unmarshalUnionNoFragmentsQueryRandomLeafLeafContent(
|
||||
&v.RandomLeaf, firstPass.RandomLeaf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
testdata/errors/MissingTypeName.schema.graphql:2: union/interface type I must request __typename
|
||||
@@ -0,0 +1 @@
|
||||
testdata/errors/MissingTypeName.schema.graphql:2: union/interface type I must request __typename
|
||||
+51
-3
@@ -126,11 +126,45 @@ func (typ *goStructType) WriteDefinition(w io.Writer, g *generator) error {
|
||||
}
|
||||
fmt.Fprintf(w, "}\n")
|
||||
|
||||
return g.maybeWriteUnmarshal(w, typ)
|
||||
// Now, if needed, write the unmarshaler.
|
||||
//
|
||||
// Specifically, in order to unmarshal interface values, we need to add an
|
||||
// UnmarshalJSON method to each type which has an interface-typed *field*
|
||||
// (not the interface type itself -- we can't add methods to that).
|
||||
// But we put most of the logic in a per-interface-type helper function,
|
||||
// written along with the interface type; the UnmarshalJSON method is just
|
||||
// the boilerplate.
|
||||
if len(typ.AbstractFields()) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(benkraft): Avoid having to enumerate these in advance; just let the
|
||||
// template add them directly.
|
||||
_, err := g.addRef("encoding/json.Unmarshal")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return g.execute("unmarshal.go.tmpl", w, typ)
|
||||
}
|
||||
|
||||
func (typ *goStructType) Reference() string { return typ.GoName }
|
||||
|
||||
// AbstractFields returns all the fields which are abstract types (i.e. GraphQL
|
||||
// unions and interfaces; equivalently, types represented by interfaces in Go).
|
||||
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 {
|
||||
ret = append(ret, field)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// goInterfaceType represents a Go interface type, used to represent a GraphQL
|
||||
// interface or union type.
|
||||
type goInterfaceType struct {
|
||||
@@ -154,11 +188,25 @@ func (typ *goInterfaceType) WriteDefinition(w io.Writer, g *generator) error {
|
||||
|
||||
// Now, write out the implementations.
|
||||
for _, impl := range typ.Implementations {
|
||||
fmt.Fprintf(w, "func (v %s) %s() {}\n",
|
||||
fmt.Fprintf(w, "func (v *%s) %s() {}\n",
|
||||
impl.Reference(), implementsMethodName)
|
||||
}
|
||||
|
||||
return nil
|
||||
// Finally, write the unmarshal-helper, which will be called by struct
|
||||
// fields referencing this type (see goStructType.WriteDefinition).
|
||||
//
|
||||
// TODO(benkraft): Avoid having to enumerate these refs in advance; just
|
||||
// let the template add them directly.
|
||||
_, err := g.addRef("encoding/json.Unmarshal")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = g.addRef("fmt.Errorf")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return g.execute("unmarshal_helper.go.tmpl", w, typ)
|
||||
}
|
||||
|
||||
func (typ *goInterfaceType) Reference() string { return typ.GoName }
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package generate
|
||||
|
||||
import "io"
|
||||
|
||||
// TODO(benkraft): We could potentially get rid of these now, and do everything
|
||||
// directly from the types.
|
||||
type templateData struct {
|
||||
// Go type to which the method will be added
|
||||
Type string
|
||||
// Abstract fields of the type, which need special handling.
|
||||
Fields []abstractField
|
||||
}
|
||||
|
||||
type abstractField struct {
|
||||
// Name of the field, in Go and JSON
|
||||
GoName, JSONName string
|
||||
// Concrete types the field might take.
|
||||
ConcreteTypes []concreteType
|
||||
}
|
||||
|
||||
type concreteType struct {
|
||||
// Name of the type, in Go and GraphQL
|
||||
GoName, GraphQLName string
|
||||
}
|
||||
|
||||
func (g *generator) maybeWriteUnmarshal(w io.Writer, typ *goStructType) error {
|
||||
data := templateData{Type: typ.GoName}
|
||||
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 iface, ok := field.GoType.(*goInterfaceType); ok {
|
||||
fieldInfo := abstractField{
|
||||
GoName: field.GoName,
|
||||
JSONName: field.JSONName,
|
||||
}
|
||||
for _, impl := range iface.Implementations {
|
||||
fieldInfo.ConcreteTypes = append(fieldInfo.ConcreteTypes,
|
||||
concreteType{
|
||||
GoName: impl.GoName,
|
||||
GraphQLName: impl.GraphQLName,
|
||||
})
|
||||
}
|
||||
data.Fields = append(data.Fields, fieldInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if len(data.Fields) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := g.addRef("encoding/json.Unmarshal")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return g.execute("unmarshal.go.tmpl", w, data)
|
||||
}
|
||||
+28
-28
@@ -1,40 +1,40 @@
|
||||
{{/* (the blank lines at the start are intentional, to separate
|
||||
UnmarshalJSON from the function it follows) */}}
|
||||
|
||||
func (v *{{.Type}}) UnmarshalJSON(b []byte) error {
|
||||
var firstPass struct{
|
||||
*{{.Type}}
|
||||
{{range .Fields -}}
|
||||
{{.GoName}} {{ref "encoding/json.RawMessage"}} `json:"{{.JSONName}}"`
|
||||
func (v *{{.GoName}}) UnmarshalJSON(b []byte) error {
|
||||
{{/* We want to specially handle the abstract fields (.AbstractFields),
|
||||
but unmarshal everything else normally. To handle abstract fields,
|
||||
first we unmarshal them into a json.RawMessage, and then handle those
|
||||
further, below. For the rest, we just want to call json.Unmarshal.
|
||||
But if we do that naively on a value of type `.Type`, it will call
|
||||
this function again, and recurse infinitely. So we make a wrapper
|
||||
type -- with a different name, thus different methods, but the same
|
||||
fields, and unmarshal into that. For more on why this is so
|
||||
difficult, see
|
||||
https://github.com/benjaminjkraft/notes/blob/master/go-json-interfaces.md
|
||||
TODO(benkraft)): Ensure `{{.Type}}Wrapper` won't collide with any
|
||||
other type we need. (For the most part it being locally-scoped saves
|
||||
us; it's not clear if this can be a problem in practice.)
|
||||
*/}}
|
||||
type {{.GoName}}Wrapper {{.GoName}}
|
||||
|
||||
var firstPass struct{
|
||||
*{{.GoName}}Wrapper
|
||||
{{range .AbstractFields -}}
|
||||
{{.GoName}} {{ref "encoding/json.RawMessage"}} `json:"{{.JSONName}}"`
|
||||
{{end}}
|
||||
}
|
||||
firstPass.{{.Type}} = v
|
||||
}
|
||||
firstPass.{{.GoName}}Wrapper = (*{{.GoName}}Wrapper)(v)
|
||||
|
||||
err := {{ref "encoding/json.Unmarshal"}}(b, &firstPass)
|
||||
err := {{ref "encoding/json.Unmarshal"}}(b, &firstPass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
{{/* TODO(benkraft): split this out into a separate helper function,
|
||||
generated for each interface type, rather than generating it inline
|
||||
with the struct type. */}}
|
||||
{{range .Fields -}}
|
||||
var tn struct { TypeName string `json:"__typename"` }
|
||||
err = {{ref "encoding/json.Unmarshal"}}(firstPass.{{.GoName}}, &tn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch tn.TypeName {
|
||||
{{with $field := .}}
|
||||
{{range $field.ConcreteTypes}}
|
||||
case "{{.GraphQLName}}":
|
||||
{{/* TODO: handle repeated fields! */}}
|
||||
v.{{$field.GoName}} = {{.GoName}}{}
|
||||
err = {{ref "encoding/json.Unmarshal"}}(
|
||||
firstPass.{{$field.GoName}}, &v.{{$field.GoName}})
|
||||
{{end}}
|
||||
{{end}}
|
||||
}
|
||||
{{/* 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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{{/* (the blank lines at the start are intentional, to separate
|
||||
the helper from the function it follows) */}}
|
||||
|
||||
func __unmarshal{{.GoName}}(v *{{.GoName}}, m {{ref "encoding/json.RawMessage"}}) error {
|
||||
if string(m) == "null" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var tn struct {
|
||||
TypeName string `json:"__typename"`
|
||||
}
|
||||
err := {{ref "encoding/json.Unmarshal"}}(m, &tn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch tn.TypeName {
|
||||
{{range .Implementations -}}
|
||||
case "{{.GraphQLName}}":
|
||||
{{/* TODO: handle repeated fields! */ -}}
|
||||
*v = new({{.GoName}})
|
||||
return {{ref "encoding/json.Unmarshal"}}(m, *v)
|
||||
{{end -}}
|
||||
default:
|
||||
return {{ref "fmt.Errorf"}}(
|
||||
`Unexpected concrete type for {{.GoName}}: "%v"`, tn.TypeName)
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,98 @@ package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/Khan/genqlient/graphql"
|
||||
)
|
||||
|
||||
// queryWithInterfaceNoFragmentsBeing includes the requested fields of the GraphQL type Being.
|
||||
type queryWithInterfaceNoFragmentsBeing interface {
|
||||
implementsGraphQLInterfacequeryWithInterfaceNoFragmentsBeing()
|
||||
}
|
||||
|
||||
func (v *queryWithInterfaceNoFragmentsBeingUser) implementsGraphQLInterfacequeryWithInterfaceNoFragmentsBeing() {
|
||||
}
|
||||
func (v *queryWithInterfaceNoFragmentsBeingAnimal) implementsGraphQLInterfacequeryWithInterfaceNoFragmentsBeing() {
|
||||
}
|
||||
|
||||
func __unmarshalqueryWithInterfaceNoFragmentsBeing(v *queryWithInterfaceNoFragmentsBeing, 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(queryWithInterfaceNoFragmentsBeingUser)
|
||||
return json.Unmarshal(m, *v)
|
||||
case "Animal":
|
||||
*v = new(queryWithInterfaceNoFragmentsBeingAnimal)
|
||||
return json.Unmarshal(m, *v)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
`Unexpected concrete type for queryWithInterfaceNoFragmentsBeing: "%v"`, tn.TypeName)
|
||||
}
|
||||
}
|
||||
|
||||
// queryWithInterfaceNoFragmentsBeingAnimal includes the requested fields of the GraphQL type Animal.
|
||||
type queryWithInterfaceNoFragmentsBeingAnimal struct {
|
||||
Typename string `json:"__typename"`
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// queryWithInterfaceNoFragmentsBeingUser includes the requested fields of the GraphQL type User.
|
||||
type queryWithInterfaceNoFragmentsBeingUser struct {
|
||||
Typename string `json:"__typename"`
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// queryWithInterfaceNoFragmentsMeUser includes the requested fields of the GraphQL type User.
|
||||
type queryWithInterfaceNoFragmentsMeUser struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// queryWithInterfaceNoFragmentsResponse is returned by queryWithInterfaceNoFragments on success.
|
||||
type queryWithInterfaceNoFragmentsResponse struct {
|
||||
Being queryWithInterfaceNoFragmentsBeing `json:"-"`
|
||||
Me queryWithInterfaceNoFragmentsMeUser `json:"me"`
|
||||
}
|
||||
|
||||
func (v *queryWithInterfaceNoFragmentsResponse) UnmarshalJSON(b []byte) error {
|
||||
|
||||
type queryWithInterfaceNoFragmentsResponseWrapper queryWithInterfaceNoFragmentsResponse
|
||||
|
||||
var firstPass struct {
|
||||
*queryWithInterfaceNoFragmentsResponseWrapper
|
||||
Being json.RawMessage `json:"being"`
|
||||
}
|
||||
firstPass.queryWithInterfaceNoFragmentsResponseWrapper = (*queryWithInterfaceNoFragmentsResponseWrapper)(v)
|
||||
|
||||
err := json.Unmarshal(b, &firstPass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = __unmarshalqueryWithInterfaceNoFragmentsBeing(
|
||||
&v.Being, firstPass.Being)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// queryWithVariablesResponse is returned by queryWithVariables on success.
|
||||
type queryWithVariablesResponse struct {
|
||||
User queryWithVariablesUser `json:"user"`
|
||||
@@ -82,3 +170,35 @@ query queryWithVariables ($id: ID!) {
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
func queryWithInterfaceNoFragments(
|
||||
ctx context.Context,
|
||||
client graphql.Client,
|
||||
id string,
|
||||
) (*queryWithInterfaceNoFragmentsResponse, error) {
|
||||
variables := map[string]interface{}{
|
||||
"id": id,
|
||||
}
|
||||
|
||||
var retval queryWithInterfaceNoFragmentsResponse
|
||||
err := client.MakeRequest(
|
||||
ctx,
|
||||
"queryWithInterfaceNoFragments",
|
||||
`
|
||||
query queryWithInterfaceNoFragments ($id: ID!) {
|
||||
being(id: $id) {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
me {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
`,
|
||||
&retval,
|
||||
variables,
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
@@ -2,3 +2,4 @@ schema: schema.graphql
|
||||
operations:
|
||||
- "*_test.go"
|
||||
generated: generated.go
|
||||
allow_broken_features: true
|
||||
|
||||
@@ -56,6 +56,49 @@ func TestVariables(t *testing.T) {
|
||||
assert.Zero(t, resp.User)
|
||||
}
|
||||
|
||||
func TestInterfaceNoFragments(t *testing.T) {
|
||||
_ = `# @genqlient
|
||||
query queryWithInterfaceNoFragments($id: ID!) {
|
||||
being(id: $id) { __typename id name }
|
||||
me { id name }
|
||||
}`
|
||||
|
||||
ctx := context.Background()
|
||||
server := server.RunServer()
|
||||
defer server.Close()
|
||||
client := graphql.NewClient(server.URL, http.DefaultClient)
|
||||
|
||||
resp, err := queryWithInterfaceNoFragments(ctx, client, "1")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "1", resp.Me.Id)
|
||||
assert.Equal(t, "Yours Truly", resp.Me.Name)
|
||||
|
||||
user, ok := resp.Being.(*queryWithInterfaceNoFragmentsBeingUser)
|
||||
require.Truef(t, ok, "got %T, not User", resp.Being)
|
||||
assert.Equal(t, "1", user.Id)
|
||||
assert.Equal(t, "Yours Truly", user.Name)
|
||||
|
||||
resp, err = queryWithInterfaceNoFragments(ctx, client, "3")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "1", resp.Me.Id)
|
||||
assert.Equal(t, "Yours Truly", resp.Me.Name)
|
||||
|
||||
animal, ok := resp.Being.(*queryWithInterfaceNoFragmentsBeingAnimal)
|
||||
require.Truef(t, ok, "got %T, not Animal", resp.Being)
|
||||
assert.Equal(t, "3", animal.Id)
|
||||
assert.Equal(t, "Fido", animal.Name)
|
||||
|
||||
resp, err = queryWithInterfaceNoFragments(ctx, client, "4757233945723")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "1", resp.Me.Id)
|
||||
assert.Equal(t, "Yours Truly", resp.Me.Name)
|
||||
|
||||
assert.Nil(t, resp.Being)
|
||||
}
|
||||
|
||||
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,10 +1,28 @@
|
||||
type Query {
|
||||
me: User
|
||||
user(id: ID!): User
|
||||
being(id: ID!): Being
|
||||
}
|
||||
|
||||
type User {
|
||||
type User implements Being {
|
||||
id: ID!
|
||||
name: String!
|
||||
luckyNumber: Int
|
||||
}
|
||||
|
||||
type Animal implements Being {
|
||||
id: ID!
|
||||
name: String!
|
||||
species: Species!
|
||||
owner: Being
|
||||
}
|
||||
|
||||
enum Species {
|
||||
DOG
|
||||
COELACANTH
|
||||
}
|
||||
|
||||
interface Being {
|
||||
id: ID!
|
||||
name: String!
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
@@ -40,9 +41,17 @@ type DirectiveRoot struct {
|
||||
}
|
||||
|
||||
type ComplexityRoot struct {
|
||||
Animal struct {
|
||||
ID func(childComplexity int) int
|
||||
Name func(childComplexity int) int
|
||||
Owner func(childComplexity int) int
|
||||
Species func(childComplexity int) int
|
||||
}
|
||||
|
||||
Query struct {
|
||||
Me func(childComplexity int) int
|
||||
User func(childComplexity int, id string) int
|
||||
Being func(childComplexity int, id string) int
|
||||
Me func(childComplexity int) int
|
||||
User func(childComplexity int, id string) int
|
||||
}
|
||||
|
||||
User struct {
|
||||
@@ -55,6 +64,7 @@ type ComplexityRoot struct {
|
||||
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)
|
||||
}
|
||||
|
||||
type executableSchema struct {
|
||||
@@ -72,6 +82,46 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
_ = ec
|
||||
switch typeName + "." + field {
|
||||
|
||||
case "Animal.id":
|
||||
if e.complexity.Animal.ID == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Animal.ID(childComplexity), true
|
||||
|
||||
case "Animal.name":
|
||||
if e.complexity.Animal.Name == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Animal.Name(childComplexity), true
|
||||
|
||||
case "Animal.owner":
|
||||
if e.complexity.Animal.Owner == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Animal.Owner(childComplexity), true
|
||||
|
||||
case "Animal.species":
|
||||
if e.complexity.Animal.Species == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Animal.Species(childComplexity), true
|
||||
|
||||
case "Query.being":
|
||||
if e.complexity.Query.Being == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Query_being_args(context.TODO(), rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Query.Being(childComplexity, args["id"].(string)), true
|
||||
|
||||
case "Query.me":
|
||||
if e.complexity.Query.Me == nil {
|
||||
break
|
||||
@@ -165,13 +215,31 @@ var sources = []*ast.Source{
|
||||
{Name: "../schema.graphql", Input: `type Query {
|
||||
me: User
|
||||
user(id: ID!): User
|
||||
being(id: ID!): Being
|
||||
}
|
||||
|
||||
type User {
|
||||
type User implements Being {
|
||||
id: ID!
|
||||
name: String!
|
||||
luckyNumber: Int
|
||||
}
|
||||
|
||||
type Animal implements Being {
|
||||
id: ID!
|
||||
name: String!
|
||||
species: Species!
|
||||
owner: Being
|
||||
}
|
||||
|
||||
enum Species {
|
||||
DOG
|
||||
COELACANTH
|
||||
}
|
||||
|
||||
interface Being {
|
||||
id: ID!
|
||||
name: String!
|
||||
}
|
||||
`, BuiltIn: false},
|
||||
}
|
||||
var parsedSchema = gqlparser.MustLoadSchema(sources...)
|
||||
@@ -195,6 +263,21 @@ func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Query_being_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["id"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id"))
|
||||
arg0, err = ec.unmarshalNID2string(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["id"] = 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{}{}
|
||||
@@ -248,6 +331,143 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg
|
||||
|
||||
// region **************************** field.gotpl *****************************
|
||||
|
||||
func (ec *executionContext) _Animal_id(ctx context.Context, field graphql.CollectedField, obj *Animal) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "Animal",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.ID, nil
|
||||
})
|
||||
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.(string)
|
||||
fc.Result = res
|
||||
return ec.marshalNID2string(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Animal_name(ctx context.Context, field graphql.CollectedField, obj *Animal) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "Animal",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.Name, nil
|
||||
})
|
||||
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.(string)
|
||||
fc.Result = res
|
||||
return ec.marshalNString2string(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Animal_species(ctx context.Context, field graphql.CollectedField, obj *Animal) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "Animal",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.Species, nil
|
||||
})
|
||||
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.(Species)
|
||||
fc.Result = res
|
||||
return ec.marshalNSpecies2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐSpecies(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Animal_owner(ctx context.Context, field graphql.CollectedField, obj *Animal) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "Animal",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.Owner, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(Being)
|
||||
fc.Result = res
|
||||
return ec.marshalOBeing2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐBeing(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Query_me(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@@ -319,6 +539,45 @@ func (ec *executionContext) _Query_user(ctx context.Context, field graphql.Colle
|
||||
return ec.marshalOUser2ᚖgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐUser(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Query_being(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_being_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().Being(rctx, args["id"].(string))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(Being)
|
||||
fc.Result = res
|
||||
return ec.marshalOBeing2githubᚗ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 {
|
||||
@@ -1583,10 +1842,72 @@ func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.Co
|
||||
|
||||
// region ************************** interface.gotpl ***************************
|
||||
|
||||
func (ec *executionContext) _Being(ctx context.Context, sel ast.SelectionSet, obj Being) graphql.Marshaler {
|
||||
switch obj := (obj).(type) {
|
||||
case nil:
|
||||
return graphql.Null
|
||||
case User:
|
||||
return ec._User(ctx, sel, &obj)
|
||||
case *User:
|
||||
if obj == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
return ec._User(ctx, sel, obj)
|
||||
case Animal:
|
||||
return ec._Animal(ctx, sel, &obj)
|
||||
case *Animal:
|
||||
if obj == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
return ec._Animal(ctx, sel, obj)
|
||||
default:
|
||||
panic(fmt.Errorf("unexpected type %T", obj))
|
||||
}
|
||||
}
|
||||
|
||||
// endregion ************************** interface.gotpl ***************************
|
||||
|
||||
// region **************************** object.gotpl ****************************
|
||||
|
||||
var animalImplementors = []string{"Animal", "Being"}
|
||||
|
||||
func (ec *executionContext) _Animal(ctx context.Context, sel ast.SelectionSet, obj *Animal) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ec.OperationContext, sel, animalImplementors)
|
||||
|
||||
out := graphql.NewFieldSet(fields)
|
||||
var invalids uint32
|
||||
for i, field := range fields {
|
||||
switch field.Name {
|
||||
case "__typename":
|
||||
out.Values[i] = graphql.MarshalString("Animal")
|
||||
case "id":
|
||||
out.Values[i] = ec._Animal_id(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "name":
|
||||
out.Values[i] = ec._Animal_name(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "species":
|
||||
out.Values[i] = ec._Animal_species(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "owner":
|
||||
out.Values[i] = ec._Animal_owner(ctx, field, obj)
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
}
|
||||
out.Dispatch()
|
||||
if invalids > 0 {
|
||||
return graphql.Null
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
var queryImplementors = []string{"Query"}
|
||||
|
||||
func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler {
|
||||
@@ -1624,6 +1945,17 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
|
||||
res = ec._Query_user(ctx, field)
|
||||
return res
|
||||
})
|
||||
case "being":
|
||||
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_being(ctx, field)
|
||||
return res
|
||||
})
|
||||
case "__type":
|
||||
out.Values[i] = ec._Query___type(ctx, field)
|
||||
case "__schema":
|
||||
@@ -1639,7 +1971,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
|
||||
return out
|
||||
}
|
||||
|
||||
var userImplementors = []string{"User"}
|
||||
var userImplementors = []string{"User", "Being"}
|
||||
|
||||
func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj *User) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ec.OperationContext, sel, userImplementors)
|
||||
@@ -1948,6 +2280,16 @@ func (ec *executionContext) marshalNID2string(ctx context.Context, sel ast.Selec
|
||||
return res
|
||||
}
|
||||
|
||||
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)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNSpecies2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐSpecies(ctx context.Context, sel ast.SelectionSet, v Species) graphql.Marshaler {
|
||||
return v
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNString2string(ctx context.Context, v interface{}) (string, error) {
|
||||
res, err := graphql.UnmarshalString(v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
@@ -2192,6 +2534,13 @@ func (ec *executionContext) marshalN__TypeKind2string(ctx context.Context, sel a
|
||||
return res
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalOBeing2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐBeing(ctx context.Context, sel ast.SelectionSet, v Being) graphql.Marshaler {
|
||||
if v == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
return ec._Being(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalOBoolean2bool(ctx context.Context, v interface{}) (bool, error) {
|
||||
res, err := graphql.UnmarshalBoolean(v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
|
||||
@@ -2,8 +2,70 @@
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Being interface {
|
||||
IsBeing()
|
||||
}
|
||||
|
||||
type Animal struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Species Species `json:"species"`
|
||||
Owner Being `json:"owner"`
|
||||
}
|
||||
|
||||
func (Animal) IsBeing() {}
|
||||
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
LuckyNumber *int `json:"luckyNumber"`
|
||||
}
|
||||
|
||||
func (User) IsBeing() {}
|
||||
|
||||
type Species string
|
||||
|
||||
const (
|
||||
SpeciesDog Species = "DOG"
|
||||
SpeciesCoelacanth Species = "COELACANTH"
|
||||
)
|
||||
|
||||
var AllSpecies = []Species{
|
||||
SpeciesDog,
|
||||
SpeciesCoelacanth,
|
||||
}
|
||||
|
||||
func (e Species) IsValid() bool {
|
||||
switch e {
|
||||
case SpeciesDog, SpeciesCoelacanth:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e Species) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *Species) UnmarshalGQL(v interface{}) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = Species(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid Species", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Species) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ var users = []*User{
|
||||
{ID: "2", Name: "Raven", LuckyNumber: intptr(-1)},
|
||||
}
|
||||
|
||||
var animals = []*Animal{
|
||||
{ID: "3", Name: "Fido", Species: SpeciesDog, Owner: userByID("0")},
|
||||
{ID: "4", Name: "Old One", Species: SpeciesCoelacanth, Owner: nil},
|
||||
}
|
||||
|
||||
func userByID(id string) *User {
|
||||
for _, user := range users {
|
||||
if id == user.ID {
|
||||
@@ -24,6 +29,20 @@ func userByID(id string) *User {
|
||||
return nil
|
||||
}
|
||||
|
||||
func beingByID(id string) Being {
|
||||
for _, user := range users {
|
||||
if id == user.ID {
|
||||
return user
|
||||
}
|
||||
}
|
||||
for _, animal := range animals {
|
||||
if id == animal.ID {
|
||||
return animal
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) Me(ctx context.Context) (*User, error) {
|
||||
return userByID("1"), nil
|
||||
}
|
||||
@@ -32,6 +51,10 @@ func (r *queryResolver) User(ctx context.Context, id string) (*User, error) {
|
||||
return userByID(id), nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) Being(ctx context.Context, id string) (Being, error) {
|
||||
return beingByID(id), nil
|
||||
}
|
||||
|
||||
func RunServer() *httptest.Server {
|
||||
gqlgenServer := handler.New(NewExecutableSchema(Config{Resolvers: &resolver{}}))
|
||||
gqlgenServer.AddTransport(transport.POST{})
|
||||
|
||||
Reference in New Issue
Block a user