From c54143aef44be14e6875713b0b80794a59bb9d6e Mon Sep 17 00:00:00 2001 From: Ben Kraft Date: Tue, 24 Aug 2021 18:01:15 -0700 Subject: [PATCH] Add support for inline fragments In this commit I add support for inline fragments (`... on MyType { fields }`) to genqlient. This will make interfaces a lot more useful! In future commits I'll add named fragments, for which we'll generate slightly different types, as discussed in DESIGN.md. In general, implementing the flattening approach described in DESIGN.md was... surprisingly easy. All we have to do is recurse on applicable fragments when generating our selection-set. The refactor to selection-set handling this encouraged was, I think, quite beneficial. It did reveal two tricky pre-existing issues. One issue is that GraphQL allows for duplicate selections, as long as they match. (In practice, this is only useful in the context of fragments, although GraphQL allows it even without.) I decided to handle the simple case (duplicate leaf fields; we just deduplicate) but leave to the future the complex cases where we need to merge different sub-selections (now #64). For now we just forbid that; we can see how much it comes up. The other issue is that we are generating type-names incorrectly for interface types; I had intended to do `MyInterfaceMyFieldMyType` for shared fields and `MyImplMyFieldMyType` for non-shared ones, but instead I did `MyFieldMyType`, which is inconsistent already and can result in conflicts in the presence of fragments. I'm going to fix this in a separate commit, though, because it's going to require some refactoring and is irrelevant to the main logic of this commit; I left some TODOs in the tests related to this. Issue: https://github.com/Khan/genqlient/issues/8 Test plan: make check Reviewers: marksandstrom, adam, miguel --- generate/convert.go | 211 +++- .../testdata/errors/ConflictingSelections.go | 12 + .../errors/ConflictingSelections.graphql | 8 + .../ConflictingSelections.schema.graphql | 18 + .../queries/ComplexInlineFragments.graphql | 69 ++ .../queries/SimpleInlineFragment.graphql | 8 + generate/testdata/queries/schema.graphql | 31 +- ....graphql-ComplexInlineFragments.graphql.go | 939 ++++++++++++++++++ ...raphql-ComplexInlineFragments.graphql.json | 9 + ...nt.graphql-SimpleInlineFragment.graphql.go | 186 ++++ ....graphql-SimpleInlineFragment.graphql.json | 9 + ...estGenerateErrors-ConflictingSelections.go | 1 + ...nerateErrors-ConflictingSelections.graphql | 1 + internal/integration/generated.go | 303 ++++++ internal/integration/integration_test.go | 85 ++ internal/integration/schema.graphql | 13 +- internal/integration/server/gqlgen_exec.go | 378 ++++++- internal/integration/server/gqlgen_models.go | 23 +- internal/integration/server/server.go | 29 +- 19 files changed, 2267 insertions(+), 66 deletions(-) create mode 100644 generate/testdata/errors/ConflictingSelections.go create mode 100644 generate/testdata/errors/ConflictingSelections.graphql create mode 100644 generate/testdata/errors/ConflictingSelections.schema.graphql create mode 100644 generate/testdata/queries/ComplexInlineFragments.graphql create mode 100644 generate/testdata/queries/SimpleInlineFragment.graphql create mode 100644 generate/testdata/snapshots/TestGenerate-ComplexInlineFragments.graphql-ComplexInlineFragments.graphql.go create mode 100644 generate/testdata/snapshots/TestGenerate-ComplexInlineFragments.graphql-ComplexInlineFragments.graphql.json create mode 100644 generate/testdata/snapshots/TestGenerate-SimpleInlineFragment.graphql-SimpleInlineFragment.graphql.go create mode 100644 generate/testdata/snapshots/TestGenerate-SimpleInlineFragment.graphql-SimpleInlineFragment.graphql.json create mode 100644 generate/testdata/snapshots/TestGenerateErrors-ConflictingSelections.go create mode 100644 generate/testdata/snapshots/TestGenerateErrors-ConflictingSelections.graphql diff --git a/generate/convert.go b/generate/convert.go index c86780f5..e1f106cb 100644 --- a/generate/convert.go +++ b/generate/convert.go @@ -186,38 +186,20 @@ func (g *generator) convertDefinition( switch def.Kind { case ast.Object: + fields, err := g.convertSelectionSet( + namePrefix, selectionSet, def, queryOptions) + if err != nil { + return nil, err + } + goType := &goStructType{ GoName: name, Description: def.Description, GraphQLName: def.Name, - Fields: make([]*goStructField, len(selectionSet)), + Fields: fields, Incomplete: true, } g.typeMap[name] = goType - - for i, selection := range selectionSet { - _, selectionDirective, err := g.parsePrecedingComment( - selection, selection.GetPosition()) - if err != nil { - return nil, err - } - selectionOptions := queryOptions.merge(selectionDirective) - - switch selection := selection.(type) { - case *ast.Field: - goType.Fields[i], err = g.convertField( - namePrefix, selection, selectionOptions, queryOptions) - if err != nil { - return nil, err - } - case *ast.FragmentSpread: - return nil, errorf(selection.Position, "not implemented: %T", selection) - case *ast.InlineFragment: - return nil, errorf(selection.Position, "not implemented: %T", selection) - default: - return nil, errorf(nil, "invalid selection type: %T", selection) - } - } return goType, nil case ast.InputObject: @@ -260,38 +242,22 @@ func (g *generator) convertDefinition( return nil, errorf(pos, "not implemented: %v", def.Kind) } + sharedFields, err := g.convertSelectionSet( + namePrefix, selectionSet, def, queryOptions) + if err != nil { + return nil, err + } + implementationTypes := g.schema.GetPossibleTypes(def) goType := &goInterfaceType{ GoName: name, Description: def.Description, GraphQLName: def.Name, - SharedFields: make([]*goStructField, 0, len(selectionSet)), + SharedFields: sharedFields, Implementations: make([]*goStructType, len(implementationTypes)), } g.typeMap[name] = goType - // TODO(benkraft): This sorta-duplicates what we'll do in each - // implementation when it traverses the fields. But they'll differ - // more once we support fragments; at that point we should figure out - // how to refactor. - for _, selection := range selectionSet { - field, ok := selection.(*ast.Field) - if !ok { // fragment/interface, not a shared field - continue - } - _, fieldDirective, err := g.parsePrecedingComment(field, field.GetPosition()) - if err != nil { - return nil, err - } - fieldOptions := queryOptions.merge(fieldDirective) - - goField, err := g.convertField(namePrefix, field, fieldOptions, queryOptions) - if err != nil { - return nil, err - } - goType.SharedFields = append(goType.SharedFields, goField) - } - for i, implDef := range implementationTypes { // Note for shared fields we propagate forward the interface's // name-prefix: that is, the implementations will have fields with @@ -346,7 +312,154 @@ func (g *generator) convertDefinition( } } -// convertField converts a single GraphQL operation-field into a GraphQL type. +// convertSelectionSet converts a GraphQL selection-set into a list of +// corresponding Go struct-fields (and their Go types) +// +// A selection-set is a list of fields within braces like `{ myField }`, as +// appears at the toplevel of a query, in a field's sub-selections, or within +// an inline or named fragment. +// +// containingTypedef is the type-def whose fields we are selecting, and may be +// an object type or an interface type. In the case of interfaces, we'll call +// convertSelectionSet once for the interface, and once for each +// implementation. +func (g *generator) convertSelectionSet( + namePrefix string, + selectionSet ast.SelectionSet, + containingTypedef *ast.Definition, + queryOptions *GenqlientDirective, +) ([]*goStructField, error) { + fields := make([]*goStructField, 0, len(selectionSet)) + for _, selection := range selectionSet { + _, selectionDirective, err := g.parsePrecedingComment( + selection, selection.GetPosition()) + if err != nil { + return nil, err + } + selectionOptions := queryOptions.merge(selectionDirective) + + switch selection := selection.(type) { + case *ast.Field: + field, err := g.convertField( + namePrefix, selection, selectionOptions, queryOptions) + if err != nil { + return nil, err + } + fields = append(fields, field) + case *ast.FragmentSpread: + return nil, errorf(selection.Position, "not implemented: %T", selection) + case *ast.InlineFragment: + fragmentFields, err := g.convertInlineFragment( + namePrefix, selection, containingTypedef, queryOptions) + if err != nil { + return nil, err + } + fields = append(fields, fragmentFields...) + default: + return nil, errorf(nil, "invalid selection type: %T", selection) + } + } + + // We need to deduplicate, if you asked for + // { id, id, id, ... on SubType { id } } + // (which, yes, is legal) we'll treat that as just { id }. + uniqFields := make([]*goStructField, 0, len(selectionSet)) + fieldNames := make(map[string]bool, len(selectionSet)) + for _, field := range fields { + // GraphQL (and, effectively, JSON) requires that all fields with the + // same alias (JSON-name) must be the same (i.e. refer to the same + // field), so that's how we deduplicate. + if fieldNames[field.JSONName] { + // GraphQL (and, effectively, JSON) forbids you from having two + // fields with the same alias (JSON-name) that refer to different + // GraphQL fields. But it does allow you to have the same field + // with different selections (subject to some additional rules). + // We say: that's too complicated! and allow duplicate fields + // only if they're "leaf" types (enum or scalar). + switch field.GoType.Unwrap().(type) { + case *goOpaqueType, *goEnumType: + // Leaf field; we can just deduplicate. + // Note GraphQL already guarantees that the conflicting field + // has scalar/enum type iff this field does: + // https://spec.graphql.org/draft/#SameResponseShape() + continue + case *goStructType, *goInterfaceType: + // TODO(benkraft): Keep track of the position of each + // selection, so we can put this error on the right line. + return nil, errorf(nil, + "genqlient doesn't allow duplicate fields with different selections "+ + "(see https://github.com/Khan/genqlient/issues/64); "+ + "duplicate field: %s.%s", containingTypedef.Name, field.JSONName) + default: + return nil, errorf(nil, "unexpected field-type: %T", field.GoType.Unwrap()) + } + } + uniqFields = append(uniqFields, field) + fieldNames[field.JSONName] = true + } + return uniqFields, nil +} + +// fragmentMatches returns true if the given fragment is "active" when applied +// to the given type. +// +// "Active" here means "the fragment's fields will be returned on all objects +// of the given type", which is true when the given type is or implements +// the fragment's type. This is distinct from the rules for when a fragment +// spread is legal, which is true when the fragment would be active for *any* +// of the concrete types the spread-context could have (see +// https://spec.graphql.org/draft/#sec-Fragment-Spreads or DESIGN.md). +// +// containingTypedef is as described in convertInlineFragment, below. +// fragmentTypedef is the definition of the fragment's type-condition, i.e. the +// definition of MyType in a fragment `on MyType`. +func fragmentMatches(containingTypedef, fragmentTypedef *ast.Definition) bool { + if containingTypedef.Name == fragmentTypedef.Name { + return true + } + for _, iface := range containingTypedef.Interfaces { + // Note we don't need to recurse into the interfaces here, because in + // GraphQL types must list all the interfaces they implement, including + // all types those interfaces implement [1]. Actually, at present + // gqlparser doesn't even support interfaces implementing other + // interfaces, but our code would handle that too. + // [1] https://spec.graphql.org/draft/#sec-Interfaces.Interfaces-Implementing-Interfaces + if iface == fragmentTypedef.Name { + return true + } + } + return false +} + +// convertInlineFragment converts a single GraphQL inline fragment +// (`... on MyType { myField }`) into Go struct-fields. +// +// containingTypedef is the type-def corresponding to the type into which we +// are spreading; it may be either an interface type (when spreading into one) +// or an object type (when writing the implementations of such an interface, or +// when using an inline fragment in an object type which is rare). +// +// In general, we treat such fragments' fields as if they were fields of the +// parent selection-set (except of course they are only included in types the +// fragment matches); see DESIGN.md for more. +func (g *generator) convertInlineFragment( + namePrefix string, + fragment *ast.InlineFragment, + containingTypedef *ast.Definition, + queryOptions *GenqlientDirective, +) ([]*goStructField, error) { + // You might think fragmentTypedef would be fragment.ObjectDefinition, but + // actually that's the type into which the fragment is spread. + fragmentTypedef := g.schema.Types[fragment.TypeCondition] + if !fragmentMatches(containingTypedef, fragmentTypedef) { + return nil, nil + } + return g.convertSelectionSet(namePrefix, fragment.SelectionSet, + containingTypedef, queryOptions) +} + +// convertField converts a single GraphQL operation-field into a Go +// struct-field (and its type). // // Note that input-type fields are handled separately (inline in // convertDefinition), because they come from the type-definition, not the diff --git a/generate/testdata/errors/ConflictingSelections.go b/generate/testdata/errors/ConflictingSelections.go new file mode 100644 index 00000000..c8ff6a37 --- /dev/null +++ b/generate/testdata/errors/ConflictingSelections.go @@ -0,0 +1,12 @@ +package errors + +const _ = `# @genqlient +query { + myField { + subField { subSubField1 subSubField2 } + ... on OnePossibleConcreteType { + subField { subSubField3 subSubField4 } + } + } +} +` diff --git a/generate/testdata/errors/ConflictingSelections.graphql b/generate/testdata/errors/ConflictingSelections.graphql new file mode 100644 index 00000000..159624e5 --- /dev/null +++ b/generate/testdata/errors/ConflictingSelections.graphql @@ -0,0 +1,8 @@ +query { + myField { + subField { subSubField1 subSubField2 } + ... on OnePossibleConcreteType { + subField { subSubField3 subSubField4 } + } + } +} diff --git a/generate/testdata/errors/ConflictingSelections.schema.graphql b/generate/testdata/errors/ConflictingSelections.schema.graphql new file mode 100644 index 00000000..72ed5399 --- /dev/null +++ b/generate/testdata/errors/ConflictingSelections.schema.graphql @@ -0,0 +1,18 @@ +type Query { + myField: InterfaceType +} + +interface InterfaceType { + subField: SubFieldType +} + +type OnePossibleConcreteType implements InterfaceType { + subField: SubFieldType +} + +type SubFieldType { + subSubField1: String! + subSubField2: String! + subSubField3: String! + subSubField4: String! +} diff --git a/generate/testdata/queries/ComplexInlineFragments.graphql b/generate/testdata/queries/ComplexInlineFragments.graphql new file mode 100644 index 00000000..99698f35 --- /dev/null +++ b/generate/testdata/queries/ComplexInlineFragments.graphql @@ -0,0 +1,69 @@ +# We test all the spread cases from DESIGN.md, see there for more context on +# each, as well as various other nonsense. But for abstract-in-abstract +# spreads, we can't test cases (4b) and (4c), where I implements J or vice +# versa, because gqlparser doesn't support interfaces that implement other +# interfaces yet. +query ComplexInlineFragments { + root { + id + ... on Topic { schoolGrade } # (1) object spread in object scope + ... on Content { name } # (3) abstract spread in object scope + } + randomItem { + id + ... on Article { text } # (2) object spread in abstract scope + ... on Content { name } # (4a) abstract spread in abstract scope, I == J + ... on HasDuration { duration } # (4d) abstract spread in abstract scope, neither implements the other + } + repeatedStuff: randomItem { + id + id + url + otherId: id + ... on Article { + name + text + otherName: name + } + ... on Content { + id + name + otherName: name + } + ... on HasDuration { duration } + } + conflictingStuff: randomItem { + # These two have different types! Naming gets complicated. Note GraphQL + # says [1] that you can only have such naming conflicts when the fields are + # both on object-typed spreads (reasonable, so they can never collide) and + # they are of "shapes that can be merged", e.g. both nullable objects, + # which seems very strange to me but is the most interesting case for us + # anyway (since where we could have trouble is naming the result types). + # [1] https://spec.graphql.org/draft/#SameResponseShape() + # TODO(benkraft): This actually generates the wrong thing right now (the + # two thumbnail types get the same name, and one clobbers the other). Fix + # in a follow-up commit. + ... on Article { thumbnail { id thumbnailUrl } } + ... on Video { thumbnail { id timestampSec } } + } + nestedStuff: randomItem { + ... on Topic { + children { + id + ... on Article { + text + parent { + ... on Content { + name + parent { + ... on Topic { + children { id name } + } + } + } + } + } + } + } + } +} diff --git a/generate/testdata/queries/SimpleInlineFragment.graphql b/generate/testdata/queries/SimpleInlineFragment.graphql new file mode 100644 index 00000000..f2b75f6c --- /dev/null +++ b/generate/testdata/queries/SimpleInlineFragment.graphql @@ -0,0 +1,8 @@ +query SimpleInlineFragment { + randomItem { + id + name + ... on Article { text } + ... on Video { duration } + } +} diff --git a/generate/testdata/queries/schema.graphql b/generate/testdata/queries/schema.graphql index dc872c8d..a530dc58 100644 --- a/generate/testdata/queries/schema.graphql +++ b/generate/testdata/queries/schema.graphql @@ -53,6 +53,13 @@ type User { emailsWithNulls: [String]! emailsWithNullsOrNull: [String] authMethods: [AuthMethod!]! + greeting: Clip +} + +"""An audio clip, such as of a user saying hello.""" +type Clip implements HasDuration { + id: ID! + duration: Int! } """Content is implemented by various types like Article, Video, and Topic.""" @@ -61,6 +68,12 @@ interface Content { id: ID! name: String! parent: Topic + url: String! +} + +"""An object with a duration, like a video.""" +interface HasDuration { + duration: Int! } """LeafContent represents content items that can't have child-nodes.""" @@ -71,15 +84,29 @@ type Article implements Content { id: ID! name: String! parent: Topic! + url: String! text: String! + thumbnail: StuffThumbnail } -type Video implements Content { +type StuffThumbnail { # for articles, but let's give the name-generator a hard time. + id: ID! + thumbnailUrl: String! +} + +type Video implements Content & HasDuration { """ID is documented in the Content interface.""" id: ID! name: String! parent: Topic! + url: String! duration: Int! + thumbnail: Thumbnail +} + +type Thumbnail { # for videos, but let's give the name-generator a hard time. + id: ID! + timestampSec: Int! } type Topic implements Content { @@ -87,7 +114,9 @@ type Topic implements Content { id: ID! name: String! parent: Topic + url: String! children: [Content!]! + schoolGrade: String } """Query's description is probably ignored by almost all callers.""" diff --git a/generate/testdata/snapshots/TestGenerate-ComplexInlineFragments.graphql-ComplexInlineFragments.graphql.go b/generate/testdata/snapshots/TestGenerate-ComplexInlineFragments.graphql-ComplexInlineFragments.graphql.go new file mode 100644 index 00000000..b223929d --- /dev/null +++ b/generate/testdata/snapshots/TestGenerate-ComplexInlineFragments.graphql-ComplexInlineFragments.graphql.go @@ -0,0 +1,939 @@ +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" +) + +// ComplexInlineFragmentsConflictingStuffArticle includes the requested fields of the GraphQL type Article. +type ComplexInlineFragmentsConflictingStuffArticle struct { + Typename string `json:"__typename"` + Thumbnail ComplexInlineFragmentsConflictingStuffThumbnail `json:"thumbnail"` +} + +// ComplexInlineFragmentsConflictingStuffContent includes the requested fields of the GraphQL interface Content. +// +// ComplexInlineFragmentsConflictingStuffContent is implemented by the following types: +// ComplexInlineFragmentsConflictingStuffArticle +// ComplexInlineFragmentsConflictingStuffVideo +// ComplexInlineFragmentsConflictingStuffTopic +// +// The GraphQL type's documentation follows. +// +// Content is implemented by various types like Article, Video, and Topic. +type ComplexInlineFragmentsConflictingStuffContent interface { + implementsGraphQLInterfaceComplexInlineFragmentsConflictingStuffContent() + // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). + GetTypename() string +} + +func (v *ComplexInlineFragmentsConflictingStuffArticle) implementsGraphQLInterfaceComplexInlineFragmentsConflictingStuffContent() { +} + +// GetTypename is a part of, and documented with, the interface ComplexInlineFragmentsConflictingStuffContent. +func (v *ComplexInlineFragmentsConflictingStuffArticle) GetTypename() string { return v.Typename } + +func (v *ComplexInlineFragmentsConflictingStuffVideo) implementsGraphQLInterfaceComplexInlineFragmentsConflictingStuffContent() { +} + +// GetTypename is a part of, and documented with, the interface ComplexInlineFragmentsConflictingStuffContent. +func (v *ComplexInlineFragmentsConflictingStuffVideo) GetTypename() string { return v.Typename } + +func (v *ComplexInlineFragmentsConflictingStuffTopic) implementsGraphQLInterfaceComplexInlineFragmentsConflictingStuffContent() { +} + +// GetTypename is a part of, and documented with, the interface ComplexInlineFragmentsConflictingStuffContent. +func (v *ComplexInlineFragmentsConflictingStuffTopic) GetTypename() string { return v.Typename } + +func __unmarshalComplexInlineFragmentsConflictingStuffContent(v *ComplexInlineFragmentsConflictingStuffContent, 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(ComplexInlineFragmentsConflictingStuffArticle) + return json.Unmarshal(m, *v) + case "Video": + *v = new(ComplexInlineFragmentsConflictingStuffVideo) + return json.Unmarshal(m, *v) + case "Topic": + *v = new(ComplexInlineFragmentsConflictingStuffTopic) + return json.Unmarshal(m, *v) + default: + return fmt.Errorf( + `Unexpected concrete type for ComplexInlineFragmentsConflictingStuffContent: "%v"`, tn.TypeName) + } +} + +// ComplexInlineFragmentsConflictingStuffThumbnail includes the requested fields of the GraphQL type Thumbnail. +type ComplexInlineFragmentsConflictingStuffThumbnail struct { + Id testutil.ID `json:"id"` + TimestampSec int `json:"timestampSec"` +} + +// ComplexInlineFragmentsConflictingStuffTopic includes the requested fields of the GraphQL type Topic. +type ComplexInlineFragmentsConflictingStuffTopic struct { + Typename string `json:"__typename"` +} + +// ComplexInlineFragmentsConflictingStuffVideo includes the requested fields of the GraphQL type Video. +type ComplexInlineFragmentsConflictingStuffVideo struct { + Typename string `json:"__typename"` + Thumbnail ComplexInlineFragmentsConflictingStuffThumbnail `json:"thumbnail"` +} + +// ComplexInlineFragmentsNestedStuffArticle includes the requested fields of the GraphQL type Article. +type ComplexInlineFragmentsNestedStuffArticle struct { + Typename string `json:"__typename"` +} + +// ComplexInlineFragmentsNestedStuffChildrenArticle includes the requested fields of the GraphQL type Article. +type ComplexInlineFragmentsNestedStuffChildrenArticle struct { + Typename string `json:"__typename"` + // ID is the identifier of the content. + Id testutil.ID `json:"id"` + Text string `json:"text"` + Parent ComplexInlineFragmentsNestedStuffChildrenParentTopic `json:"parent"` +} + +// ComplexInlineFragmentsNestedStuffChildrenContent includes the requested fields of the GraphQL interface Content. +// +// ComplexInlineFragmentsNestedStuffChildrenContent is implemented by the following types: +// ComplexInlineFragmentsNestedStuffChildrenArticle +// ComplexInlineFragmentsNestedStuffChildrenVideo +// ComplexInlineFragmentsNestedStuffChildrenTopic +// +// The GraphQL type's documentation follows. +// +// Content is implemented by various types like Article, Video, and Topic. +type ComplexInlineFragmentsNestedStuffChildrenContent interface { + implementsGraphQLInterfaceComplexInlineFragmentsNestedStuffChildrenContent() + // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). + GetTypename() string + // GetId returns the interface-field "id" from its implementation. + // The GraphQL interface field's documentation follows. + // + // ID is the identifier of the content. + GetId() testutil.ID +} + +func (v *ComplexInlineFragmentsNestedStuffChildrenArticle) implementsGraphQLInterfaceComplexInlineFragmentsNestedStuffChildrenContent() { +} + +// GetTypename is a part of, and documented with, the interface ComplexInlineFragmentsNestedStuffChildrenContent. +func (v *ComplexInlineFragmentsNestedStuffChildrenArticle) GetTypename() string { return v.Typename } + +// GetId is a part of, and documented with, the interface ComplexInlineFragmentsNestedStuffChildrenContent. +func (v *ComplexInlineFragmentsNestedStuffChildrenArticle) GetId() testutil.ID { return v.Id } + +func (v *ComplexInlineFragmentsNestedStuffChildrenVideo) implementsGraphQLInterfaceComplexInlineFragmentsNestedStuffChildrenContent() { +} + +// GetTypename is a part of, and documented with, the interface ComplexInlineFragmentsNestedStuffChildrenContent. +func (v *ComplexInlineFragmentsNestedStuffChildrenVideo) GetTypename() string { return v.Typename } + +// GetId is a part of, and documented with, the interface ComplexInlineFragmentsNestedStuffChildrenContent. +func (v *ComplexInlineFragmentsNestedStuffChildrenVideo) GetId() testutil.ID { return v.Id } + +func (v *ComplexInlineFragmentsNestedStuffChildrenTopic) implementsGraphQLInterfaceComplexInlineFragmentsNestedStuffChildrenContent() { +} + +// GetTypename is a part of, and documented with, the interface ComplexInlineFragmentsNestedStuffChildrenContent. +func (v *ComplexInlineFragmentsNestedStuffChildrenTopic) GetTypename() string { return v.Typename } + +// GetId is a part of, and documented with, the interface ComplexInlineFragmentsNestedStuffChildrenContent. +func (v *ComplexInlineFragmentsNestedStuffChildrenTopic) GetId() testutil.ID { return v.Id } + +func __unmarshalComplexInlineFragmentsNestedStuffChildrenContent(v *ComplexInlineFragmentsNestedStuffChildrenContent, 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(ComplexInlineFragmentsNestedStuffChildrenArticle) + return json.Unmarshal(m, *v) + case "Video": + *v = new(ComplexInlineFragmentsNestedStuffChildrenVideo) + return json.Unmarshal(m, *v) + case "Topic": + *v = new(ComplexInlineFragmentsNestedStuffChildrenTopic) + return json.Unmarshal(m, *v) + default: + return fmt.Errorf( + `Unexpected concrete type for ComplexInlineFragmentsNestedStuffChildrenContent: "%v"`, tn.TypeName) + } +} + +// ComplexInlineFragmentsNestedStuffChildrenParentTopic includes the requested fields of the GraphQL type Topic. +type ComplexInlineFragmentsNestedStuffChildrenParentTopic struct { + Name string `json:"name"` + Parent ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopic `json:"parent"` +} + +// ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopic includes the requested fields of the GraphQL type Topic. +type ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopic struct { + Children []ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent `json:"-"` +} + +func (v *ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopic) UnmarshalJSON(b []byte) error { + + type ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicWrapper ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopic + + var firstPass struct { + *ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicWrapper + Children []json.RawMessage `json:"children"` + } + firstPass.ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicWrapper = (*ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicWrapper)(v) + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + { + target := &v.Children + raw := firstPass.Children + *target = make( + []ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent, + len(raw)) + for i, raw := range raw { + target := &(*target)[i] + err = __unmarshalComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent( + target, raw) + if err != nil { + return err + } + } + } + return nil +} + +// ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenArticle includes the requested fields of the GraphQL type Article. +type ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenArticle struct { + Typename string `json:"__typename"` + // ID is the identifier of the content. + Id testutil.ID `json:"id"` + Name string `json:"name"` +} + +// ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent includes the requested fields of the GraphQL interface Content. +// +// ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent is implemented by the following types: +// ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenArticle +// ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenVideo +// ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenTopic +// +// The GraphQL type's documentation follows. +// +// Content is implemented by various types like Article, Video, and Topic. +type ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent interface { + implementsGraphQLInterfaceComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent() + // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). + GetTypename() string + // GetId returns the interface-field "id" from its implementation. + // The GraphQL interface field's documentation follows. + // + // ID is the identifier of the content. + GetId() testutil.ID + // GetName returns the interface-field "name" from its implementation. + GetName() string +} + +func (v *ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenArticle) implementsGraphQLInterfaceComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent() { +} + +// GetTypename is a part of, and documented with, the interface ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent. +func (v *ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenArticle) GetTypename() string { + return v.Typename +} + +// GetId is a part of, and documented with, the interface ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent. +func (v *ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenArticle) GetId() testutil.ID { + return v.Id +} + +// GetName is a part of, and documented with, the interface ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent. +func (v *ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenArticle) GetName() string { + return v.Name +} + +func (v *ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenVideo) implementsGraphQLInterfaceComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent() { +} + +// GetTypename is a part of, and documented with, the interface ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent. +func (v *ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenVideo) GetTypename() string { + return v.Typename +} + +// GetId is a part of, and documented with, the interface ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent. +func (v *ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenVideo) GetId() testutil.ID { + return v.Id +} + +// GetName is a part of, and documented with, the interface ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent. +func (v *ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenVideo) GetName() string { + return v.Name +} + +func (v *ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenTopic) implementsGraphQLInterfaceComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent() { +} + +// GetTypename is a part of, and documented with, the interface ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent. +func (v *ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenTopic) GetTypename() string { + return v.Typename +} + +// GetId is a part of, and documented with, the interface ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent. +func (v *ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenTopic) GetId() testutil.ID { + return v.Id +} + +// GetName is a part of, and documented with, the interface ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent. +func (v *ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenTopic) GetName() string { + return v.Name +} + +func __unmarshalComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent(v *ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent, 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(ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenArticle) + return json.Unmarshal(m, *v) + case "Video": + *v = new(ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenVideo) + return json.Unmarshal(m, *v) + case "Topic": + *v = new(ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenTopic) + return json.Unmarshal(m, *v) + default: + return fmt.Errorf( + `Unexpected concrete type for ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenContent: "%v"`, tn.TypeName) + } +} + +// ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenTopic includes the requested fields of the GraphQL type Topic. +type ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenTopic struct { + Typename string `json:"__typename"` + // ID is the identifier of the content. + Id testutil.ID `json:"id"` + Name string `json:"name"` +} + +// ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenVideo includes the requested fields of the GraphQL type Video. +type ComplexInlineFragmentsNestedStuffChildrenParentTopicParentTopicChildrenVideo struct { + Typename string `json:"__typename"` + // ID is the identifier of the content. + Id testutil.ID `json:"id"` + Name string `json:"name"` +} + +// ComplexInlineFragmentsNestedStuffChildrenTopic includes the requested fields of the GraphQL type Topic. +type ComplexInlineFragmentsNestedStuffChildrenTopic struct { + Typename string `json:"__typename"` + // ID is the identifier of the content. + Id testutil.ID `json:"id"` +} + +// ComplexInlineFragmentsNestedStuffChildrenVideo includes the requested fields of the GraphQL type Video. +type ComplexInlineFragmentsNestedStuffChildrenVideo struct { + Typename string `json:"__typename"` + // ID is the identifier of the content. + Id testutil.ID `json:"id"` +} + +// ComplexInlineFragmentsNestedStuffContent includes the requested fields of the GraphQL interface Content. +// +// ComplexInlineFragmentsNestedStuffContent is implemented by the following types: +// ComplexInlineFragmentsNestedStuffArticle +// ComplexInlineFragmentsNestedStuffVideo +// ComplexInlineFragmentsNestedStuffTopic +// +// The GraphQL type's documentation follows. +// +// Content is implemented by various types like Article, Video, and Topic. +type ComplexInlineFragmentsNestedStuffContent interface { + implementsGraphQLInterfaceComplexInlineFragmentsNestedStuffContent() + // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). + GetTypename() string +} + +func (v *ComplexInlineFragmentsNestedStuffArticle) implementsGraphQLInterfaceComplexInlineFragmentsNestedStuffContent() { +} + +// GetTypename is a part of, and documented with, the interface ComplexInlineFragmentsNestedStuffContent. +func (v *ComplexInlineFragmentsNestedStuffArticle) GetTypename() string { return v.Typename } + +func (v *ComplexInlineFragmentsNestedStuffVideo) implementsGraphQLInterfaceComplexInlineFragmentsNestedStuffContent() { +} + +// GetTypename is a part of, and documented with, the interface ComplexInlineFragmentsNestedStuffContent. +func (v *ComplexInlineFragmentsNestedStuffVideo) GetTypename() string { return v.Typename } + +func (v *ComplexInlineFragmentsNestedStuffTopic) implementsGraphQLInterfaceComplexInlineFragmentsNestedStuffContent() { +} + +// GetTypename is a part of, and documented with, the interface ComplexInlineFragmentsNestedStuffContent. +func (v *ComplexInlineFragmentsNestedStuffTopic) GetTypename() string { return v.Typename } + +func __unmarshalComplexInlineFragmentsNestedStuffContent(v *ComplexInlineFragmentsNestedStuffContent, 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(ComplexInlineFragmentsNestedStuffArticle) + return json.Unmarshal(m, *v) + case "Video": + *v = new(ComplexInlineFragmentsNestedStuffVideo) + return json.Unmarshal(m, *v) + case "Topic": + *v = new(ComplexInlineFragmentsNestedStuffTopic) + return json.Unmarshal(m, *v) + default: + return fmt.Errorf( + `Unexpected concrete type for ComplexInlineFragmentsNestedStuffContent: "%v"`, tn.TypeName) + } +} + +// ComplexInlineFragmentsNestedStuffTopic includes the requested fields of the GraphQL type Topic. +type ComplexInlineFragmentsNestedStuffTopic struct { + Typename string `json:"__typename"` + Children []ComplexInlineFragmentsNestedStuffChildrenContent `json:"-"` +} + +func (v *ComplexInlineFragmentsNestedStuffTopic) UnmarshalJSON(b []byte) error { + + type ComplexInlineFragmentsNestedStuffTopicWrapper ComplexInlineFragmentsNestedStuffTopic + + var firstPass struct { + *ComplexInlineFragmentsNestedStuffTopicWrapper + Children []json.RawMessage `json:"children"` + } + firstPass.ComplexInlineFragmentsNestedStuffTopicWrapper = (*ComplexInlineFragmentsNestedStuffTopicWrapper)(v) + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + { + target := &v.Children + raw := firstPass.Children + *target = make( + []ComplexInlineFragmentsNestedStuffChildrenContent, + len(raw)) + for i, raw := range raw { + target := &(*target)[i] + err = __unmarshalComplexInlineFragmentsNestedStuffChildrenContent( + target, raw) + if err != nil { + return err + } + } + } + return nil +} + +// ComplexInlineFragmentsNestedStuffVideo includes the requested fields of the GraphQL type Video. +type ComplexInlineFragmentsNestedStuffVideo struct { + Typename string `json:"__typename"` +} + +// ComplexInlineFragmentsRandomItemArticle includes the requested fields of the GraphQL type Article. +type ComplexInlineFragmentsRandomItemArticle struct { + Typename string `json:"__typename"` + // ID is the identifier of the content. + Id testutil.ID `json:"id"` + Text string `json:"text"` + Name string `json:"name"` +} + +// ComplexInlineFragmentsRandomItemContent includes the requested fields of the GraphQL interface Content. +// +// ComplexInlineFragmentsRandomItemContent is implemented by the following types: +// ComplexInlineFragmentsRandomItemArticle +// ComplexInlineFragmentsRandomItemVideo +// ComplexInlineFragmentsRandomItemTopic +// +// The GraphQL type's documentation follows. +// +// Content is implemented by various types like Article, Video, and Topic. +type ComplexInlineFragmentsRandomItemContent interface { + implementsGraphQLInterfaceComplexInlineFragmentsRandomItemContent() + // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). + GetTypename() string + // GetId returns the interface-field "id" from its implementation. + // The GraphQL interface field's documentation follows. + // + // ID is the identifier of the content. + GetId() testutil.ID + // GetName returns the interface-field "name" from its implementation. + GetName() string +} + +func (v *ComplexInlineFragmentsRandomItemArticle) implementsGraphQLInterfaceComplexInlineFragmentsRandomItemContent() { +} + +// GetTypename is a part of, and documented with, the interface ComplexInlineFragmentsRandomItemContent. +func (v *ComplexInlineFragmentsRandomItemArticle) GetTypename() string { return v.Typename } + +// GetId is a part of, and documented with, the interface ComplexInlineFragmentsRandomItemContent. +func (v *ComplexInlineFragmentsRandomItemArticle) GetId() testutil.ID { return v.Id } + +// GetName is a part of, and documented with, the interface ComplexInlineFragmentsRandomItemContent. +func (v *ComplexInlineFragmentsRandomItemArticle) GetName() string { return v.Name } + +func (v *ComplexInlineFragmentsRandomItemVideo) implementsGraphQLInterfaceComplexInlineFragmentsRandomItemContent() { +} + +// GetTypename is a part of, and documented with, the interface ComplexInlineFragmentsRandomItemContent. +func (v *ComplexInlineFragmentsRandomItemVideo) GetTypename() string { return v.Typename } + +// GetId is a part of, and documented with, the interface ComplexInlineFragmentsRandomItemContent. +func (v *ComplexInlineFragmentsRandomItemVideo) GetId() testutil.ID { return v.Id } + +// GetName is a part of, and documented with, the interface ComplexInlineFragmentsRandomItemContent. +func (v *ComplexInlineFragmentsRandomItemVideo) GetName() string { return v.Name } + +func (v *ComplexInlineFragmentsRandomItemTopic) implementsGraphQLInterfaceComplexInlineFragmentsRandomItemContent() { +} + +// GetTypename is a part of, and documented with, the interface ComplexInlineFragmentsRandomItemContent. +func (v *ComplexInlineFragmentsRandomItemTopic) GetTypename() string { return v.Typename } + +// GetId is a part of, and documented with, the interface ComplexInlineFragmentsRandomItemContent. +func (v *ComplexInlineFragmentsRandomItemTopic) GetId() testutil.ID { return v.Id } + +// GetName is a part of, and documented with, the interface ComplexInlineFragmentsRandomItemContent. +func (v *ComplexInlineFragmentsRandomItemTopic) GetName() string { return v.Name } + +func __unmarshalComplexInlineFragmentsRandomItemContent(v *ComplexInlineFragmentsRandomItemContent, 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(ComplexInlineFragmentsRandomItemArticle) + return json.Unmarshal(m, *v) + case "Video": + *v = new(ComplexInlineFragmentsRandomItemVideo) + return json.Unmarshal(m, *v) + case "Topic": + *v = new(ComplexInlineFragmentsRandomItemTopic) + return json.Unmarshal(m, *v) + default: + return fmt.Errorf( + `Unexpected concrete type for ComplexInlineFragmentsRandomItemContent: "%v"`, tn.TypeName) + } +} + +// ComplexInlineFragmentsRandomItemTopic includes the requested fields of the GraphQL type Topic. +type ComplexInlineFragmentsRandomItemTopic struct { + Typename string `json:"__typename"` + // ID is the identifier of the content. + Id testutil.ID `json:"id"` + Name string `json:"name"` +} + +// ComplexInlineFragmentsRandomItemVideo includes the requested fields of the GraphQL type Video. +type ComplexInlineFragmentsRandomItemVideo struct { + Typename string `json:"__typename"` + // ID is the identifier of the content. + Id testutil.ID `json:"id"` + Name string `json:"name"` + Duration int `json:"duration"` +} + +// ComplexInlineFragmentsRepeatedStuffArticle includes the requested fields of the GraphQL type Article. +type ComplexInlineFragmentsRepeatedStuffArticle struct { + Typename string `json:"__typename"` + // ID is the identifier of the content. + Id testutil.ID `json:"id"` + Url string `json:"url"` + // ID is the identifier of the content. + OtherId testutil.ID `json:"otherId"` + Name string `json:"name"` + Text string `json:"text"` + OtherName string `json:"otherName"` +} + +// ComplexInlineFragmentsRepeatedStuffContent includes the requested fields of the GraphQL interface Content. +// +// ComplexInlineFragmentsRepeatedStuffContent is implemented by the following types: +// ComplexInlineFragmentsRepeatedStuffArticle +// ComplexInlineFragmentsRepeatedStuffVideo +// ComplexInlineFragmentsRepeatedStuffTopic +// +// The GraphQL type's documentation follows. +// +// Content is implemented by various types like Article, Video, and Topic. +type ComplexInlineFragmentsRepeatedStuffContent interface { + implementsGraphQLInterfaceComplexInlineFragmentsRepeatedStuffContent() + // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). + GetTypename() string + // GetId returns the interface-field "id" from its implementation. + // The GraphQL interface field's documentation follows. + // + // ID is the identifier of the content. + GetId() testutil.ID + // GetUrl returns the interface-field "url" from its implementation. + GetUrl() string + // GetOtherId returns the interface-field "id" from its implementation. + // The GraphQL interface field's documentation follows. + // + // ID is the identifier of the content. + GetOtherId() testutil.ID + // GetName returns the interface-field "name" from its implementation. + GetName() string + // GetOtherName returns the interface-field "name" from its implementation. + GetOtherName() string +} + +func (v *ComplexInlineFragmentsRepeatedStuffArticle) implementsGraphQLInterfaceComplexInlineFragmentsRepeatedStuffContent() { +} + +// GetTypename is a part of, and documented with, the interface ComplexInlineFragmentsRepeatedStuffContent. +func (v *ComplexInlineFragmentsRepeatedStuffArticle) GetTypename() string { return v.Typename } + +// GetId is a part of, and documented with, the interface ComplexInlineFragmentsRepeatedStuffContent. +func (v *ComplexInlineFragmentsRepeatedStuffArticle) GetId() testutil.ID { return v.Id } + +// GetUrl is a part of, and documented with, the interface ComplexInlineFragmentsRepeatedStuffContent. +func (v *ComplexInlineFragmentsRepeatedStuffArticle) GetUrl() string { return v.Url } + +// GetOtherId is a part of, and documented with, the interface ComplexInlineFragmentsRepeatedStuffContent. +func (v *ComplexInlineFragmentsRepeatedStuffArticle) GetOtherId() testutil.ID { return v.OtherId } + +// GetName is a part of, and documented with, the interface ComplexInlineFragmentsRepeatedStuffContent. +func (v *ComplexInlineFragmentsRepeatedStuffArticle) GetName() string { return v.Name } + +// GetOtherName is a part of, and documented with, the interface ComplexInlineFragmentsRepeatedStuffContent. +func (v *ComplexInlineFragmentsRepeatedStuffArticle) GetOtherName() string { return v.OtherName } + +func (v *ComplexInlineFragmentsRepeatedStuffVideo) implementsGraphQLInterfaceComplexInlineFragmentsRepeatedStuffContent() { +} + +// GetTypename is a part of, and documented with, the interface ComplexInlineFragmentsRepeatedStuffContent. +func (v *ComplexInlineFragmentsRepeatedStuffVideo) GetTypename() string { return v.Typename } + +// GetId is a part of, and documented with, the interface ComplexInlineFragmentsRepeatedStuffContent. +func (v *ComplexInlineFragmentsRepeatedStuffVideo) GetId() testutil.ID { return v.Id } + +// GetUrl is a part of, and documented with, the interface ComplexInlineFragmentsRepeatedStuffContent. +func (v *ComplexInlineFragmentsRepeatedStuffVideo) GetUrl() string { return v.Url } + +// GetOtherId is a part of, and documented with, the interface ComplexInlineFragmentsRepeatedStuffContent. +func (v *ComplexInlineFragmentsRepeatedStuffVideo) GetOtherId() testutil.ID { return v.OtherId } + +// GetName is a part of, and documented with, the interface ComplexInlineFragmentsRepeatedStuffContent. +func (v *ComplexInlineFragmentsRepeatedStuffVideo) GetName() string { return v.Name } + +// GetOtherName is a part of, and documented with, the interface ComplexInlineFragmentsRepeatedStuffContent. +func (v *ComplexInlineFragmentsRepeatedStuffVideo) GetOtherName() string { return v.OtherName } + +func (v *ComplexInlineFragmentsRepeatedStuffTopic) implementsGraphQLInterfaceComplexInlineFragmentsRepeatedStuffContent() { +} + +// GetTypename is a part of, and documented with, the interface ComplexInlineFragmentsRepeatedStuffContent. +func (v *ComplexInlineFragmentsRepeatedStuffTopic) GetTypename() string { return v.Typename } + +// GetId is a part of, and documented with, the interface ComplexInlineFragmentsRepeatedStuffContent. +func (v *ComplexInlineFragmentsRepeatedStuffTopic) GetId() testutil.ID { return v.Id } + +// GetUrl is a part of, and documented with, the interface ComplexInlineFragmentsRepeatedStuffContent. +func (v *ComplexInlineFragmentsRepeatedStuffTopic) GetUrl() string { return v.Url } + +// GetOtherId is a part of, and documented with, the interface ComplexInlineFragmentsRepeatedStuffContent. +func (v *ComplexInlineFragmentsRepeatedStuffTopic) GetOtherId() testutil.ID { return v.OtherId } + +// GetName is a part of, and documented with, the interface ComplexInlineFragmentsRepeatedStuffContent. +func (v *ComplexInlineFragmentsRepeatedStuffTopic) GetName() string { return v.Name } + +// GetOtherName is a part of, and documented with, the interface ComplexInlineFragmentsRepeatedStuffContent. +func (v *ComplexInlineFragmentsRepeatedStuffTopic) GetOtherName() string { return v.OtherName } + +func __unmarshalComplexInlineFragmentsRepeatedStuffContent(v *ComplexInlineFragmentsRepeatedStuffContent, 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(ComplexInlineFragmentsRepeatedStuffArticle) + return json.Unmarshal(m, *v) + case "Video": + *v = new(ComplexInlineFragmentsRepeatedStuffVideo) + return json.Unmarshal(m, *v) + case "Topic": + *v = new(ComplexInlineFragmentsRepeatedStuffTopic) + return json.Unmarshal(m, *v) + default: + return fmt.Errorf( + `Unexpected concrete type for ComplexInlineFragmentsRepeatedStuffContent: "%v"`, tn.TypeName) + } +} + +// ComplexInlineFragmentsRepeatedStuffTopic includes the requested fields of the GraphQL type Topic. +type ComplexInlineFragmentsRepeatedStuffTopic struct { + Typename string `json:"__typename"` + // ID is the identifier of the content. + Id testutil.ID `json:"id"` + Url string `json:"url"` + // ID is the identifier of the content. + OtherId testutil.ID `json:"otherId"` + Name string `json:"name"` + OtherName string `json:"otherName"` +} + +// ComplexInlineFragmentsRepeatedStuffVideo includes the requested fields of the GraphQL type Video. +type ComplexInlineFragmentsRepeatedStuffVideo struct { + Typename string `json:"__typename"` + // ID is the identifier of the content. + Id testutil.ID `json:"id"` + Url string `json:"url"` + // ID is the identifier of the content. + OtherId testutil.ID `json:"otherId"` + Name string `json:"name"` + OtherName string `json:"otherName"` + Duration int `json:"duration"` +} + +// ComplexInlineFragmentsResponse is returned by ComplexInlineFragments on success. +type ComplexInlineFragmentsResponse struct { + Root ComplexInlineFragmentsRootTopic `json:"root"` + RandomItem ComplexInlineFragmentsRandomItemContent `json:"-"` + RepeatedStuff ComplexInlineFragmentsRepeatedStuffContent `json:"-"` + ConflictingStuff ComplexInlineFragmentsConflictingStuffContent `json:"-"` + NestedStuff ComplexInlineFragmentsNestedStuffContent `json:"-"` +} + +func (v *ComplexInlineFragmentsResponse) UnmarshalJSON(b []byte) error { + + type ComplexInlineFragmentsResponseWrapper ComplexInlineFragmentsResponse + + var firstPass struct { + *ComplexInlineFragmentsResponseWrapper + RandomItem json.RawMessage `json:"randomItem"` + RepeatedStuff json.RawMessage `json:"repeatedStuff"` + ConflictingStuff json.RawMessage `json:"conflictingStuff"` + NestedStuff json.RawMessage `json:"nestedStuff"` + } + firstPass.ComplexInlineFragmentsResponseWrapper = (*ComplexInlineFragmentsResponseWrapper)(v) + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + { + target := &v.RandomItem + raw := firstPass.RandomItem + err = __unmarshalComplexInlineFragmentsRandomItemContent( + target, raw) + if err != nil { + return err + } + } + { + target := &v.RepeatedStuff + raw := firstPass.RepeatedStuff + err = __unmarshalComplexInlineFragmentsRepeatedStuffContent( + target, raw) + if err != nil { + return err + } + } + { + target := &v.ConflictingStuff + raw := firstPass.ConflictingStuff + err = __unmarshalComplexInlineFragmentsConflictingStuffContent( + target, raw) + if err != nil { + return err + } + } + { + target := &v.NestedStuff + raw := firstPass.NestedStuff + err = __unmarshalComplexInlineFragmentsNestedStuffContent( + target, raw) + if err != nil { + return err + } + } + return nil +} + +// ComplexInlineFragmentsRootTopic includes the requested fields of the GraphQL type Topic. +type ComplexInlineFragmentsRootTopic struct { + // ID is documented in the Content interface. + Id testutil.ID `json:"id"` + SchoolGrade string `json:"schoolGrade"` + Name string `json:"name"` +} + +// We test all the spread cases from DESIGN.md, see there for more context on +// each, as well as various other nonsense. But for abstract-in-abstract +// spreads, we can't test cases (4b) and (4c), where I implements J or vice +// versa, because gqlparser doesn't support interfaces that implement other +// interfaces yet. +func ComplexInlineFragments( + client graphql.Client, +) (*ComplexInlineFragmentsResponse, error) { + var retval ComplexInlineFragmentsResponse + err := client.MakeRequest( + nil, + "ComplexInlineFragments", + ` +query ComplexInlineFragments { + root { + id + ... on Topic { + schoolGrade + } + ... on Content { + name + } + } + randomItem { + __typename + id + ... on Article { + text + } + ... on Content { + name + } + ... on HasDuration { + duration + } + } + repeatedStuff: randomItem { + __typename + id + id + url + otherId: id + ... on Article { + name + text + otherName: name + } + ... on Content { + id + name + otherName: name + } + ... on HasDuration { + duration + } + } + conflictingStuff: randomItem { + __typename + ... on Article { + thumbnail { + id + thumbnailUrl + } + } + ... on Video { + thumbnail { + id + timestampSec + } + } + } + nestedStuff: randomItem { + __typename + ... on Topic { + children { + __typename + id + ... on Article { + text + parent { + ... on Content { + name + parent { + ... on Topic { + children { + __typename + id + name + } + } + } + } + } + } + } + } + } +} +`, + &retval, + nil, + ) + return &retval, err +} + diff --git a/generate/testdata/snapshots/TestGenerate-ComplexInlineFragments.graphql-ComplexInlineFragments.graphql.json b/generate/testdata/snapshots/TestGenerate-ComplexInlineFragments.graphql-ComplexInlineFragments.graphql.json new file mode 100644 index 00000000..357c8890 --- /dev/null +++ b/generate/testdata/snapshots/TestGenerate-ComplexInlineFragments.graphql-ComplexInlineFragments.graphql.json @@ -0,0 +1,9 @@ +{ + "operations": [ + { + "operationName": "ComplexInlineFragments", + "query": "\nquery ComplexInlineFragments {\n\troot {\n\t\tid\n\t\t... on Topic {\n\t\t\tschoolGrade\n\t\t}\n\t\t... on Content {\n\t\t\tname\n\t\t}\n\t}\n\trandomItem {\n\t\t__typename\n\t\tid\n\t\t... on Article {\n\t\t\ttext\n\t\t}\n\t\t... on Content {\n\t\t\tname\n\t\t}\n\t\t... on HasDuration {\n\t\t\tduration\n\t\t}\n\t}\n\trepeatedStuff: randomItem {\n\t\t__typename\n\t\tid\n\t\tid\n\t\turl\n\t\totherId: id\n\t\t... on Article {\n\t\t\tname\n\t\t\ttext\n\t\t\totherName: name\n\t\t}\n\t\t... on Content {\n\t\t\tid\n\t\t\tname\n\t\t\totherName: name\n\t\t}\n\t\t... on HasDuration {\n\t\t\tduration\n\t\t}\n\t}\n\tconflictingStuff: randomItem {\n\t\t__typename\n\t\t... on Article {\n\t\t\tthumbnail {\n\t\t\t\tid\n\t\t\t\tthumbnailUrl\n\t\t\t}\n\t\t}\n\t\t... on Video {\n\t\t\tthumbnail {\n\t\t\t\tid\n\t\t\t\ttimestampSec\n\t\t\t}\n\t\t}\n\t}\n\tnestedStuff: randomItem {\n\t\t__typename\n\t\t... on Topic {\n\t\t\tchildren {\n\t\t\t\t__typename\n\t\t\t\tid\n\t\t\t\t... on Article {\n\t\t\t\t\ttext\n\t\t\t\t\tparent {\n\t\t\t\t\t\t... on Content {\n\t\t\t\t\t\t\tname\n\t\t\t\t\t\t\tparent {\n\t\t\t\t\t\t\t\t... on Topic {\n\t\t\t\t\t\t\t\t\tchildren {\n\t\t\t\t\t\t\t\t\t\t__typename\n\t\t\t\t\t\t\t\t\t\tid\n\t\t\t\t\t\t\t\t\t\tname\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n", + "sourceLocation": "testdata/queries/ComplexInlineFragments.graphql" + } + ] +} diff --git a/generate/testdata/snapshots/TestGenerate-SimpleInlineFragment.graphql-SimpleInlineFragment.graphql.go b/generate/testdata/snapshots/TestGenerate-SimpleInlineFragment.graphql-SimpleInlineFragment.graphql.go new file mode 100644 index 00000000..f3e27020 --- /dev/null +++ b/generate/testdata/snapshots/TestGenerate-SimpleInlineFragment.graphql-SimpleInlineFragment.graphql.go @@ -0,0 +1,186 @@ +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" +) + +// SimpleInlineFragmentRandomItemArticle includes the requested fields of the GraphQL type Article. +type SimpleInlineFragmentRandomItemArticle struct { + Typename string `json:"__typename"` + // ID is the identifier of the content. + Id testutil.ID `json:"id"` + Name string `json:"name"` + Text string `json:"text"` +} + +// SimpleInlineFragmentRandomItemContent includes the requested fields of the GraphQL interface Content. +// +// SimpleInlineFragmentRandomItemContent is implemented by the following types: +// SimpleInlineFragmentRandomItemArticle +// SimpleInlineFragmentRandomItemVideo +// SimpleInlineFragmentRandomItemTopic +// +// The GraphQL type's documentation follows. +// +// Content is implemented by various types like Article, Video, and Topic. +type SimpleInlineFragmentRandomItemContent interface { + implementsGraphQLInterfaceSimpleInlineFragmentRandomItemContent() + // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). + GetTypename() string + // GetId returns the interface-field "id" from its implementation. + // The GraphQL interface field's documentation follows. + // + // ID is the identifier of the content. + GetId() testutil.ID + // GetName returns the interface-field "name" from its implementation. + GetName() string +} + +func (v *SimpleInlineFragmentRandomItemArticle) implementsGraphQLInterfaceSimpleInlineFragmentRandomItemContent() { +} + +// GetTypename is a part of, and documented with, the interface SimpleInlineFragmentRandomItemContent. +func (v *SimpleInlineFragmentRandomItemArticle) GetTypename() string { return v.Typename } + +// GetId is a part of, and documented with, the interface SimpleInlineFragmentRandomItemContent. +func (v *SimpleInlineFragmentRandomItemArticle) GetId() testutil.ID { return v.Id } + +// GetName is a part of, and documented with, the interface SimpleInlineFragmentRandomItemContent. +func (v *SimpleInlineFragmentRandomItemArticle) GetName() string { return v.Name } + +func (v *SimpleInlineFragmentRandomItemVideo) implementsGraphQLInterfaceSimpleInlineFragmentRandomItemContent() { +} + +// GetTypename is a part of, and documented with, the interface SimpleInlineFragmentRandomItemContent. +func (v *SimpleInlineFragmentRandomItemVideo) GetTypename() string { return v.Typename } + +// GetId is a part of, and documented with, the interface SimpleInlineFragmentRandomItemContent. +func (v *SimpleInlineFragmentRandomItemVideo) GetId() testutil.ID { return v.Id } + +// GetName is a part of, and documented with, the interface SimpleInlineFragmentRandomItemContent. +func (v *SimpleInlineFragmentRandomItemVideo) GetName() string { return v.Name } + +func (v *SimpleInlineFragmentRandomItemTopic) implementsGraphQLInterfaceSimpleInlineFragmentRandomItemContent() { +} + +// GetTypename is a part of, and documented with, the interface SimpleInlineFragmentRandomItemContent. +func (v *SimpleInlineFragmentRandomItemTopic) GetTypename() string { return v.Typename } + +// GetId is a part of, and documented with, the interface SimpleInlineFragmentRandomItemContent. +func (v *SimpleInlineFragmentRandomItemTopic) GetId() testutil.ID { return v.Id } + +// GetName is a part of, and documented with, the interface SimpleInlineFragmentRandomItemContent. +func (v *SimpleInlineFragmentRandomItemTopic) GetName() string { return v.Name } + +func __unmarshalSimpleInlineFragmentRandomItemContent(v *SimpleInlineFragmentRandomItemContent, 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(SimpleInlineFragmentRandomItemArticle) + return json.Unmarshal(m, *v) + case "Video": + *v = new(SimpleInlineFragmentRandomItemVideo) + return json.Unmarshal(m, *v) + case "Topic": + *v = new(SimpleInlineFragmentRandomItemTopic) + return json.Unmarshal(m, *v) + default: + return fmt.Errorf( + `Unexpected concrete type for SimpleInlineFragmentRandomItemContent: "%v"`, tn.TypeName) + } +} + +// SimpleInlineFragmentRandomItemTopic includes the requested fields of the GraphQL type Topic. +type SimpleInlineFragmentRandomItemTopic struct { + Typename string `json:"__typename"` + // ID is the identifier of the content. + Id testutil.ID `json:"id"` + Name string `json:"name"` +} + +// SimpleInlineFragmentRandomItemVideo includes the requested fields of the GraphQL type Video. +type SimpleInlineFragmentRandomItemVideo struct { + Typename string `json:"__typename"` + // ID is the identifier of the content. + Id testutil.ID `json:"id"` + Name string `json:"name"` + Duration int `json:"duration"` +} + +// SimpleInlineFragmentResponse is returned by SimpleInlineFragment on success. +type SimpleInlineFragmentResponse struct { + RandomItem SimpleInlineFragmentRandomItemContent `json:"-"` +} + +func (v *SimpleInlineFragmentResponse) UnmarshalJSON(b []byte) error { + + type SimpleInlineFragmentResponseWrapper SimpleInlineFragmentResponse + + var firstPass struct { + *SimpleInlineFragmentResponseWrapper + RandomItem json.RawMessage `json:"randomItem"` + } + firstPass.SimpleInlineFragmentResponseWrapper = (*SimpleInlineFragmentResponseWrapper)(v) + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + { + target := &v.RandomItem + raw := firstPass.RandomItem + err = __unmarshalSimpleInlineFragmentRandomItemContent( + target, raw) + if err != nil { + return err + } + } + return nil +} + +func SimpleInlineFragment( + client graphql.Client, +) (*SimpleInlineFragmentResponse, error) { + var retval SimpleInlineFragmentResponse + err := client.MakeRequest( + nil, + "SimpleInlineFragment", + ` +query SimpleInlineFragment { + randomItem { + __typename + id + name + ... on Article { + text + } + ... on Video { + duration + } + } +} +`, + &retval, + nil, + ) + return &retval, err +} + diff --git a/generate/testdata/snapshots/TestGenerate-SimpleInlineFragment.graphql-SimpleInlineFragment.graphql.json b/generate/testdata/snapshots/TestGenerate-SimpleInlineFragment.graphql-SimpleInlineFragment.graphql.json new file mode 100644 index 00000000..150cfa95 --- /dev/null +++ b/generate/testdata/snapshots/TestGenerate-SimpleInlineFragment.graphql-SimpleInlineFragment.graphql.json @@ -0,0 +1,9 @@ +{ + "operations": [ + { + "operationName": "SimpleInlineFragment", + "query": "\nquery SimpleInlineFragment {\n\trandomItem {\n\t\t__typename\n\t\tid\n\t\tname\n\t\t... on Article {\n\t\t\ttext\n\t\t}\n\t\t... on Video {\n\t\t\tduration\n\t\t}\n\t}\n}\n", + "sourceLocation": "testdata/queries/SimpleInlineFragment.graphql" + } + ] +} diff --git a/generate/testdata/snapshots/TestGenerateErrors-ConflictingSelections.go b/generate/testdata/snapshots/TestGenerateErrors-ConflictingSelections.go new file mode 100644 index 00000000..4b3033f4 --- /dev/null +++ b/generate/testdata/snapshots/TestGenerateErrors-ConflictingSelections.go @@ -0,0 +1 @@ +testdata/errors/ConflictingSelections.go:4: operations must have operation-names diff --git a/generate/testdata/snapshots/TestGenerateErrors-ConflictingSelections.graphql b/generate/testdata/snapshots/TestGenerateErrors-ConflictingSelections.graphql new file mode 100644 index 00000000..b3bf9893 --- /dev/null +++ b/generate/testdata/snapshots/TestGenerateErrors-ConflictingSelections.graphql @@ -0,0 +1 @@ +testdata/errors/ConflictingSelections.graphql:1: operations must have operation-names diff --git a/internal/integration/generated.go b/internal/integration/generated.go index 69e7250f..8cf83333 100644 --- a/internal/integration/generated.go +++ b/internal/integration/generated.go @@ -10,6 +10,253 @@ import ( "github.com/Khan/genqlient/graphql" ) +type Species string + +const ( + SpeciesDog Species = "DOG" + SpeciesCoelacanth Species = "COELACANTH" +) + +// queryWithFragmentsBeingsAnimal includes the requested fields of the GraphQL type Animal. +type queryWithFragmentsBeingsAnimal struct { + Typename string `json:"__typename"` + Id string `json:"id"` + Name string `json:"name"` + Hair queryWithFragmentsBeingsHair `json:"hair"` + Species Species `json:"species"` + Owner queryWithFragmentsBeingsOwnerBeing `json:"-"` +} + +func (v *queryWithFragmentsBeingsAnimal) UnmarshalJSON(b []byte) error { + + type queryWithFragmentsBeingsAnimalWrapper queryWithFragmentsBeingsAnimal + + var firstPass struct { + *queryWithFragmentsBeingsAnimalWrapper + Owner json.RawMessage `json:"owner"` + } + firstPass.queryWithFragmentsBeingsAnimalWrapper = (*queryWithFragmentsBeingsAnimalWrapper)(v) + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + { + target := &v.Owner + raw := firstPass.Owner + err = __unmarshalqueryWithFragmentsBeingsOwnerBeing( + target, raw) + if err != nil { + return err + } + } + return nil +} + +// queryWithFragmentsBeingsBeing includes the requested fields of the GraphQL interface Being. +// +// queryWithFragmentsBeingsBeing is implemented by the following types: +// queryWithFragmentsBeingsUser +// queryWithFragmentsBeingsAnimal +// +// The GraphQL type's documentation follows. +// +// +type queryWithFragmentsBeingsBeing interface { + implementsGraphQLInterfacequeryWithFragmentsBeingsBeing() + // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). + GetTypename() string + // GetId returns the interface-field "id" from its implementation. + GetId() string + // GetName returns the interface-field "name" from its implementation. + GetName() string +} + +func (v *queryWithFragmentsBeingsUser) implementsGraphQLInterfacequeryWithFragmentsBeingsBeing() {} + +// GetTypename is a part of, and documented with, the interface queryWithFragmentsBeingsBeing. +func (v *queryWithFragmentsBeingsUser) GetTypename() string { return v.Typename } + +// GetId is a part of, and documented with, the interface queryWithFragmentsBeingsBeing. +func (v *queryWithFragmentsBeingsUser) GetId() string { return v.Id } + +// GetName is a part of, and documented with, the interface queryWithFragmentsBeingsBeing. +func (v *queryWithFragmentsBeingsUser) GetName() string { return v.Name } + +func (v *queryWithFragmentsBeingsAnimal) implementsGraphQLInterfacequeryWithFragmentsBeingsBeing() {} + +// GetTypename is a part of, and documented with, the interface queryWithFragmentsBeingsBeing. +func (v *queryWithFragmentsBeingsAnimal) GetTypename() string { return v.Typename } + +// GetId is a part of, and documented with, the interface queryWithFragmentsBeingsBeing. +func (v *queryWithFragmentsBeingsAnimal) GetId() string { return v.Id } + +// GetName is a part of, and documented with, the interface queryWithFragmentsBeingsBeing. +func (v *queryWithFragmentsBeingsAnimal) GetName() string { return v.Name } + +func __unmarshalqueryWithFragmentsBeingsBeing(v *queryWithFragmentsBeingsBeing, 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(queryWithFragmentsBeingsUser) + return json.Unmarshal(m, *v) + case "Animal": + *v = new(queryWithFragmentsBeingsAnimal) + return json.Unmarshal(m, *v) + default: + return fmt.Errorf( + `Unexpected concrete type for queryWithFragmentsBeingsBeing: "%v"`, tn.TypeName) + } +} + +// queryWithFragmentsBeingsHair includes the requested fields of the GraphQL type BeingsHair. +type queryWithFragmentsBeingsHair struct { + HasHair bool `json:"hasHair"` +} + +// queryWithFragmentsBeingsOwnerAnimal includes the requested fields of the GraphQL type Animal. +type queryWithFragmentsBeingsOwnerAnimal struct { + Typename string `json:"__typename"` + Id string `json:"id"` + Name string `json:"name"` +} + +// queryWithFragmentsBeingsOwnerBeing includes the requested fields of the GraphQL interface Being. +// +// queryWithFragmentsBeingsOwnerBeing is implemented by the following types: +// queryWithFragmentsBeingsOwnerUser +// queryWithFragmentsBeingsOwnerAnimal +// +// The GraphQL type's documentation follows. +// +// +type queryWithFragmentsBeingsOwnerBeing interface { + implementsGraphQLInterfacequeryWithFragmentsBeingsOwnerBeing() + // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). + GetTypename() string + // GetId returns the interface-field "id" from its implementation. + GetId() string + // GetName returns the interface-field "name" from its implementation. + GetName() string +} + +func (v *queryWithFragmentsBeingsOwnerUser) implementsGraphQLInterfacequeryWithFragmentsBeingsOwnerBeing() { +} + +// GetTypename is a part of, and documented with, the interface queryWithFragmentsBeingsOwnerBeing. +func (v *queryWithFragmentsBeingsOwnerUser) GetTypename() string { return v.Typename } + +// GetId is a part of, and documented with, the interface queryWithFragmentsBeingsOwnerBeing. +func (v *queryWithFragmentsBeingsOwnerUser) GetId() string { return v.Id } + +// GetName is a part of, and documented with, the interface queryWithFragmentsBeingsOwnerBeing. +func (v *queryWithFragmentsBeingsOwnerUser) GetName() string { return v.Name } + +func (v *queryWithFragmentsBeingsOwnerAnimal) implementsGraphQLInterfacequeryWithFragmentsBeingsOwnerBeing() { +} + +// GetTypename is a part of, and documented with, the interface queryWithFragmentsBeingsOwnerBeing. +func (v *queryWithFragmentsBeingsOwnerAnimal) GetTypename() string { return v.Typename } + +// GetId is a part of, and documented with, the interface queryWithFragmentsBeingsOwnerBeing. +func (v *queryWithFragmentsBeingsOwnerAnimal) GetId() string { return v.Id } + +// GetName is a part of, and documented with, the interface queryWithFragmentsBeingsOwnerBeing. +func (v *queryWithFragmentsBeingsOwnerAnimal) GetName() string { return v.Name } + +func __unmarshalqueryWithFragmentsBeingsOwnerBeing(v *queryWithFragmentsBeingsOwnerBeing, 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(queryWithFragmentsBeingsOwnerUser) + return json.Unmarshal(m, *v) + case "Animal": + *v = new(queryWithFragmentsBeingsOwnerAnimal) + return json.Unmarshal(m, *v) + default: + return fmt.Errorf( + `Unexpected concrete type for queryWithFragmentsBeingsOwnerBeing: "%v"`, tn.TypeName) + } +} + +// queryWithFragmentsBeingsOwnerUser includes the requested fields of the GraphQL type User. +type queryWithFragmentsBeingsOwnerUser struct { + Typename string `json:"__typename"` + Id string `json:"id"` + Name string `json:"name"` + LuckyNumber int `json:"luckyNumber"` +} + +// queryWithFragmentsBeingsUser includes the requested fields of the GraphQL type User. +type queryWithFragmentsBeingsUser struct { + Typename string `json:"__typename"` + Id string `json:"id"` + Name string `json:"name"` + LuckyNumber int `json:"luckyNumber"` + Hair queryWithFragmentsBeingsHair `json:"hair"` +} + +// queryWithFragmentsResponse is returned by queryWithFragments on success. +type queryWithFragmentsResponse struct { + Beings []queryWithFragmentsBeingsBeing `json:"-"` +} + +func (v *queryWithFragmentsResponse) UnmarshalJSON(b []byte) error { + + type queryWithFragmentsResponseWrapper queryWithFragmentsResponse + + var firstPass struct { + *queryWithFragmentsResponseWrapper + Beings []json.RawMessage `json:"beings"` + } + firstPass.queryWithFragmentsResponseWrapper = (*queryWithFragmentsResponseWrapper)(v) + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + { + target := &v.Beings + raw := firstPass.Beings + *target = make( + []queryWithFragmentsBeingsBeing, + len(raw)) + for i, raw := range raw { + target := &(*target)[i] + err = __unmarshalqueryWithFragmentsBeingsBeing( + target, raw) + if err != nil { + return err + } + } + } + return nil +} + // queryWithInterfaceListFieldBeingsAnimal includes the requested fields of the GraphQL type Animal. type queryWithInterfaceListFieldBeingsAnimal struct { Typename string `json:"__typename"` @@ -537,3 +784,59 @@ query queryWithInterfaceListPointerField ($ids: [ID!]!) { ) return &retval, err } + +func queryWithFragments( + ctx context.Context, + client graphql.Client, + ids []string, +) (*queryWithFragmentsResponse, error) { + variables := map[string]interface{}{ + "ids": ids, + } + + var retval queryWithFragmentsResponse + err := client.MakeRequest( + ctx, + "queryWithFragments", + ` +query queryWithFragments ($ids: [ID!]!) { + beings(ids: $ids) { + __typename + id + ... on Being { + id + name + } + ... on Animal { + id + hair { + hasHair + } + species + owner { + __typename + id + ... on Being { + name + } + ... on User { + luckyNumber + } + } + } + ... on Lucky { + luckyNumber + } + ... on User { + hair { + color + } + } + } +} +`, + &retval, + variables, + ) + return &retval, err +} diff --git a/internal/integration/integration_test.go b/internal/integration/integration_test.go index b759ef32..de3578b5 100644 --- a/internal/integration/integration_test.go +++ b/internal/integration/integration_test.go @@ -206,6 +206,91 @@ func TestInterfaceListPointerField(t *testing.T) { assert.Nil(t, *resp.Beings[2]) } +func TestFragments(t *testing.T) { + _ = `# @genqlient + query queryWithFragments($ids: [ID!]!) { + beings(ids: $ids) { + __typename id + ... on Being { id name } + ... on Animal { + id + hair { hasHair } + species + owner { + id + ... on Being { name } + ... on User { luckyNumber } + } + } + ... on Lucky { luckyNumber } + ... on User { hair { color } } + } + }` + + ctx := context.Background() + server := server.RunServer() + defer server.Close() + client := graphql.NewClient(server.URL, http.DefaultClient) + + resp, err := queryWithFragments(ctx, client, []string{"1", "3", "12847394823"}) + require.NoError(t, err) + + require.Len(t, resp.Beings, 3) + + // We should get the following three beings: + // User{Id: 1, Name: "Yours Truly"}, + // Animal{Id: 3, Name: "Fido"}, + // null + + // Check fields both via interface and via type-assertion when possible + // User has, in total, the fields: __typename id name luckyNumber. + assert.Equal(t, "User", resp.Beings[0].GetTypename()) + assert.Equal(t, "1", resp.Beings[0].GetId()) + assert.Equal(t, "Yours Truly", resp.Beings[0].GetName()) + // (hair and luckyNumber we need to cast for) + + user, ok := resp.Beings[0].(*queryWithFragmentsBeingsUser) + require.Truef(t, ok, "got %T, not User", resp.Beings[0]) + assert.Equal(t, "1", user.Id) + assert.Equal(t, "Yours Truly", user.Name) + // TODO(benkraft): Uncomment once we fix the interface-field type-naming + // bug that's causing this to get the wrong type (because we end up + // generating two conflicting types). + // assert.Equal(t, "Black", user.Hair.Color) + assert.Equal(t, 17, user.LuckyNumber) + + // Animal has, in total, the fields: + // __typename + // id + // species + // owner { + // id + // name + // ... on User { luckyNumber } + // } + assert.Equal(t, "Animal", resp.Beings[1].GetTypename()) + assert.Equal(t, "3", resp.Beings[1].GetId()) + // (hair, species, and owner.* we have to cast for) + + animal, ok := resp.Beings[1].(*queryWithFragmentsBeingsAnimal) + require.Truef(t, ok, "got %T, not Animal", resp.Beings[1]) + assert.Equal(t, "3", animal.Id) + assert.Equal(t, SpeciesDog, animal.Species) + assert.True(t, animal.Hair.HasHair) + + assert.Equal(t, "1", animal.Owner.GetId()) + assert.Equal(t, "Yours Truly", animal.Owner.GetName()) + // (luckyNumber we have to cast for, again) + + owner, ok := animal.Owner.(*queryWithFragmentsBeingsOwnerUser) + require.Truef(t, ok, "got %T, not User", animal.Owner) + assert.Equal(t, "1", owner.Id) + assert.Equal(t, "Yours Truly", owner.Name) + assert.Equal(t, 17, owner.LuckyNumber) + + 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 5c42e462..de6d3f50 100644 --- a/internal/integration/schema.graphql +++ b/internal/integration/schema.graphql @@ -3,21 +3,28 @@ type Query { user(id: ID!): User being(id: ID!): Being beings(ids: [ID!]!): [Being]! + lotteryWinner(number: Int!): Lucky } -type User implements Being { +type User implements Being & Lucky { id: ID! name: String! luckyNumber: Int + hair: Hair } +type Hair { color: String } # silly name to confuse the name-generator + type Animal implements Being { id: ID! name: String! species: Species! owner: Being + hair: BeingsHair } +type BeingsHair { hasHair: Boolean! } # silly name to confuse the name-generator + enum Species { DOG COELACANTH @@ -27,3 +34,7 @@ interface Being { id: ID! name: String! } + +interface Lucky { + luckyNumber: Int +} diff --git a/internal/integration/server/gqlgen_exec.go b/internal/integration/server/gqlgen_exec.go index d50db5ad..709824fe 100644 --- a/internal/integration/server/gqlgen_exec.go +++ b/internal/integration/server/gqlgen_exec.go @@ -43,20 +43,31 @@ type DirectiveRoot struct { type ComplexityRoot struct { Animal struct { + Hair func(childComplexity int) int ID func(childComplexity int) int Name func(childComplexity int) int Owner func(childComplexity int) int Species func(childComplexity int) int } + BeingsHair struct { + HasHair func(childComplexity int) int + } + + Hair struct { + Color func(childComplexity int) int + } + Query struct { - 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 + Being func(childComplexity int, id string) int + Beings func(childComplexity int, ids []string) int + LotteryWinner func(childComplexity int, number int) int + Me func(childComplexity int) int + User func(childComplexity int, id string) int } User struct { + Hair func(childComplexity int) int ID func(childComplexity int) int LuckyNumber func(childComplexity int) int Name func(childComplexity int) int @@ -68,6 +79,7 @@ type QueryResolver interface { User(ctx context.Context, id string) (*User, error) Being(ctx context.Context, id string) (Being, error) Beings(ctx context.Context, ids []string) ([]Being, error) + LotteryWinner(ctx context.Context, number int) (Lucky, error) } type executableSchema struct { @@ -85,6 +97,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in _ = ec switch typeName + "." + field { + case "Animal.hair": + if e.complexity.Animal.Hair == nil { + break + } + + return e.complexity.Animal.Hair(childComplexity), true + case "Animal.id": if e.complexity.Animal.ID == nil { break @@ -113,6 +132,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Animal.Species(childComplexity), true + case "BeingsHair.hasHair": + if e.complexity.BeingsHair.HasHair == nil { + break + } + + return e.complexity.BeingsHair.HasHair(childComplexity), true + + case "Hair.color": + if e.complexity.Hair.Color == nil { + break + } + + return e.complexity.Hair.Color(childComplexity), true + case "Query.being": if e.complexity.Query.Being == nil { break @@ -137,6 +170,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Beings(childComplexity, args["ids"].([]string)), true + case "Query.lotteryWinner": + if e.complexity.Query.LotteryWinner == nil { + break + } + + args, err := ec.field_Query_lotteryWinner_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.LotteryWinner(childComplexity, args["number"].(int)), true + case "Query.me": if e.complexity.Query.Me == nil { break @@ -156,6 +201,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.User(childComplexity, args["id"].(string)), true + case "User.hair": + if e.complexity.User.Hair == nil { + break + } + + return e.complexity.User.Hair(childComplexity), true + case "User.id": if e.complexity.User.ID == nil { break @@ -232,21 +284,28 @@ var sources = []*ast.Source{ user(id: ID!): User being(id: ID!): Being beings(ids: [ID!]!): [Being]! + lotteryWinner(number: Int!): Lucky } -type User implements Being { +type User implements Being & Lucky { id: ID! name: String! luckyNumber: Int + hair: Hair } +type Hair { color: String } # silly name to confuse the name-generator + type Animal implements Being { id: ID! name: String! species: Species! owner: Being + hair: BeingsHair } +type BeingsHair { hasHair: Boolean! } # silly name to confuse the name-generator + enum Species { DOG COELACANTH @@ -256,6 +315,10 @@ interface Being { id: ID! name: String! } + +interface Lucky { + luckyNumber: Int +} `, BuiltIn: false}, } var parsedSchema = gqlparser.MustLoadSchema(sources...) @@ -309,6 +372,21 @@ func (ec *executionContext) field_Query_beings_args(ctx context.Context, rawArgs return args, nil } +func (ec *executionContext) field_Query_lotteryWinner_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 int + if tmp, ok := rawArgs["number"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("number")) + arg0, err = ec.unmarshalNInt2int(ctx, tmp) + if err != nil { + return nil, err + } + } + args["number"] = 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{}{} @@ -499,6 +577,105 @@ func (ec *executionContext) _Animal_owner(ctx context.Context, field graphql.Col return ec.marshalOBeing2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐBeing(ctx, field.Selections, res) } +func (ec *executionContext) _Animal_hair(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.Hair, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*BeingsHair) + fc.Result = res + return ec.marshalOBeingsHair2ᚖgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐBeingsHair(ctx, field.Selections, res) +} + +func (ec *executionContext) _BeingsHair_hasHair(ctx context.Context, field graphql.CollectedField, obj *BeingsHair) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "BeingsHair", + 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.HasHair, 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.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) _Hair_color(ctx context.Context, field graphql.CollectedField, obj *Hair) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Hair", + 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.Color, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(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 { @@ -651,6 +828,45 @@ func (ec *executionContext) _Query_beings(ctx context.Context, field graphql.Col return ec.marshalNBeing2ᚕgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐBeing(ctx, field.Selections, res) } +func (ec *executionContext) _Query_lotteryWinner(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_lotteryWinner_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().LotteryWinner(rctx, args["number"].(int)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(Lucky) + fc.Result = res + return ec.marshalOLucky2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐLucky(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 { @@ -824,6 +1040,38 @@ func (ec *executionContext) _User_luckyNumber(ctx context.Context, field graphql return ec.marshalOInt2ᚖint(ctx, field.Selections, res) } +func (ec *executionContext) _User_hair(ctx context.Context, field graphql.CollectedField, obj *User) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "User", + 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.Hair, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*Hair) + fc.Result = res + return ec.marshalOHair2ᚖgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐHair(ctx, field.Selections, res) +} + func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -1938,6 +2186,22 @@ func (ec *executionContext) _Being(ctx context.Context, sel ast.SelectionSet, ob } } +func (ec *executionContext) _Lucky(ctx context.Context, sel ast.SelectionSet, obj Lucky) 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) + default: + panic(fmt.Errorf("unexpected type %T", obj)) + } +} + // endregion ************************** interface.gotpl *************************** // region **************************** object.gotpl **************************** @@ -1970,6 +2234,59 @@ func (ec *executionContext) _Animal(ctx context.Context, sel ast.SelectionSet, o } case "owner": out.Values[i] = ec._Animal_owner(ctx, field, obj) + case "hair": + out.Values[i] = ec._Animal_hair(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + +var beingsHairImplementors = []string{"BeingsHair"} + +func (ec *executionContext) _BeingsHair(ctx context.Context, sel ast.SelectionSet, obj *BeingsHair) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, beingsHairImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("BeingsHair") + case "hasHair": + out.Values[i] = ec._BeingsHair_hasHair(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + +var hairImplementors = []string{"Hair"} + +func (ec *executionContext) _Hair(ctx context.Context, sel ast.SelectionSet, obj *Hair) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, hairImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Hair") + case "color": + out.Values[i] = ec._Hair_color(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -2043,6 +2360,17 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } return res }) + case "lotteryWinner": + 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_lotteryWinner(ctx, field) + return res + }) case "__type": out.Values[i] = ec._Query___type(ctx, field) case "__schema": @@ -2058,7 +2386,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr return out } -var userImplementors = []string{"User", "Being"} +var userImplementors = []string{"User", "Being", "Lucky"} func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj *User) graphql.Marshaler { fields := graphql.CollectFields(ec.OperationContext, sel, userImplementors) @@ -2081,6 +2409,8 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj } case "luckyNumber": out.Values[i] = ec._User_luckyNumber(ctx, field, obj) + case "hair": + out.Values[i] = ec._User_hair(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -2434,6 +2764,21 @@ func (ec *executionContext) marshalNID2ᚕstringᚄ(ctx context.Context, sel ast return ret } +func (ec *executionContext) unmarshalNInt2int(ctx context.Context, v interface{}) (int, error) { + res, err := graphql.UnmarshalInt(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.SelectionSet, v int) graphql.Marshaler { + res := graphql.MarshalInt(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + } + 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) @@ -2695,6 +3040,13 @@ func (ec *executionContext) marshalOBeing2githubᚗcomᚋKhanᚋgenqlientᚋinte return ec._Being(ctx, sel, v) } +func (ec *executionContext) marshalOBeingsHair2ᚖgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐBeingsHair(ctx context.Context, sel ast.SelectionSet, v *BeingsHair) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._BeingsHair(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) @@ -2719,6 +3071,13 @@ func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast return graphql.MarshalBoolean(*v) } +func (ec *executionContext) marshalOHair2ᚖgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐHair(ctx context.Context, sel ast.SelectionSet, v *Hair) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._Hair(ctx, sel, v) +} + func (ec *executionContext) unmarshalOInt2ᚖint(ctx context.Context, v interface{}) (*int, error) { if v == nil { return nil, nil @@ -2734,6 +3093,13 @@ func (ec *executionContext) marshalOInt2ᚖint(ctx context.Context, sel ast.Sele return graphql.MarshalInt(*v) } +func (ec *executionContext) marshalOLucky2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐLucky(ctx context.Context, sel ast.SelectionSet, v Lucky) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._Lucky(ctx, sel, v) +} + func (ec *executionContext) unmarshalOString2string(ctx context.Context, v interface{}) (string, error) { res, err := graphql.UnmarshalString(v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/internal/integration/server/gqlgen_models.go b/internal/integration/server/gqlgen_models.go index a3318fe4..a8b225b6 100644 --- a/internal/integration/server/gqlgen_models.go +++ b/internal/integration/server/gqlgen_models.go @@ -12,22 +12,37 @@ type Being interface { IsBeing() } +type Lucky interface { + IsLucky() +} + type Animal struct { - ID string `json:"id"` - Name string `json:"name"` - Species Species `json:"species"` - Owner Being `json:"owner"` + ID string `json:"id"` + Name string `json:"name"` + Species Species `json:"species"` + Owner Being `json:"owner"` + Hair *BeingsHair `json:"hair"` } func (Animal) IsBeing() {} +type BeingsHair struct { + HasHair bool `json:"hasHair"` +} + +type Hair struct { + Color *string `json:"color"` +} + type User struct { ID string `json:"id"` Name string `json:"name"` LuckyNumber *int `json:"luckyNumber"` + Hair *Hair `json:"hair"` } func (User) IsBeing() {} +func (User) IsLucky() {} type Species string diff --git a/internal/integration/server/server.go b/internal/integration/server/server.go index 8032326f..6cff7ccc 100644 --- a/internal/integration/server/server.go +++ b/internal/integration/server/server.go @@ -8,16 +8,26 @@ import ( "github.com/99designs/gqlgen/graphql/handler/transport" ) -func intptr(v int) *int { return &v } +func strptr(v string) *string { return &v } +func intptr(v int) *int { return &v } var users = []*User{ - {ID: "1", Name: "Yours Truly", LuckyNumber: intptr(17)}, - {ID: "2", Name: "Raven", LuckyNumber: intptr(-1)}, + { + ID: "1", Name: "Yours Truly", LuckyNumber: intptr(17), + Hair: &Hair{Color: strptr("Black")}, + }, + {ID: "2", Name: "Raven", LuckyNumber: intptr(-1), Hair: nil}, } var animals = []*Animal{ - {ID: "3", Name: "Fido", Species: SpeciesDog, Owner: userByID("0")}, - {ID: "4", Name: "Old One", Species: SpeciesCoelacanth, Owner: nil}, + { + ID: "3", Name: "Fido", Species: SpeciesDog, Owner: userByID("1"), + Hair: &BeingsHair{HasHair: true}, + }, + { + ID: "4", Name: "Old One", Species: SpeciesCoelacanth, Owner: nil, + Hair: &BeingsHair{HasHair: false}, + }, } func userByID(id string) *User { @@ -63,6 +73,15 @@ func (r *queryResolver) Beings(ctx context.Context, ids []string) ([]Being, erro return ret, nil } +func (r *queryResolver) LotteryWinner(ctx context.Context, number int) (Lucky, error) { + for _, user := range users { + if user.LuckyNumber != nil && *user.LuckyNumber == number { + return user, nil + } + } + return nil, nil +} + func RunServer() *httptest.Server { gqlgenServer := handler.New(NewExecutableSchema(Config{Resolvers: &resolver{}})) gqlgenServer.AddTransport(transport.POST{})