Add support for concrete-typed named fragments (#75)

## Summary:
In previous commits I added support to genqlient for interfaces and
inline fragments.  This means the only query structures that remain are
named fragments and their spreads, e.g.
```
fragment MyFragment on MyType { myField }
query MyQuery { getMyType { ...MyFragment } }
```
Other than mere completionism, these are potentially useful for code
sharing: you can spread the same fragment multiple places; and then
genqlient can notice that and generate the same type for each.  (They
can even be shared between different queries in the same package.)

In this commit I add support for named fragments of concrete
(object/struct, not interface) type, spread into either concrete or
abstract scope.  For genqlient's purposes, these are a new "root"
type-name, just like each operation, and are then embedded into the
appropriate struct.  (Using embeds allows their fields to be referenced
as fields of the containing type, if convenient.  Further design
considerations are discussed in DESIGN.md.)

This requires new code in two main places (plus miscellaneous glue),
both nontrivial but neither particularly complex:
- We need to actually traverse both structures and generate the types
  (in `convert.go`).
- We need to decide which fragments from this package to send to the
  server, both for good hyigene and because GraphQL requires we send
  only ones this query uses (in `generate.go`).
- We need a little new wiring for options -- because fragments can be
  shared between queries they get their own toplevel options, rather
  than inheriting the query's options.

Finally, this required slightly subtler changes to how we do
unmarshaling (in `types.go` and `unmarshal.go.tmpl`).  Basically,
because embedded fields' methods, including `UnmarshalJSON`, get
promoted to the parent type, and because the JSON library ignores their
fields when shadowed by those of the parent type, we need a little bit
of special logic in each such parent type to do its own unmarshal and
then delegate to each embed.  This is similar (and much simpler) to
what we did for interfaces, although it required some changes to the
"method-hiding" trick (used for both).  It's only really necessary in
certain specific cases (namely when an embedded type has an
`UnmarshalJSON` method or a field with the same name as the embedder),
but it's easier to just generate it always.  This is all described in
more detail inline.

This does not support fragments of abstract type, which have their own
complexities.  I'll address those, which are now the only remaining
piece of #8, in a future commit.

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

## Test plan:
make check


Author: benjaminjkraft

Reviewers: dnerdy, benjaminjkraft, aberkan, MiguelCastillo

Required Reviewers: 

Approved By: dnerdy

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

Pull Request URL: https://github.com/Khan/genqlient/pull/75
This commit is contained in:
Ben Kraft
2021-09-09 09:39:30 -07:00
committed by GitHub
parent 1c061b153a
commit f99c10d6fd
22 changed files with 1857 additions and 123 deletions
+75 -2
View File
@@ -330,7 +330,12 @@ func (g *generator) convertSelectionSet(
}
fields = append(fields, field)
case *ast.FragmentSpread:
return nil, errorf(selection.Position, "not implemented: %T", selection)
maybeField, err := g.convertFragmentSpread(selection, containingTypedef)
if err != nil {
return nil, err
} else if maybeField != nil {
fields = append(fields, maybeField)
}
case *ast.InlineFragment:
// (Note this will return nil, nil if the fragment doesn't apply to
// this type.)
@@ -354,7 +359,9 @@ func (g *generator) convertSelectionSet(
// 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] {
// It's fine to have duplicate embeds (i.e. via named fragments), even
// ones with complicated overlaps, since they are separate types to us.
if field.JSONName != "" && 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
@@ -444,6 +451,72 @@ func (g *generator) convertInlineFragment(
containingTypedef, queryOptions)
}
// convertFragmentSpread converts a single GraphQL fragment-spread
// (`...MyFragment`) into a Go struct-field. If the fragment does not apply to
// this type, returns nil.
//
// containingTypedef is as described in convertInlineFragment, above.
func (g *generator) convertFragmentSpread(
fragmentSpread *ast.FragmentSpread,
containingTypedef *ast.Definition,
) (*goStructField, error) {
if !fragmentMatches(containingTypedef, fragmentSpread.Definition.Definition) {
return nil, nil
}
typ, ok := g.typeMap[fragmentSpread.Name]
if !ok {
// If we haven't yet, convert the fragment itself. Note that fragments
// aren't allowed to have cycles, so this won't recurse forever.
var err error
typ, err = g.convertNamedFragment(fragmentSpread.Definition)
if err != nil {
return nil, err
}
}
return &goStructField{GoName: "" /* i.e. embedded */, GoType: typ}, nil
}
// convertNamedFragment converts a single GraphQL named fragment-definition
// (`fragment MyFragment on MyType { ... }`) into a Go struct.
func (g *generator) convertNamedFragment(fragment *ast.FragmentDefinition) (goType, error) {
typ := g.schema.Types[fragment.TypeCondition]
if !g.Config.AllowBrokenFeatures &&
(typ.Kind == ast.Interface || typ.Kind == ast.Union) {
return nil, errorf(fragment.Position, "not implemented: abstract-typed fragments")
}
description, directive, err := g.parsePrecedingComment(fragment, fragment.Position)
if err != nil {
return nil, err
}
// If the user included a comment, use that. Else make up something
// generic; there's not much to say though.
if description == "" {
description = fmt.Sprintf(
"%v includes the GraphQL fields of %v requested by the fragment %v.",
fragment.Name, fragment.TypeCondition, fragment.Name)
}
fields, err := g.convertSelectionSet(
newPrefixList(fragment.Name), fragment.SelectionSet, typ, directive)
if err != nil {
return nil, err
}
goType := &goStructType{
GoName: fragment.Name,
Description: description,
GraphQLName: fragment.TypeCondition,
Fields: fields,
Incomplete: false,
}
g.typeMap[fragment.Name] = goType
return goType, nil
}
// convertField converts a single GraphQL operation-field into a Go
// struct-field (and its type).
//
+62 -15
View File
@@ -35,6 +35,11 @@ type generator struct {
templateCache map[string]*template.Template
// Schema we are generating code against
schema *ast.Schema
// Named fragments (map by name), so we can look them up from spreads.
// TODO(benkraft): In theory we shouldn't need this, we can just use
// ast.FragmentSpread.Definition, but for some reason it doesn't seem to be
// set consistently, even post-validation.
fragments map[string]*ast.FragmentDefinition
}
// JSON tags in operation are for ExportOperations (see Config for details).
@@ -67,7 +72,11 @@ type argument struct {
Options *GenqlientDirective
}
func newGenerator(config *Config, schema *ast.Schema) (*generator, error) {
func newGenerator(
config *Config,
schema *ast.Schema,
fragments ast.FragmentDefinitionList,
) (*generator, error) {
g := generator{
Config: config,
typeMap: map[string]goType{},
@@ -75,6 +84,11 @@ func newGenerator(config *Config, schema *ast.Schema) (*generator, error) {
usedAliases: map[string]bool{},
templateCache: map[string]*template.Template{},
schema: schema,
fragments: make(map[string]*ast.FragmentDefinition, len(fragments)),
}
for _, fragment := range fragments {
g.fragments[fragment.Name] = fragment
}
_, err := g.addRef("github.com/Khan/genqlient/graphql.Client")
@@ -146,6 +160,38 @@ func (g *generator) getArgument(
}, nil
}
// usedFragmentNames returns the named-fragments used by (i.e. spread into)
// this operation.
func (g *generator) usedFragments(op *ast.OperationDefinition) ast.FragmentDefinitionList {
var retval, queue ast.FragmentDefinitionList
seen := map[string]bool{}
var observers validator.Events
// Fragment-spreads are easy to find; just ask for them!
observers.OnFragmentSpread(func(_ *validator.Walker, fragmentSpread *ast.FragmentSpread) {
if seen[fragmentSpread.Name] {
return
}
def := g.fragments[fragmentSpread.Name]
seen[fragmentSpread.Name] = true
retval = append(retval, def)
queue = append(queue, def)
})
doc := ast.QueryDocument{Operations: ast.OperationList{op}}
validator.Walk(g.schema, &doc, &observers)
// Well, easy-ish: we also have to look recursively.
// Note GraphQL guarantees there are no cycles among fragments:
// https://spec.graphql.org/draft/#sec-Fragment-spreads-must-not-form-cycles
for len(queue) > 0 {
doc = ast.QueryDocument{Fragments: ast.FragmentDefinitionList{queue[0]}}
validator.Walk(g.schema, &doc, &observers) // traversal is the same
queue = queue[1:]
}
return retval
}
// Preprocess each query to make any changes that genqlient needs.
//
// At present, the only change is that we add __typename, if not already
@@ -158,9 +204,11 @@ func (g *generator) preprocessQueryDocument(doc *ast.QueryDocument) {
// __typename field. There are four places we might find a selection-set:
// at the toplevel of a query, on a field, or in an inline or named
// fragment. The toplevel of a query must be an object type, so we don't
// need to consider that.
// TODO(benkraft): Once we support fragments, figure out whether we need to
// traverse inline/named fragments here too.
// need to consider that. And fragments must (if used at all) be spread
// into some parent selection-set, so we'll add __typename there (if
// needed). Note this does mean abstract-typed fragments spread into
// object-typed scope will *not* have access to `__typename`, but they
// indeed don't need it, since we do know the type in that context.
observers.OnField(func(_ *validator.Walker, field *ast.Field) {
// We are interested in a field from the query like
// field { subField ... }
@@ -214,6 +262,11 @@ func (g *generator) preprocessQueryDocument(doc *ast.QueryDocument) {
validator.Walk(g.schema, doc, &observers)
}
// addOperation adds to g.Operations the information needed to generate a
// genqlient entrypoint function for the given operation. It also adds to
// g.typeMap any types referenced by the operation, except for types belonging
// to named fragments, which are added separately by Generate via
// convertFragment.
func (g *generator) addOperation(op *ast.OperationDefinition) error {
if op.Name == "" {
return errorf(op.Position, "operations must have operation-names")
@@ -221,7 +274,7 @@ func (g *generator) addOperation(op *ast.OperationDefinition) error {
queryDoc := &ast.QueryDocument{
Operations: ast.OperationList{op},
// TODO: handle fragments
Fragments: g.usedFragments(op),
}
g.preprocessQueryDocument(queryDoc)
@@ -304,19 +357,13 @@ func Generate(config *Config) (map[string][]byte, error) {
strings.Join(config.Operations, ", "))
}
if len(document.Fragments) > 0 && !config.AllowBrokenFeatures {
return nil, errorf(document.Fragments[0].Position,
"genqlient does not yet support fragments")
}
// Step 2: For each operation, convert it into data structures representing
// Go types (defined in types.go). The bulk of this logic is in
// convert.go.
g, err := newGenerator(config, schema)
// Step 2: For each operation and fragment, convert it into data structures
// representing Go types (defined in types.go). The bulk of this logic is
// in convert.go.
g, err := newGenerator(config, schema, document.Fragments)
if err != nil {
return nil, err
}
for _, op := range document.Operations {
if err = g.addOperation(op); err != nil {
return nil, err
+10 -1
View File
@@ -153,6 +153,15 @@ func (dir *GenqlientDirective) validate(node interface{}) error {
// Anything else is valid on the entire operation; it will just apply
// to whatever it is relevant to.
return nil
case *ast.FragmentDefinition:
if dir.Bind != "" {
// TODO(benkraft): Implement this if people find it useful.
return errorf(dir.pos, "bind is not implemented for named fragments")
}
// Like operations, anything else will just apply to the entire
// fragment.
return nil
case *ast.VariableDefinition:
if dir.Omitempty != nil && node.Type.NonNull {
return errorf(dir.pos, "omitempty may only be used on optional arguments")
@@ -164,7 +173,7 @@ func (dir *GenqlientDirective) validate(node interface{}) error {
}
return nil
default:
return errorf(dir.pos, "invalid directive location: %T", node)
return errorf(dir.pos, "invalid @genqlient directive location: %T", node)
}
}
+39
View File
@@ -0,0 +1,39 @@
fragment QueryFragment on Query {
...InnerQueryFragment
}
fragment InnerQueryFragment on Query {
randomItem {
id name
...VideoFields
}
randomLeaf {
...VideoFields
...MoreVideoFields
}
otherLeaf: randomLeaf {
... on Video {
...MoreVideoFields
}
}
}
fragment VideoFields on Video {
id name url duration thumbnail { id }
}
# @genqlient(pointer: true)
fragment MoreVideoFields on Video {
id
parent {
name url
# @genqlient(pointer: false)
children {
...VideoFields
}
}
}
query ComplexNamedFragments {
... on Query { ...QueryFragment }
}
+13
View File
@@ -0,0 +1,13 @@
fragment VideoFields on Video {
id name url duration thumbnail { id }
}
query SimpleNamedFragment {
randomItem {
id name
...VideoFields
}
randomLeaf {
...VideoFields
}
}
@@ -184,13 +184,12 @@ type ComplexInlineFragmentsNestedStuffTopic struct {
func (v *ComplexInlineFragmentsNestedStuffTopic) UnmarshalJSON(b []byte) error {
type ComplexInlineFragmentsNestedStuffTopicWrapper ComplexInlineFragmentsNestedStuffTopic
var firstPass struct {
*ComplexInlineFragmentsNestedStuffTopicWrapper
*ComplexInlineFragmentsNestedStuffTopic
Children []json.RawMessage `json:"children"`
graphql.NoUnmarshalJSON
}
firstPass.ComplexInlineFragmentsNestedStuffTopicWrapper = (*ComplexInlineFragmentsNestedStuffTopicWrapper)(v)
firstPass.ComplexInlineFragmentsNestedStuffTopic = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
@@ -213,6 +212,7 @@ func (v *ComplexInlineFragmentsNestedStuffTopic) UnmarshalJSON(b []byte) error {
}
}
}
return nil
}
@@ -232,13 +232,12 @@ type ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParentTop
func (v *ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParentTopic) UnmarshalJSON(b []byte) error {
type ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParentTopicWrapper ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParentTopic
var firstPass struct {
*ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParentTopicWrapper
*ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParentTopic
Children []json.RawMessage `json:"children"`
graphql.NoUnmarshalJSON
}
firstPass.ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParentTopicWrapper = (*ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParentTopicWrapper)(v)
firstPass.ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParentTopic = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
@@ -261,6 +260,7 @@ func (v *ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParen
}
}
}
return nil
}
@@ -797,16 +797,15 @@ type ComplexInlineFragmentsResponse struct {
func (v *ComplexInlineFragmentsResponse) UnmarshalJSON(b []byte) error {
type ComplexInlineFragmentsResponseWrapper ComplexInlineFragmentsResponse
var firstPass struct {
*ComplexInlineFragmentsResponseWrapper
*ComplexInlineFragmentsResponse
RandomItem json.RawMessage `json:"randomItem"`
RepeatedStuff json.RawMessage `json:"repeatedStuff"`
ConflictingStuff json.RawMessage `json:"conflictingStuff"`
NestedStuff json.RawMessage `json:"nestedStuff"`
graphql.NoUnmarshalJSON
}
firstPass.ComplexInlineFragmentsResponseWrapper = (*ComplexInlineFragmentsResponseWrapper)(v)
firstPass.ComplexInlineFragmentsResponse = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
@@ -823,6 +822,7 @@ func (v *ComplexInlineFragmentsResponse) UnmarshalJSON(b []byte) error {
"Unable to unmarshal ComplexInlineFragmentsResponse.RandomItem: %w", err)
}
}
{
target := &v.RepeatedStuff
raw := firstPass.RepeatedStuff
@@ -833,6 +833,7 @@ func (v *ComplexInlineFragmentsResponse) UnmarshalJSON(b []byte) error {
"Unable to unmarshal ComplexInlineFragmentsResponse.RepeatedStuff: %w", err)
}
}
{
target := &v.ConflictingStuff
raw := firstPass.ConflictingStuff
@@ -843,6 +844,7 @@ func (v *ComplexInlineFragmentsResponse) UnmarshalJSON(b []byte) error {
"Unable to unmarshal ComplexInlineFragmentsResponse.ConflictingStuff: %w", err)
}
}
{
target := &v.NestedStuff
raw := firstPass.NestedStuff
@@ -853,6 +855,7 @@ func (v *ComplexInlineFragmentsResponse) UnmarshalJSON(b []byte) error {
"Unable to unmarshal ComplexInlineFragmentsResponse.NestedStuff: %w", err)
}
}
return nil
}
@@ -0,0 +1,666 @@
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"
)
// ComplexNamedFragmentsResponse is returned by ComplexNamedFragments on success.
type ComplexNamedFragmentsResponse struct {
QueryFragment `json:"-"`
}
func (v *ComplexNamedFragmentsResponse) UnmarshalJSON(b []byte) error {
var firstPass struct {
*ComplexNamedFragmentsResponse
graphql.NoUnmarshalJSON
}
firstPass.ComplexNamedFragmentsResponse = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
err = json.Unmarshal(b, &v.QueryFragment)
if err != nil {
return err
}
return nil
}
// InnerQueryFragment includes the GraphQL fields of Query requested by the fragment InnerQueryFragment.
type InnerQueryFragment struct {
RandomItem InnerQueryFragmentRandomItemContent `json:"-"`
RandomLeaf InnerQueryFragmentRandomLeafLeafContent `json:"-"`
OtherLeaf InnerQueryFragmentOtherLeafLeafContent `json:"-"`
}
func (v *InnerQueryFragment) UnmarshalJSON(b []byte) error {
var firstPass struct {
*InnerQueryFragment
RandomItem json.RawMessage `json:"randomItem"`
RandomLeaf json.RawMessage `json:"randomLeaf"`
OtherLeaf json.RawMessage `json:"otherLeaf"`
graphql.NoUnmarshalJSON
}
firstPass.InnerQueryFragment = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
{
target := &v.RandomItem
raw := firstPass.RandomItem
err = __unmarshalInnerQueryFragmentRandomItemContent(
target, raw)
if err != nil {
return fmt.Errorf(
"Unable to unmarshal InnerQueryFragment.RandomItem: %w", err)
}
}
{
target := &v.RandomLeaf
raw := firstPass.RandomLeaf
err = __unmarshalInnerQueryFragmentRandomLeafLeafContent(
target, raw)
if err != nil {
return fmt.Errorf(
"Unable to unmarshal InnerQueryFragment.RandomLeaf: %w", err)
}
}
{
target := &v.OtherLeaf
raw := firstPass.OtherLeaf
err = __unmarshalInnerQueryFragmentOtherLeafLeafContent(
target, raw)
if err != nil {
return fmt.Errorf(
"Unable to unmarshal InnerQueryFragment.OtherLeaf: %w", err)
}
}
return nil
}
// InnerQueryFragmentOtherLeafArticle includes the requested fields of the GraphQL type Article.
type InnerQueryFragmentOtherLeafArticle struct {
Typename string `json:"__typename"`
}
// InnerQueryFragmentOtherLeafLeafContent includes the requested fields of the GraphQL interface LeafContent.
//
// InnerQueryFragmentOtherLeafLeafContent is implemented by the following types:
// InnerQueryFragmentOtherLeafArticle
// InnerQueryFragmentOtherLeafVideo
//
// The GraphQL type's documentation follows.
//
// LeafContent represents content items that can't have child-nodes.
type InnerQueryFragmentOtherLeafLeafContent interface {
implementsGraphQLInterfaceInnerQueryFragmentOtherLeafLeafContent()
// GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values).
GetTypename() string
}
func (v *InnerQueryFragmentOtherLeafArticle) implementsGraphQLInterfaceInnerQueryFragmentOtherLeafLeafContent() {
}
// GetTypename is a part of, and documented with, the interface InnerQueryFragmentOtherLeafLeafContent.
func (v *InnerQueryFragmentOtherLeafArticle) GetTypename() string { return v.Typename }
func (v *InnerQueryFragmentOtherLeafVideo) implementsGraphQLInterfaceInnerQueryFragmentOtherLeafLeafContent() {
}
// GetTypename is a part of, and documented with, the interface InnerQueryFragmentOtherLeafLeafContent.
func (v *InnerQueryFragmentOtherLeafVideo) GetTypename() string { return v.Typename }
func __unmarshalInnerQueryFragmentOtherLeafLeafContent(v *InnerQueryFragmentOtherLeafLeafContent, 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(InnerQueryFragmentOtherLeafArticle)
return json.Unmarshal(m, *v)
case "Video":
*v = new(InnerQueryFragmentOtherLeafVideo)
return json.Unmarshal(m, *v)
case "":
return fmt.Errorf(
"Response was missing LeafContent.__typename")
default:
return fmt.Errorf(
`Unexpected concrete type for InnerQueryFragmentOtherLeafLeafContent: "%v"`, tn.TypeName)
}
}
// InnerQueryFragmentOtherLeafVideo includes the requested fields of the GraphQL type Video.
type InnerQueryFragmentOtherLeafVideo struct {
Typename string `json:"__typename"`
MoreVideoFields `json:"-"`
}
func (v *InnerQueryFragmentOtherLeafVideo) UnmarshalJSON(b []byte) error {
var firstPass struct {
*InnerQueryFragmentOtherLeafVideo
graphql.NoUnmarshalJSON
}
firstPass.InnerQueryFragmentOtherLeafVideo = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
err = json.Unmarshal(b, &v.MoreVideoFields)
if err != nil {
return err
}
return nil
}
// InnerQueryFragmentRandomItemArticle includes the requested fields of the GraphQL type Article.
type InnerQueryFragmentRandomItemArticle struct {
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id testutil.ID `json:"id"`
Name string `json:"name"`
}
// InnerQueryFragmentRandomItemContent includes the requested fields of the GraphQL interface Content.
//
// InnerQueryFragmentRandomItemContent is implemented by the following types:
// InnerQueryFragmentRandomItemArticle
// InnerQueryFragmentRandomItemVideo
// InnerQueryFragmentRandomItemTopic
//
// The GraphQL type's documentation follows.
//
// Content is implemented by various types like Article, Video, and Topic.
type InnerQueryFragmentRandomItemContent interface {
implementsGraphQLInterfaceInnerQueryFragmentRandomItemContent()
// 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 *InnerQueryFragmentRandomItemArticle) implementsGraphQLInterfaceInnerQueryFragmentRandomItemContent() {
}
// GetTypename is a part of, and documented with, the interface InnerQueryFragmentRandomItemContent.
func (v *InnerQueryFragmentRandomItemArticle) GetTypename() string { return v.Typename }
// GetId is a part of, and documented with, the interface InnerQueryFragmentRandomItemContent.
func (v *InnerQueryFragmentRandomItemArticle) GetId() testutil.ID { return v.Id }
// GetName is a part of, and documented with, the interface InnerQueryFragmentRandomItemContent.
func (v *InnerQueryFragmentRandomItemArticle) GetName() string { return v.Name }
func (v *InnerQueryFragmentRandomItemVideo) implementsGraphQLInterfaceInnerQueryFragmentRandomItemContent() {
}
// GetTypename is a part of, and documented with, the interface InnerQueryFragmentRandomItemContent.
func (v *InnerQueryFragmentRandomItemVideo) GetTypename() string { return v.Typename }
// GetId is a part of, and documented with, the interface InnerQueryFragmentRandomItemContent.
func (v *InnerQueryFragmentRandomItemVideo) GetId() testutil.ID { return v.Id }
// GetName is a part of, and documented with, the interface InnerQueryFragmentRandomItemContent.
func (v *InnerQueryFragmentRandomItemVideo) GetName() string { return v.Name }
func (v *InnerQueryFragmentRandomItemTopic) implementsGraphQLInterfaceInnerQueryFragmentRandomItemContent() {
}
// GetTypename is a part of, and documented with, the interface InnerQueryFragmentRandomItemContent.
func (v *InnerQueryFragmentRandomItemTopic) GetTypename() string { return v.Typename }
// GetId is a part of, and documented with, the interface InnerQueryFragmentRandomItemContent.
func (v *InnerQueryFragmentRandomItemTopic) GetId() testutil.ID { return v.Id }
// GetName is a part of, and documented with, the interface InnerQueryFragmentRandomItemContent.
func (v *InnerQueryFragmentRandomItemTopic) GetName() string { return v.Name }
func __unmarshalInnerQueryFragmentRandomItemContent(v *InnerQueryFragmentRandomItemContent, 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(InnerQueryFragmentRandomItemArticle)
return json.Unmarshal(m, *v)
case "Video":
*v = new(InnerQueryFragmentRandomItemVideo)
return json.Unmarshal(m, *v)
case "Topic":
*v = new(InnerQueryFragmentRandomItemTopic)
return json.Unmarshal(m, *v)
case "":
return fmt.Errorf(
"Response was missing Content.__typename")
default:
return fmt.Errorf(
`Unexpected concrete type for InnerQueryFragmentRandomItemContent: "%v"`, tn.TypeName)
}
}
// InnerQueryFragmentRandomItemTopic includes the requested fields of the GraphQL type Topic.
type InnerQueryFragmentRandomItemTopic struct {
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id testutil.ID `json:"id"`
Name string `json:"name"`
}
// InnerQueryFragmentRandomItemVideo includes the requested fields of the GraphQL type Video.
type InnerQueryFragmentRandomItemVideo struct {
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id testutil.ID `json:"id"`
Name string `json:"name"`
VideoFields `json:"-"`
}
func (v *InnerQueryFragmentRandomItemVideo) UnmarshalJSON(b []byte) error {
var firstPass struct {
*InnerQueryFragmentRandomItemVideo
graphql.NoUnmarshalJSON
}
firstPass.InnerQueryFragmentRandomItemVideo = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
err = json.Unmarshal(b, &v.VideoFields)
if err != nil {
return err
}
return nil
}
// InnerQueryFragmentRandomLeafArticle includes the requested fields of the GraphQL type Article.
type InnerQueryFragmentRandomLeafArticle struct {
Typename string `json:"__typename"`
}
// InnerQueryFragmentRandomLeafLeafContent includes the requested fields of the GraphQL interface LeafContent.
//
// InnerQueryFragmentRandomLeafLeafContent is implemented by the following types:
// InnerQueryFragmentRandomLeafArticle
// InnerQueryFragmentRandomLeafVideo
//
// The GraphQL type's documentation follows.
//
// LeafContent represents content items that can't have child-nodes.
type InnerQueryFragmentRandomLeafLeafContent interface {
implementsGraphQLInterfaceInnerQueryFragmentRandomLeafLeafContent()
// GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values).
GetTypename() string
}
func (v *InnerQueryFragmentRandomLeafArticle) implementsGraphQLInterfaceInnerQueryFragmentRandomLeafLeafContent() {
}
// GetTypename is a part of, and documented with, the interface InnerQueryFragmentRandomLeafLeafContent.
func (v *InnerQueryFragmentRandomLeafArticle) GetTypename() string { return v.Typename }
func (v *InnerQueryFragmentRandomLeafVideo) implementsGraphQLInterfaceInnerQueryFragmentRandomLeafLeafContent() {
}
// GetTypename is a part of, and documented with, the interface InnerQueryFragmentRandomLeafLeafContent.
func (v *InnerQueryFragmentRandomLeafVideo) GetTypename() string { return v.Typename }
func __unmarshalInnerQueryFragmentRandomLeafLeafContent(v *InnerQueryFragmentRandomLeafLeafContent, 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(InnerQueryFragmentRandomLeafArticle)
return json.Unmarshal(m, *v)
case "Video":
*v = new(InnerQueryFragmentRandomLeafVideo)
return json.Unmarshal(m, *v)
case "":
return fmt.Errorf(
"Response was missing LeafContent.__typename")
default:
return fmt.Errorf(
`Unexpected concrete type for InnerQueryFragmentRandomLeafLeafContent: "%v"`, tn.TypeName)
}
}
// InnerQueryFragmentRandomLeafVideo includes the requested fields of the GraphQL type Video.
type InnerQueryFragmentRandomLeafVideo struct {
Typename string `json:"__typename"`
VideoFields `json:"-"`
MoreVideoFields `json:"-"`
}
func (v *InnerQueryFragmentRandomLeafVideo) UnmarshalJSON(b []byte) error {
var firstPass struct {
*InnerQueryFragmentRandomLeafVideo
graphql.NoUnmarshalJSON
}
firstPass.InnerQueryFragmentRandomLeafVideo = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
err = json.Unmarshal(b, &v.VideoFields)
if err != nil {
return err
}
err = json.Unmarshal(b, &v.MoreVideoFields)
if err != nil {
return err
}
return nil
}
// MoreVideoFields includes the GraphQL fields of Video requested by the fragment MoreVideoFields.
type MoreVideoFields struct {
// ID is documented in the Content interface.
Id *testutil.ID `json:"id"`
Parent *MoreVideoFieldsParentTopic `json:"parent"`
}
// MoreVideoFieldsParentTopic includes the requested fields of the GraphQL type Topic.
type MoreVideoFieldsParentTopic struct {
Name *string `json:"name"`
Url *string `json:"url"`
Children []MoreVideoFieldsParentTopicChildrenContent `json:"-"`
}
func (v *MoreVideoFieldsParentTopic) UnmarshalJSON(b []byte) error {
var firstPass struct {
*MoreVideoFieldsParentTopic
Children []json.RawMessage `json:"children"`
graphql.NoUnmarshalJSON
}
firstPass.MoreVideoFieldsParentTopic = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
{
target := &v.Children
raw := firstPass.Children
*target = make(
[]MoreVideoFieldsParentTopicChildrenContent,
len(raw))
for i, raw := range raw {
target := &(*target)[i]
err = __unmarshalMoreVideoFieldsParentTopicChildrenContent(
target, raw)
if err != nil {
return fmt.Errorf(
"Unable to unmarshal MoreVideoFieldsParentTopic.Children: %w", err)
}
}
}
return nil
}
// MoreVideoFieldsParentTopicChildrenArticle includes the requested fields of the GraphQL type Article.
type MoreVideoFieldsParentTopicChildrenArticle struct {
Typename *string `json:"__typename"`
}
// MoreVideoFieldsParentTopicChildrenContent includes the requested fields of the GraphQL interface Content.
//
// MoreVideoFieldsParentTopicChildrenContent is implemented by the following types:
// MoreVideoFieldsParentTopicChildrenArticle
// MoreVideoFieldsParentTopicChildrenVideo
// MoreVideoFieldsParentTopicChildrenTopic
//
// The GraphQL type's documentation follows.
//
// Content is implemented by various types like Article, Video, and Topic.
type MoreVideoFieldsParentTopicChildrenContent interface {
implementsGraphQLInterfaceMoreVideoFieldsParentTopicChildrenContent()
// GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values).
GetTypename() *string
}
func (v *MoreVideoFieldsParentTopicChildrenArticle) implementsGraphQLInterfaceMoreVideoFieldsParentTopicChildrenContent() {
}
// GetTypename is a part of, and documented with, the interface MoreVideoFieldsParentTopicChildrenContent.
func (v *MoreVideoFieldsParentTopicChildrenArticle) GetTypename() *string { return v.Typename }
func (v *MoreVideoFieldsParentTopicChildrenVideo) implementsGraphQLInterfaceMoreVideoFieldsParentTopicChildrenContent() {
}
// GetTypename is a part of, and documented with, the interface MoreVideoFieldsParentTopicChildrenContent.
func (v *MoreVideoFieldsParentTopicChildrenVideo) GetTypename() *string { return v.Typename }
func (v *MoreVideoFieldsParentTopicChildrenTopic) implementsGraphQLInterfaceMoreVideoFieldsParentTopicChildrenContent() {
}
// GetTypename is a part of, and documented with, the interface MoreVideoFieldsParentTopicChildrenContent.
func (v *MoreVideoFieldsParentTopicChildrenTopic) GetTypename() *string { return v.Typename }
func __unmarshalMoreVideoFieldsParentTopicChildrenContent(v *MoreVideoFieldsParentTopicChildrenContent, 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(MoreVideoFieldsParentTopicChildrenArticle)
return json.Unmarshal(m, *v)
case "Video":
*v = new(MoreVideoFieldsParentTopicChildrenVideo)
return json.Unmarshal(m, *v)
case "Topic":
*v = new(MoreVideoFieldsParentTopicChildrenTopic)
return json.Unmarshal(m, *v)
case "":
return fmt.Errorf(
"Response was missing Content.__typename")
default:
return fmt.Errorf(
`Unexpected concrete type for MoreVideoFieldsParentTopicChildrenContent: "%v"`, tn.TypeName)
}
}
// MoreVideoFieldsParentTopicChildrenTopic includes the requested fields of the GraphQL type Topic.
type MoreVideoFieldsParentTopicChildrenTopic struct {
Typename *string `json:"__typename"`
}
// MoreVideoFieldsParentTopicChildrenVideo includes the requested fields of the GraphQL type Video.
type MoreVideoFieldsParentTopicChildrenVideo struct {
Typename *string `json:"__typename"`
VideoFields `json:"-"`
}
func (v *MoreVideoFieldsParentTopicChildrenVideo) UnmarshalJSON(b []byte) error {
var firstPass struct {
*MoreVideoFieldsParentTopicChildrenVideo
graphql.NoUnmarshalJSON
}
firstPass.MoreVideoFieldsParentTopicChildrenVideo = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
err = json.Unmarshal(b, &v.VideoFields)
if err != nil {
return err
}
return nil
}
// QueryFragment includes the GraphQL fields of Query requested by the fragment QueryFragment.
type QueryFragment struct {
InnerQueryFragment `json:"-"`
}
func (v *QueryFragment) UnmarshalJSON(b []byte) error {
var firstPass struct {
*QueryFragment
graphql.NoUnmarshalJSON
}
firstPass.QueryFragment = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
err = json.Unmarshal(b, &v.InnerQueryFragment)
if err != nil {
return err
}
return nil
}
// VideoFields includes the GraphQL fields of Video requested by the fragment VideoFields.
type VideoFields struct {
// ID is documented in the Content interface.
Id testutil.ID `json:"id"`
Name string `json:"name"`
Url string `json:"url"`
Duration int `json:"duration"`
Thumbnail VideoFieldsThumbnail `json:"thumbnail"`
}
// VideoFieldsThumbnail includes the requested fields of the GraphQL type Thumbnail.
type VideoFieldsThumbnail struct {
Id testutil.ID `json:"id"`
}
func ComplexNamedFragments(
client graphql.Client,
) (*ComplexNamedFragmentsResponse, error) {
var err error
var retval ComplexNamedFragmentsResponse
err = client.MakeRequest(
nil,
"ComplexNamedFragments",
`
query ComplexNamedFragments {
... on Query {
... QueryFragment
}
}
fragment QueryFragment on Query {
... InnerQueryFragment
}
fragment InnerQueryFragment on Query {
randomItem {
__typename
id
name
... VideoFields
}
randomLeaf {
__typename
... VideoFields
... MoreVideoFields
}
otherLeaf: randomLeaf {
__typename
... on Video {
... MoreVideoFields
}
}
}
fragment VideoFields on Video {
id
name
url
duration
thumbnail {
id
}
}
fragment MoreVideoFields on Video {
id
parent {
name
url
children {
__typename
... VideoFields
}
}
}
`,
&retval,
nil,
)
return &retval, err
}
@@ -0,0 +1,9 @@
{
"operations": [
{
"operationName": "ComplexNamedFragments",
"query": "\nquery ComplexNamedFragments {\n\t... on Query {\n\t\t... QueryFragment\n\t}\n}\nfragment QueryFragment on Query {\n\t... InnerQueryFragment\n}\nfragment InnerQueryFragment on Query {\n\trandomItem {\n\t\t__typename\n\t\tid\n\t\tname\n\t\t... VideoFields\n\t}\n\trandomLeaf {\n\t\t__typename\n\t\t... VideoFields\n\t\t... MoreVideoFields\n\t}\n\totherLeaf: randomLeaf {\n\t\t__typename\n\t\t... on Video {\n\t\t\t... MoreVideoFields\n\t\t}\n\t}\n}\nfragment VideoFields on Video {\n\tid\n\tname\n\turl\n\tduration\n\tthumbnail {\n\t\tid\n\t}\n}\nfragment MoreVideoFields on Video {\n\tid\n\tparent {\n\t\tname\n\t\turl\n\t\tchildren {\n\t\t\t__typename\n\t\t\t... VideoFields\n\t\t}\n\t}\n}\n",
"sourceLocation": "testdata/queries/ComplexNamedFragments.graphql"
}
]
}
@@ -26,13 +26,12 @@ type InterfaceListFieldRootTopic struct {
func (v *InterfaceListFieldRootTopic) UnmarshalJSON(b []byte) error {
type InterfaceListFieldRootTopicWrapper InterfaceListFieldRootTopic
var firstPass struct {
*InterfaceListFieldRootTopicWrapper
*InterfaceListFieldRootTopic
Children []json.RawMessage `json:"children"`
graphql.NoUnmarshalJSON
}
firstPass.InterfaceListFieldRootTopicWrapper = (*InterfaceListFieldRootTopicWrapper)(v)
firstPass.InterfaceListFieldRootTopic = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
@@ -55,6 +54,7 @@ func (v *InterfaceListFieldRootTopic) UnmarshalJSON(b []byte) error {
}
}
}
return nil
}
@@ -183,13 +183,12 @@ type InterfaceListFieldWithPointerTopic struct {
func (v *InterfaceListFieldWithPointerTopic) UnmarshalJSON(b []byte) error {
type InterfaceListFieldWithPointerTopicWrapper InterfaceListFieldWithPointerTopic
var firstPass struct {
*InterfaceListFieldWithPointerTopicWrapper
*InterfaceListFieldWithPointerTopic
Children []json.RawMessage `json:"children"`
graphql.NoUnmarshalJSON
}
firstPass.InterfaceListFieldWithPointerTopicWrapper = (*InterfaceListFieldWithPointerTopicWrapper)(v)
firstPass.InterfaceListFieldWithPointerTopic = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
@@ -212,6 +211,7 @@ func (v *InterfaceListFieldWithPointerTopic) UnmarshalJSON(b []byte) error {
}
}
}
return nil
}
@@ -151,14 +151,13 @@ type InterfaceListOfListOfListsFieldResponse struct {
func (v *InterfaceListOfListOfListsFieldResponse) UnmarshalJSON(b []byte) error {
type InterfaceListOfListOfListsFieldResponseWrapper InterfaceListOfListOfListsFieldResponse
var firstPass struct {
*InterfaceListOfListOfListsFieldResponseWrapper
*InterfaceListOfListOfListsFieldResponse
ListOfListsOfListsOfContent [][][]json.RawMessage `json:"listOfListsOfListsOfContent"`
WithPointer [][][]json.RawMessage `json:"withPointer"`
graphql.NoUnmarshalJSON
}
firstPass.InterfaceListOfListOfListsFieldResponseWrapper = (*InterfaceListOfListOfListsFieldResponseWrapper)(v)
firstPass.InterfaceListOfListOfListsFieldResponse = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
@@ -193,6 +192,7 @@ func (v *InterfaceListOfListOfListsFieldResponse) UnmarshalJSON(b []byte) error
}
}
}
{
target := &v.WithPointer
raw := firstPass.WithPointer
@@ -222,6 +222,7 @@ func (v *InterfaceListOfListOfListsFieldResponse) UnmarshalJSON(b []byte) error
}
}
}
return nil
}
@@ -24,13 +24,12 @@ type InterfaceNestingRootTopic struct {
func (v *InterfaceNestingRootTopic) UnmarshalJSON(b []byte) error {
type InterfaceNestingRootTopicWrapper InterfaceNestingRootTopic
var firstPass struct {
*InterfaceNestingRootTopicWrapper
*InterfaceNestingRootTopic
Children []json.RawMessage `json:"children"`
graphql.NoUnmarshalJSON
}
firstPass.InterfaceNestingRootTopicWrapper = (*InterfaceNestingRootTopicWrapper)(v)
firstPass.InterfaceNestingRootTopic = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
@@ -53,6 +52,7 @@ func (v *InterfaceNestingRootTopic) UnmarshalJSON(b []byte) error {
}
}
}
return nil
}
@@ -170,13 +170,12 @@ type InterfaceNestingRootTopicChildrenContentParentTopic struct {
func (v *InterfaceNestingRootTopicChildrenContentParentTopic) UnmarshalJSON(b []byte) error {
type InterfaceNestingRootTopicChildrenContentParentTopicWrapper InterfaceNestingRootTopicChildrenContentParentTopic
var firstPass struct {
*InterfaceNestingRootTopicChildrenContentParentTopicWrapper
*InterfaceNestingRootTopicChildrenContentParentTopic
Children []json.RawMessage `json:"children"`
graphql.NoUnmarshalJSON
}
firstPass.InterfaceNestingRootTopicChildrenContentParentTopicWrapper = (*InterfaceNestingRootTopicChildrenContentParentTopicWrapper)(v)
firstPass.InterfaceNestingRootTopicChildrenContentParentTopic = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
@@ -199,6 +198,7 @@ func (v *InterfaceNestingRootTopicChildrenContentParentTopic) UnmarshalJSON(b []
}
}
}
return nil
}
@@ -256,15 +256,14 @@ type InterfaceNoFragmentsQueryResponse struct {
func (v *InterfaceNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
type InterfaceNoFragmentsQueryResponseWrapper InterfaceNoFragmentsQueryResponse
var firstPass struct {
*InterfaceNoFragmentsQueryResponseWrapper
*InterfaceNoFragmentsQueryResponse
RandomItem json.RawMessage `json:"randomItem"`
RandomItemWithTypeName json.RawMessage `json:"randomItemWithTypeName"`
WithPointer json.RawMessage `json:"withPointer"`
graphql.NoUnmarshalJSON
}
firstPass.InterfaceNoFragmentsQueryResponseWrapper = (*InterfaceNoFragmentsQueryResponseWrapper)(v)
firstPass.InterfaceNoFragmentsQueryResponse = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
@@ -281,6 +280,7 @@ func (v *InterfaceNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
"Unable to unmarshal InterfaceNoFragmentsQueryResponse.RandomItem: %w", err)
}
}
{
target := &v.RandomItemWithTypeName
raw := firstPass.RandomItemWithTypeName
@@ -291,6 +291,7 @@ func (v *InterfaceNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
"Unable to unmarshal InterfaceNoFragmentsQueryResponse.RandomItemWithTypeName: %w", err)
}
}
{
target := &v.WithPointer
raw := firstPass.WithPointer
@@ -302,6 +303,7 @@ func (v *InterfaceNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
"Unable to unmarshal InterfaceNoFragmentsQueryResponse.WithPointer: %w", err)
}
}
return nil
}
@@ -134,13 +134,12 @@ type SimpleInlineFragmentResponse struct {
func (v *SimpleInlineFragmentResponse) UnmarshalJSON(b []byte) error {
type SimpleInlineFragmentResponseWrapper SimpleInlineFragmentResponse
var firstPass struct {
*SimpleInlineFragmentResponseWrapper
*SimpleInlineFragmentResponse
RandomItem json.RawMessage `json:"randomItem"`
graphql.NoUnmarshalJSON
}
firstPass.SimpleInlineFragmentResponseWrapper = (*SimpleInlineFragmentResponseWrapper)(v)
firstPass.SimpleInlineFragmentResponse = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
@@ -157,6 +156,7 @@ func (v *SimpleInlineFragmentResponse) UnmarshalJSON(b []byte) error {
"Unable to unmarshal SimpleInlineFragmentResponse.RandomItem: %w", err)
}
}
return nil
}
@@ -0,0 +1,334 @@
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"
)
// SimpleNamedFragmentRandomItemArticle includes the requested fields of the GraphQL type Article.
type SimpleNamedFragmentRandomItemArticle struct {
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id testutil.ID `json:"id"`
Name string `json:"name"`
}
// SimpleNamedFragmentRandomItemContent includes the requested fields of the GraphQL interface Content.
//
// SimpleNamedFragmentRandomItemContent is implemented by the following types:
// SimpleNamedFragmentRandomItemArticle
// SimpleNamedFragmentRandomItemVideo
// SimpleNamedFragmentRandomItemTopic
//
// The GraphQL type's documentation follows.
//
// Content is implemented by various types like Article, Video, and Topic.
type SimpleNamedFragmentRandomItemContent interface {
implementsGraphQLInterfaceSimpleNamedFragmentRandomItemContent()
// 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 *SimpleNamedFragmentRandomItemArticle) implementsGraphQLInterfaceSimpleNamedFragmentRandomItemContent() {
}
// GetTypename is a part of, and documented with, the interface SimpleNamedFragmentRandomItemContent.
func (v *SimpleNamedFragmentRandomItemArticle) GetTypename() string { return v.Typename }
// GetId is a part of, and documented with, the interface SimpleNamedFragmentRandomItemContent.
func (v *SimpleNamedFragmentRandomItemArticle) GetId() testutil.ID { return v.Id }
// GetName is a part of, and documented with, the interface SimpleNamedFragmentRandomItemContent.
func (v *SimpleNamedFragmentRandomItemArticle) GetName() string { return v.Name }
func (v *SimpleNamedFragmentRandomItemVideo) implementsGraphQLInterfaceSimpleNamedFragmentRandomItemContent() {
}
// GetTypename is a part of, and documented with, the interface SimpleNamedFragmentRandomItemContent.
func (v *SimpleNamedFragmentRandomItemVideo) GetTypename() string { return v.Typename }
// GetId is a part of, and documented with, the interface SimpleNamedFragmentRandomItemContent.
func (v *SimpleNamedFragmentRandomItemVideo) GetId() testutil.ID { return v.Id }
// GetName is a part of, and documented with, the interface SimpleNamedFragmentRandomItemContent.
func (v *SimpleNamedFragmentRandomItemVideo) GetName() string { return v.Name }
func (v *SimpleNamedFragmentRandomItemTopic) implementsGraphQLInterfaceSimpleNamedFragmentRandomItemContent() {
}
// GetTypename is a part of, and documented with, the interface SimpleNamedFragmentRandomItemContent.
func (v *SimpleNamedFragmentRandomItemTopic) GetTypename() string { return v.Typename }
// GetId is a part of, and documented with, the interface SimpleNamedFragmentRandomItemContent.
func (v *SimpleNamedFragmentRandomItemTopic) GetId() testutil.ID { return v.Id }
// GetName is a part of, and documented with, the interface SimpleNamedFragmentRandomItemContent.
func (v *SimpleNamedFragmentRandomItemTopic) GetName() string { return v.Name }
func __unmarshalSimpleNamedFragmentRandomItemContent(v *SimpleNamedFragmentRandomItemContent, 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(SimpleNamedFragmentRandomItemArticle)
return json.Unmarshal(m, *v)
case "Video":
*v = new(SimpleNamedFragmentRandomItemVideo)
return json.Unmarshal(m, *v)
case "Topic":
*v = new(SimpleNamedFragmentRandomItemTopic)
return json.Unmarshal(m, *v)
case "":
return fmt.Errorf(
"Response was missing Content.__typename")
default:
return fmt.Errorf(
`Unexpected concrete type for SimpleNamedFragmentRandomItemContent: "%v"`, tn.TypeName)
}
}
// SimpleNamedFragmentRandomItemTopic includes the requested fields of the GraphQL type Topic.
type SimpleNamedFragmentRandomItemTopic struct {
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id testutil.ID `json:"id"`
Name string `json:"name"`
}
// SimpleNamedFragmentRandomItemVideo includes the requested fields of the GraphQL type Video.
type SimpleNamedFragmentRandomItemVideo struct {
Typename string `json:"__typename"`
// ID is the identifier of the content.
Id testutil.ID `json:"id"`
Name string `json:"name"`
VideoFields `json:"-"`
}
func (v *SimpleNamedFragmentRandomItemVideo) UnmarshalJSON(b []byte) error {
var firstPass struct {
*SimpleNamedFragmentRandomItemVideo
graphql.NoUnmarshalJSON
}
firstPass.SimpleNamedFragmentRandomItemVideo = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
err = json.Unmarshal(b, &v.VideoFields)
if err != nil {
return err
}
return nil
}
// SimpleNamedFragmentRandomLeafArticle includes the requested fields of the GraphQL type Article.
type SimpleNamedFragmentRandomLeafArticle struct {
Typename string `json:"__typename"`
}
// SimpleNamedFragmentRandomLeafLeafContent includes the requested fields of the GraphQL interface LeafContent.
//
// SimpleNamedFragmentRandomLeafLeafContent is implemented by the following types:
// SimpleNamedFragmentRandomLeafArticle
// SimpleNamedFragmentRandomLeafVideo
//
// The GraphQL type's documentation follows.
//
// LeafContent represents content items that can't have child-nodes.
type SimpleNamedFragmentRandomLeafLeafContent interface {
implementsGraphQLInterfaceSimpleNamedFragmentRandomLeafLeafContent()
// GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values).
GetTypename() string
}
func (v *SimpleNamedFragmentRandomLeafArticle) implementsGraphQLInterfaceSimpleNamedFragmentRandomLeafLeafContent() {
}
// GetTypename is a part of, and documented with, the interface SimpleNamedFragmentRandomLeafLeafContent.
func (v *SimpleNamedFragmentRandomLeafArticle) GetTypename() string { return v.Typename }
func (v *SimpleNamedFragmentRandomLeafVideo) implementsGraphQLInterfaceSimpleNamedFragmentRandomLeafLeafContent() {
}
// GetTypename is a part of, and documented with, the interface SimpleNamedFragmentRandomLeafLeafContent.
func (v *SimpleNamedFragmentRandomLeafVideo) GetTypename() string { return v.Typename }
func __unmarshalSimpleNamedFragmentRandomLeafLeafContent(v *SimpleNamedFragmentRandomLeafLeafContent, 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(SimpleNamedFragmentRandomLeafArticle)
return json.Unmarshal(m, *v)
case "Video":
*v = new(SimpleNamedFragmentRandomLeafVideo)
return json.Unmarshal(m, *v)
case "":
return fmt.Errorf(
"Response was missing LeafContent.__typename")
default:
return fmt.Errorf(
`Unexpected concrete type for SimpleNamedFragmentRandomLeafLeafContent: "%v"`, tn.TypeName)
}
}
// SimpleNamedFragmentRandomLeafVideo includes the requested fields of the GraphQL type Video.
type SimpleNamedFragmentRandomLeafVideo struct {
Typename string `json:"__typename"`
VideoFields `json:"-"`
}
func (v *SimpleNamedFragmentRandomLeafVideo) UnmarshalJSON(b []byte) error {
var firstPass struct {
*SimpleNamedFragmentRandomLeafVideo
graphql.NoUnmarshalJSON
}
firstPass.SimpleNamedFragmentRandomLeafVideo = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
err = json.Unmarshal(b, &v.VideoFields)
if err != nil {
return err
}
return nil
}
// SimpleNamedFragmentResponse is returned by SimpleNamedFragment on success.
type SimpleNamedFragmentResponse struct {
RandomItem SimpleNamedFragmentRandomItemContent `json:"-"`
RandomLeaf SimpleNamedFragmentRandomLeafLeafContent `json:"-"`
}
func (v *SimpleNamedFragmentResponse) UnmarshalJSON(b []byte) error {
var firstPass struct {
*SimpleNamedFragmentResponse
RandomItem json.RawMessage `json:"randomItem"`
RandomLeaf json.RawMessage `json:"randomLeaf"`
graphql.NoUnmarshalJSON
}
firstPass.SimpleNamedFragmentResponse = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
{
target := &v.RandomItem
raw := firstPass.RandomItem
err = __unmarshalSimpleNamedFragmentRandomItemContent(
target, raw)
if err != nil {
return fmt.Errorf(
"Unable to unmarshal SimpleNamedFragmentResponse.RandomItem: %w", err)
}
}
{
target := &v.RandomLeaf
raw := firstPass.RandomLeaf
err = __unmarshalSimpleNamedFragmentRandomLeafLeafContent(
target, raw)
if err != nil {
return fmt.Errorf(
"Unable to unmarshal SimpleNamedFragmentResponse.RandomLeaf: %w", err)
}
}
return nil
}
// VideoFields includes the GraphQL fields of Video requested by the fragment VideoFields.
type VideoFields struct {
// ID is documented in the Content interface.
Id testutil.ID `json:"id"`
Name string `json:"name"`
Url string `json:"url"`
Duration int `json:"duration"`
Thumbnail VideoFieldsThumbnail `json:"thumbnail"`
}
// VideoFieldsThumbnail includes the requested fields of the GraphQL type Thumbnail.
type VideoFieldsThumbnail struct {
Id testutil.ID `json:"id"`
}
func SimpleNamedFragment(
client graphql.Client,
) (*SimpleNamedFragmentResponse, error) {
var err error
var retval SimpleNamedFragmentResponse
err = client.MakeRequest(
nil,
"SimpleNamedFragment",
`
query SimpleNamedFragment {
randomItem {
__typename
id
name
... VideoFields
}
randomLeaf {
__typename
... VideoFields
}
}
fragment VideoFields on Video {
id
name
url
duration
thumbnail {
id
}
}
`,
&retval,
nil,
)
return &retval, err
}
@@ -0,0 +1,9 @@
{
"operations": [
{
"operationName": "SimpleNamedFragment",
"query": "\nquery SimpleNamedFragment {\n\trandomItem {\n\t\t__typename\n\t\tid\n\t\tname\n\t\t... VideoFields\n\t}\n\trandomLeaf {\n\t\t__typename\n\t\t... VideoFields\n\t}\n}\nfragment VideoFields on Video {\n\tid\n\tname\n\turl\n\tduration\n\tthumbnail {\n\t\tid\n\t}\n}\n",
"sourceLocation": "testdata/queries/SimpleNamedFragment.graphql"
}
]
}
@@ -82,13 +82,12 @@ type UnionNoFragmentsQueryResponse struct {
func (v *UnionNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
type UnionNoFragmentsQueryResponseWrapper UnionNoFragmentsQueryResponse
var firstPass struct {
*UnionNoFragmentsQueryResponseWrapper
*UnionNoFragmentsQueryResponse
RandomLeaf json.RawMessage `json:"randomLeaf"`
graphql.NoUnmarshalJSON
}
firstPass.UnionNoFragmentsQueryResponseWrapper = (*UnionNoFragmentsQueryResponseWrapper)(v)
firstPass.UnionNoFragmentsQueryResponse = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
@@ -105,6 +104,7 @@ func (v *UnionNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
"Unable to unmarshal UnionNoFragmentsQueryResponse.RandomLeaf: %w", err)
}
}
return nil
}
+41 -26
View File
@@ -119,11 +119,19 @@ type goStructField struct {
Description string
}
func isAbstract(typ goType) bool {
_, ok := typ.Unwrap().(*goInterfaceType)
// IsAbstract returns true if this field is of abstract type (i.e. GraphQL
// union or interface; equivalently, represented by an interface in Go).
func (field *goStructField) IsAbstract() bool {
_, ok := field.GoType.Unwrap().(*goInterfaceType)
return ok
}
// IsEmbedded returns true if this field is embedded (a.k.a. anonymous), which
// is in practice true if it corresponds to a named fragment spread in GraphQL.
func (field *goStructField) IsEmbedded() bool {
return field.GoName == ""
}
func (typ *goStructType) WriteDefinition(w io.Writer, g *generator) error {
description := typ.Description
if typ.Incomplete {
@@ -144,28 +152,47 @@ func (typ *goStructType) WriteDefinition(w io.Writer, g *generator) error {
}
writeDescription(w, description)
needUnmarshaler := false
fmt.Fprintf(w, "type %s struct {\n", typ.GoName)
for _, field := range typ.Fields {
writeDescription(w, field.Description)
jsonName := field.JSONName
if isAbstract(field.GoType) {
// abstract types are handled in our UnmarshalJSON
if field.IsAbstract() {
// abstract types are handled in our UnmarshalJSON (see below)
needUnmarshaler = true
jsonName = "-"
}
fmt.Fprintf(w, "\t%s %s `json:\"%s\"`\n",
field.GoName, field.GoType.Reference(), jsonName)
if field.IsEmbedded() {
// embedded fields also need UnmarshalJSON handling (see below)
needUnmarshaler = true
fmt.Fprintf(w, "\t%s `json:\"-\"`\n", field.GoType.Unwrap().Reference())
} else {
fmt.Fprintf(w, "\t%s %s `json:\"%s\"`\n",
field.GoName, field.GoType.Reference(), jsonName)
}
}
fmt.Fprintf(w, "}\n")
// Now, if needed, write the unmarshaler.
// Now, if needed, write the unmarshaler. We need one if we have any
// interface-typed fields, or any embedded fields.
//
// Specifically, in order to unmarshal interface values, we need to add an
// UnmarshalJSON method to each type which has an interface-typed *field*
// (not the interface type itself -- we can't add methods to that).
// But we put most of the logic in a per-interface-type helper function,
// written along with the interface type; the UnmarshalJSON method is just
// the boilerplate.
if len(typ.AbstractFields()) == 0 {
// For interface-typed fields, ideally we'd write an UnmarshalJSON method
// on the field, but you can't add a method to an interface. So we write a
// per-interface-type helper, but we have to call it (with a little
// boilerplate) everywhere the type is referenced.
//
// For embedded fields (from fragments), mostly the JSON library would just
// do what we want, but there are two problems. First, if the embedded
// type has its own UnmarshalJSON, naively that would be promoted to
// become our UnmarshalJSON, which is no good. But we don't want to just
// hide that method and inline its fields, either; we need to call its
// UnmarshalJSON (on the same object we unmarshal into this struct).
// Second, if the embedded type duplicates any fields of the embedding type
// -- maybe both the fragment and the selection into which it's spread
// select the same field, or several fragments select the same field -- the
// JSON library will only fill one of those (the least-nested one); we want
// to fill them all.
if !needUnmarshaler {
return nil
}
@@ -181,18 +208,6 @@ func (typ *goStructType) WriteDefinition(w io.Writer, g *generator) error {
func (typ *goStructType) Reference() string { return typ.GoName }
// AbstractFields returns all the fields which are abstract types (i.e. GraphQL
// unions and interfaces; equivalently, types represented by interfaces in Go).
func (typ *goStructType) AbstractFields() []*goStructField {
var ret []*goStructField
for _, field := range typ.Fields {
if isAbstract(field.GoType) {
ret = append(ret, field)
}
}
return ret
}
// goInterfaceType represents a Go interface type, used to represent a GraphQL
// interface or union type.
type goInterfaceType struct {
+37 -15
View File
@@ -2,36 +2,49 @@
UnmarshalJSON from the function it follows) */}}
func (v *{{.GoName}}) UnmarshalJSON(b []byte) error {
{{/* We want to specially handle the abstract fields (.AbstractFields),
but unmarshal everything else normally. To handle abstract fields,
{{/* We want to specially handle the abstract or embedded fields, but
unmarshal everything else normally. To handle abstract fields,
first we unmarshal them into a json.RawMessage, and then handle those
further, below. For the rest, we just want to call json.Unmarshal.
further, below. Embedded fields we just unmarshal directly into the
embedded value. For the rest, we just want to call json.Unmarshal.
But if we do that naively on a value of type `.Type`, it will call
this function again, and recurse infinitely. So we make a wrapper
type -- with a different name, thus different methods, but the same
fields, and unmarshal into that. For more on why this is so
difficult, see
https://github.com/benjaminjkraft/notes/blob/master/go-json-interfaces.md
type which embeds both this type and NoUmnarshalJSON, which prevents
either's UnmarshalJSON method from being promoted. For more on why
this is so difficult, see
https://github.com/benjaminjkraft/notes/blob/master/go-json-interfaces.md.
(Note there are a few different ways "hide" the method, but this one
seems to be the best option that works if this type has embedded types
with UnmarshalJSON methods.)
TODO(benkraft)): Ensure `{{.Type}}Wrapper` won't collide with any
other type we need. (For the most part it being locally-scoped saves
us; it's not clear if this can be a problem in practice.)
*/}}
type {{.GoName}}Wrapper {{.GoName}}
{{/* TODO(benkraft): Omit/simplify the first pass if all fields are
embedded/abstract. */ -}}
var firstPass struct{
*{{.GoName}}Wrapper
{{range .AbstractFields -}}
*{{.GoName}}
{{range .Fields -}}
{{if .IsAbstract -}}
{{.GoName}} {{repeat .GoType.SliceDepth "[]"}}{{ref "encoding/json.RawMessage"}} `json:"{{.JSONName}}"`
{{end}}
{{end -}}
{{end -}}
{{/* TODO(benkraft): In principle you might have a field-name that
conflicts with this one; avoid that. */ -}}
{{ref "github.com/Khan/genqlient/graphql.NoUnmarshalJSON"}}
}
firstPass.{{.GoName}}Wrapper = (*{{.GoName}}Wrapper)(v)
firstPass.{{.GoName}} = v
err := {{ref "encoding/json.Unmarshal"}}(b, &firstPass)
if err != nil {
return err
}
{{/* Now, for each field, call out to the unmarshal-helper.
{{/* Now, handle the fields needing special handling. */}}
{{range $field := .Fields -}}
{{if $field.IsAbstract -}}
{{/* First, for abstract fields, call the unmarshal-helper.
This gets a little complicated because we may have a slice field.
So what we do is basically, for each field of type `[][]...[]MyType`:
@@ -65,7 +78,6 @@ func (v *{{.GoName}}) UnmarshalJSON(b []byte) error {
each field we are handling, which would otherwise conflict. (We could
instead suffix the names, but that makes things much harder to read.)
*/}}
{{range $field := .AbstractFields -}}
{
target := &v.{{$field.GoName}}
raw := firstPass.{{$field.GoName}}
@@ -92,7 +104,17 @@ func (v *{{.GoName}}) UnmarshalJSON(b []byte) error {
}
{{end -}}
}
{{end -}}
{{end -}}{{/* end if .IsAbstract */}}
{{if $field.IsEmbedded -}}
{{/* Embedded fields are easier: we just unmarshal the same input into
them. (They're also easier because they can't be lists, since they
arise from GraphQL fragment spreads.) */}}
err = json.Unmarshal(b, &v.{{$field.GoType.Unwrap.Reference}})
if err != nil {
return err
}
{{end}}{{/* end if .IsEmbedded */ -}}
{{end}}{{/* end range .Fields */ -}}
return nil
}
+2
View File
@@ -10,6 +10,7 @@ github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/bradleyjkemp/cupaloy/v2 v2.6.0 h1:knToPYa2xtfg42U3I6punFEjaGFKWQRXJwj0JTv4mTs=
github.com/bradleyjkemp/cupaloy/v2 v2.6.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -39,6 +40,7 @@ github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047 h1:zCoDWFD5
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+19
View File
@@ -0,0 +1,19 @@
package graphql
// Utility types used by the generated code. In general, these are *not*
// intended for end-users.
// NoUnmarshalJSON is intended for the use of genqlient's generated code only.
//
// It is used to prevent a struct type from inheriting its embed's
// UnmarshalJSON method: given a type
// type T struct { E; NoUnmarshalJSON }
// where E has an UnmarshalJSON method, T will not inherit it, per the Go
// selector rules: https://golang.org/ref/spec#Selectors.
type NoUnmarshalJSON struct{}
// UnmarshalJSON should never be called; it exists only to prevent a sibling
// UnmarshalJSON method from being promoted.
func (NoUnmarshalJSON) UnmarshalJSON(b []byte) error {
panic("NoUnmarshalJSON.UnmarshalJSON should never be called!")
}
+403 -20
View File
@@ -10,6 +10,152 @@ import (
"github.com/Khan/genqlient/graphql"
)
// AnimalFields includes the GraphQL fields of Animal requested by the fragment AnimalFields.
type AnimalFields struct {
Id string `json:"id"`
Hair AnimalFieldsHairBeingsHair `json:"hair"`
Owner AnimalFieldsOwnerBeing `json:"-"`
}
func (v *AnimalFields) UnmarshalJSON(b []byte) error {
var firstPass struct {
*AnimalFields
Owner json.RawMessage `json:"owner"`
graphql.NoUnmarshalJSON
}
firstPass.AnimalFields = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
{
target := &v.Owner
raw := firstPass.Owner
err = __unmarshalAnimalFieldsOwnerBeing(
target, raw)
if err != nil {
return fmt.Errorf(
"Unable to unmarshal AnimalFields.Owner: %w", err)
}
}
return nil
}
// AnimalFieldsHairBeingsHair includes the requested fields of the GraphQL type BeingsHair.
type AnimalFieldsHairBeingsHair struct {
HasHair bool `json:"hasHair"`
}
// AnimalFieldsOwnerAnimal includes the requested fields of the GraphQL type Animal.
type AnimalFieldsOwnerAnimal struct {
Typename string `json:"__typename"`
Id string `json:"id"`
}
// AnimalFieldsOwnerBeing includes the requested fields of the GraphQL interface Being.
//
// AnimalFieldsOwnerBeing is implemented by the following types:
// AnimalFieldsOwnerUser
// AnimalFieldsOwnerAnimal
//
// The GraphQL type's documentation follows.
//
//
type AnimalFieldsOwnerBeing interface {
implementsGraphQLInterfaceAnimalFieldsOwnerBeing()
// 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
}
func (v *AnimalFieldsOwnerUser) implementsGraphQLInterfaceAnimalFieldsOwnerBeing() {}
// GetTypename is a part of, and documented with, the interface AnimalFieldsOwnerBeing.
func (v *AnimalFieldsOwnerUser) GetTypename() string { return v.Typename }
// GetId is a part of, and documented with, the interface AnimalFieldsOwnerBeing.
func (v *AnimalFieldsOwnerUser) GetId() string { return v.Id }
func (v *AnimalFieldsOwnerAnimal) implementsGraphQLInterfaceAnimalFieldsOwnerBeing() {}
// GetTypename is a part of, and documented with, the interface AnimalFieldsOwnerBeing.
func (v *AnimalFieldsOwnerAnimal) GetTypename() string { return v.Typename }
// GetId is a part of, and documented with, the interface AnimalFieldsOwnerBeing.
func (v *AnimalFieldsOwnerAnimal) GetId() string { return v.Id }
func __unmarshalAnimalFieldsOwnerBeing(v *AnimalFieldsOwnerBeing, 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(AnimalFieldsOwnerUser)
return json.Unmarshal(m, *v)
case "Animal":
*v = new(AnimalFieldsOwnerAnimal)
return json.Unmarshal(m, *v)
case "":
return fmt.Errorf(
"Response was missing Being.__typename")
default:
return fmt.Errorf(
`Unexpected concrete type for AnimalFieldsOwnerBeing: "%v"`, tn.TypeName)
}
}
// AnimalFieldsOwnerUser includes the requested fields of the GraphQL type User.
type AnimalFieldsOwnerUser struct {
Typename string `json:"__typename"`
Id string `json:"id"`
UserFields `json:"-"`
}
func (v *AnimalFieldsOwnerUser) UnmarshalJSON(b []byte) error {
var firstPass struct {
*AnimalFieldsOwnerUser
graphql.NoUnmarshalJSON
}
firstPass.AnimalFieldsOwnerUser = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
err = json.Unmarshal(b, &v.UserFields)
if err != nil {
return err
}
return nil
}
// MoreUserFields includes the GraphQL fields of User requested by the fragment MoreUserFields.
type MoreUserFields struct {
Id string `json:"id"`
Hair MoreUserFieldsHair `json:"hair"`
}
// MoreUserFieldsHair includes the requested fields of the GraphQL type Hair.
type MoreUserFieldsHair struct {
Color string `json:"color"`
}
type Species string
const (
@@ -17,6 +163,33 @@ const (
SpeciesCoelacanth Species = "COELACANTH"
)
// UserFields includes the GraphQL fields of User requested by the fragment UserFields.
type UserFields struct {
Id string `json:"id"`
LuckyNumber int `json:"luckyNumber"`
MoreUserFields `json:"-"`
}
func (v *UserFields) UnmarshalJSON(b []byte) error {
var firstPass struct {
*UserFields
graphql.NoUnmarshalJSON
}
firstPass.UserFields = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
err = json.Unmarshal(b, &v.MoreUserFields)
if err != nil {
return err
}
return nil
}
// queryWithFragmentsBeingsAnimal includes the requested fields of the GraphQL type Animal.
type queryWithFragmentsBeingsAnimal struct {
Typename string `json:"__typename"`
@@ -29,13 +202,12 @@ type queryWithFragmentsBeingsAnimal struct {
func (v *queryWithFragmentsBeingsAnimal) UnmarshalJSON(b []byte) error {
type queryWithFragmentsBeingsAnimalWrapper queryWithFragmentsBeingsAnimal
var firstPass struct {
*queryWithFragmentsBeingsAnimalWrapper
*queryWithFragmentsBeingsAnimal
Owner json.RawMessage `json:"owner"`
graphql.NoUnmarshalJSON
}
firstPass.queryWithFragmentsBeingsAnimalWrapper = (*queryWithFragmentsBeingsAnimalWrapper)(v)
firstPass.queryWithFragmentsBeingsAnimal = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
@@ -52,6 +224,7 @@ func (v *queryWithFragmentsBeingsAnimal) UnmarshalJSON(b []byte) error {
"Unable to unmarshal queryWithFragmentsBeingsAnimal.Owner: %w", err)
}
}
return nil
}
@@ -238,13 +411,12 @@ type queryWithFragmentsResponse struct {
func (v *queryWithFragmentsResponse) UnmarshalJSON(b []byte) error {
type queryWithFragmentsResponseWrapper queryWithFragmentsResponse
var firstPass struct {
*queryWithFragmentsResponseWrapper
*queryWithFragmentsResponse
Beings []json.RawMessage `json:"beings"`
graphql.NoUnmarshalJSON
}
firstPass.queryWithFragmentsResponseWrapper = (*queryWithFragmentsResponseWrapper)(v)
firstPass.queryWithFragmentsResponse = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
@@ -267,6 +439,7 @@ func (v *queryWithFragmentsResponse) UnmarshalJSON(b []byte) error {
}
}
}
return nil
}
@@ -363,13 +536,12 @@ type queryWithInterfaceListFieldResponse struct {
func (v *queryWithInterfaceListFieldResponse) UnmarshalJSON(b []byte) error {
type queryWithInterfaceListFieldResponseWrapper queryWithInterfaceListFieldResponse
var firstPass struct {
*queryWithInterfaceListFieldResponseWrapper
*queryWithInterfaceListFieldResponse
Beings []json.RawMessage `json:"beings"`
graphql.NoUnmarshalJSON
}
firstPass.queryWithInterfaceListFieldResponseWrapper = (*queryWithInterfaceListFieldResponseWrapper)(v)
firstPass.queryWithInterfaceListFieldResponse = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
@@ -392,6 +564,7 @@ func (v *queryWithInterfaceListFieldResponse) UnmarshalJSON(b []byte) error {
}
}
}
return nil
}
@@ -488,13 +661,12 @@ type queryWithInterfaceListPointerFieldResponse struct {
func (v *queryWithInterfaceListPointerFieldResponse) UnmarshalJSON(b []byte) error {
type queryWithInterfaceListPointerFieldResponseWrapper queryWithInterfaceListPointerFieldResponse
var firstPass struct {
*queryWithInterfaceListPointerFieldResponseWrapper
*queryWithInterfaceListPointerFieldResponse
Beings []json.RawMessage `json:"beings"`
graphql.NoUnmarshalJSON
}
firstPass.queryWithInterfaceListPointerFieldResponseWrapper = (*queryWithInterfaceListPointerFieldResponseWrapper)(v)
firstPass.queryWithInterfaceListPointerFieldResponse = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
@@ -518,6 +690,7 @@ func (v *queryWithInterfaceListPointerFieldResponse) UnmarshalJSON(b []byte) err
}
}
}
return nil
}
@@ -621,13 +794,12 @@ type queryWithInterfaceNoFragmentsResponse struct {
func (v *queryWithInterfaceNoFragmentsResponse) UnmarshalJSON(b []byte) error {
type queryWithInterfaceNoFragmentsResponseWrapper queryWithInterfaceNoFragmentsResponse
var firstPass struct {
*queryWithInterfaceNoFragmentsResponseWrapper
*queryWithInterfaceNoFragmentsResponse
Being json.RawMessage `json:"being"`
graphql.NoUnmarshalJSON
}
firstPass.queryWithInterfaceNoFragmentsResponseWrapper = (*queryWithInterfaceNoFragmentsResponseWrapper)(v)
firstPass.queryWithInterfaceNoFragmentsResponse = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
@@ -644,6 +816,164 @@ func (v *queryWithInterfaceNoFragmentsResponse) UnmarshalJSON(b []byte) error {
"Unable to unmarshal queryWithInterfaceNoFragmentsResponse.Being: %w", err)
}
}
return nil
}
// queryWithNamedFragmentsBeingsAnimal includes the requested fields of the GraphQL type Animal.
type queryWithNamedFragmentsBeingsAnimal struct {
Typename string `json:"__typename"`
Id string `json:"id"`
AnimalFields `json:"-"`
}
func (v *queryWithNamedFragmentsBeingsAnimal) UnmarshalJSON(b []byte) error {
var firstPass struct {
*queryWithNamedFragmentsBeingsAnimal
graphql.NoUnmarshalJSON
}
firstPass.queryWithNamedFragmentsBeingsAnimal = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
err = json.Unmarshal(b, &v.AnimalFields)
if err != nil {
return err
}
return nil
}
// queryWithNamedFragmentsBeingsBeing includes the requested fields of the GraphQL interface Being.
//
// queryWithNamedFragmentsBeingsBeing is implemented by the following types:
// queryWithNamedFragmentsBeingsUser
// queryWithNamedFragmentsBeingsAnimal
//
// The GraphQL type's documentation follows.
//
//
type queryWithNamedFragmentsBeingsBeing interface {
implementsGraphQLInterfacequeryWithNamedFragmentsBeingsBeing()
// 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
}
func (v *queryWithNamedFragmentsBeingsUser) implementsGraphQLInterfacequeryWithNamedFragmentsBeingsBeing() {
}
// GetTypename is a part of, and documented with, the interface queryWithNamedFragmentsBeingsBeing.
func (v *queryWithNamedFragmentsBeingsUser) GetTypename() string { return v.Typename }
// GetId is a part of, and documented with, the interface queryWithNamedFragmentsBeingsBeing.
func (v *queryWithNamedFragmentsBeingsUser) GetId() string { return v.Id }
func (v *queryWithNamedFragmentsBeingsAnimal) implementsGraphQLInterfacequeryWithNamedFragmentsBeingsBeing() {
}
// GetTypename is a part of, and documented with, the interface queryWithNamedFragmentsBeingsBeing.
func (v *queryWithNamedFragmentsBeingsAnimal) GetTypename() string { return v.Typename }
// GetId is a part of, and documented with, the interface queryWithNamedFragmentsBeingsBeing.
func (v *queryWithNamedFragmentsBeingsAnimal) GetId() string { return v.Id }
func __unmarshalqueryWithNamedFragmentsBeingsBeing(v *queryWithNamedFragmentsBeingsBeing, 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(queryWithNamedFragmentsBeingsUser)
return json.Unmarshal(m, *v)
case "Animal":
*v = new(queryWithNamedFragmentsBeingsAnimal)
return json.Unmarshal(m, *v)
case "":
return fmt.Errorf(
"Response was missing Being.__typename")
default:
return fmt.Errorf(
`Unexpected concrete type for queryWithNamedFragmentsBeingsBeing: "%v"`, tn.TypeName)
}
}
// queryWithNamedFragmentsBeingsUser includes the requested fields of the GraphQL type User.
type queryWithNamedFragmentsBeingsUser struct {
Typename string `json:"__typename"`
Id string `json:"id"`
UserFields `json:"-"`
}
func (v *queryWithNamedFragmentsBeingsUser) UnmarshalJSON(b []byte) error {
var firstPass struct {
*queryWithNamedFragmentsBeingsUser
graphql.NoUnmarshalJSON
}
firstPass.queryWithNamedFragmentsBeingsUser = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
err = json.Unmarshal(b, &v.UserFields)
if err != nil {
return err
}
return nil
}
// queryWithNamedFragmentsResponse is returned by queryWithNamedFragments on success.
type queryWithNamedFragmentsResponse struct {
Beings []queryWithNamedFragmentsBeingsBeing `json:"-"`
}
func (v *queryWithNamedFragmentsResponse) UnmarshalJSON(b []byte) error {
var firstPass struct {
*queryWithNamedFragmentsResponse
Beings []json.RawMessage `json:"beings"`
graphql.NoUnmarshalJSON
}
firstPass.queryWithNamedFragmentsResponse = v
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
{
target := &v.Beings
raw := firstPass.Beings
*target = make(
[]queryWithNamedFragmentsBeingsBeing,
len(raw))
for i, raw := range raw {
target := &(*target)[i]
err = __unmarshalqueryWithNamedFragmentsBeingsBeing(
target, raw)
if err != nil {
return fmt.Errorf(
"Unable to unmarshal queryWithNamedFragmentsResponse.Beings: %w", err)
}
}
}
return nil
}
@@ -877,3 +1207,56 @@ query queryWithFragments ($ids: [ID!]!) {
)
return &retval, err
}
func queryWithNamedFragments(
ctx context.Context,
client graphql.Client,
ids []string,
) (*queryWithNamedFragmentsResponse, error) {
variables := map[string]interface{}{
"ids": ids,
}
var err error
var retval queryWithNamedFragmentsResponse
err = client.MakeRequest(
ctx,
"queryWithNamedFragments",
`
query queryWithNamedFragments ($ids: [ID!]!) {
beings(ids: $ids) {
__typename
id
... AnimalFields
... UserFields
}
}
fragment AnimalFields on Animal {
id
hair {
hasHair
}
owner {
__typename
id
... UserFields
}
}
fragment UserFields on User {
id
luckyNumber
... MoreUserFields
}
fragment MoreUserFields on User {
id
hair {
color
}
}
`,
&retval,
variables,
)
return &retval, err
}
+88
View File
@@ -288,6 +288,94 @@ func TestFragments(t *testing.T) {
assert.Nil(t, resp.Beings[2])
}
func TestNamedFragments(t *testing.T) {
_ = `# @genqlient
fragment AnimalFields on Animal {
id
hair { hasHair }
owner { id ...UserFields }
}
fragment MoreUserFields on User {
id
hair { color }
}
fragment UserFields on User {
id luckyNumber
...MoreUserFields
}
query queryWithNamedFragments($ids: [ID!]!) {
beings(ids: $ids) {
__typename id
...AnimalFields
...UserFields
}
}`
ctx := context.Background()
server := server.RunServer()
defer server.Close()
client := graphql.NewClient(server.URL, http.DefaultClient)
resp, err := queryWithNamedFragments(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 luckyNumber.
assert.Equal(t, "User", resp.Beings[0].GetTypename())
assert.Equal(t, "1", resp.Beings[0].GetId())
// (luckyNumber, hair we need to cast for)
user, ok := resp.Beings[0].(*queryWithNamedFragmentsBeingsUser)
require.Truef(t, ok, "got %T, not User", resp.Beings[0])
assert.Equal(t, "1", user.Id)
assert.Equal(t, "1", user.UserFields.Id)
assert.Equal(t, "1", user.UserFields.MoreUserFields.Id)
// on UserFields, but we should be able to access directly via embedding:
assert.Equal(t, 17, user.LuckyNumber)
assert.Equal(t, "Black", user.Hair.Color)
// Animal has, in total, the fields:
// __typename
// id
// hair { hasHair }
// owner { id luckyNumber }
assert.Equal(t, "Animal", resp.Beings[1].GetTypename())
assert.Equal(t, "3", resp.Beings[1].GetId())
// (hair.* and owner.* we have to cast for)
animal, ok := resp.Beings[1].(*queryWithNamedFragmentsBeingsAnimal)
require.Truef(t, ok, "got %T, not Animal", resp.Beings[1])
// Check that we filled in *both* ID fields:
assert.Equal(t, "3", animal.Id)
assert.Equal(t, "3", animal.AnimalFields.Id)
// on AnimalFields:
assert.True(t, animal.Hair.HasHair)
assert.Equal(t, "1", animal.Owner.GetId())
// (luckyNumber we have to cast for, again)
owner, ok := animal.Owner.(*AnimalFieldsOwnerUser)
require.Truef(t, ok, "got %T, not User", animal.Owner)
// Check that we filled in *both* ID fields:
assert.Equal(t, "1", owner.Id)
assert.Equal(t, "1", owner.UserFields.Id)
assert.Equal(t, "1", owner.UserFields.MoreUserFields.Id)
// on UserFields:
assert.Equal(t, 17, owner.LuckyNumber)
assert.Equal(t, "Black", owner.Hair.Color)
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