diff --git a/generate/convert.go b/generate/convert.go index d5066154..ac0f3946 100644 --- a/generate/convert.go +++ b/generate/convert.go @@ -200,38 +200,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: @@ -270,38 +252,22 @@ func (g *generator) convertDefinition( return goType, nil case ast.Interface, ast.Union: + 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 @@ -357,7 +323,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 feaf9b26..d3c940c2 100644 --- a/generate/testdata/queries/schema.graphql +++ b/generate/testdata/queries/schema.graphql @@ -65,6 +65,13 @@ type User { emailsWithNullsOrNull: [String] authMethods: [AuthMethod!]! pokemon: [Pokemon!] + 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.""" @@ -73,6 +80,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.""" @@ -83,15 +96,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 { @@ -99,7 +126,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{})