Add support for "flattening" fragment-spreads (#121)
## Summary:
One common use of fragment spreads is as the entirety of a field's
selection, e.g.
```graphql
query MyQuery {
myField {
...MyFragment
}
}
```
In this case, by default, genqlient generates a wrapper type
`MyQueryMyFieldMyType`, which just embeds `MyFragment`. This makes
sense if you later want to add more fields in addition to the fragment
spread. But if you don't -- and you did the fragment because you want
to share types, it's an extra layer of indirection. (Which becomes
especially onerous if `myField` has list type (`[MyType!]`), such that
it's not just an extra attribute-access to get to `MyFragment`.)
The new option `# @genqlient(flatten: true)` simplifies this situation:
if applied to `myField` is skips the wrapper type;
`MyQueryResponse.MyField` will simply have type `MyFragment` (or
`[]MyFragment`, or whatever). This should hopefully make the `typename`
option, which has more limitations, less necessary.
Note that in #30 the initial idea was to support this for fields as
well. This would require significant additional complexity in the
JSON-(un)marshaling code, and has proven less necessary, so I
implemented this option only for fragment-spreads for now. With that
restriction, it was shockingly simple; we have to hook into a bunch of
different places, but they're all quite simple, since the structure of
the Go types still matches the structure in GraphQL.
Issue: https://github.com/Khan/genqlient/issues/30
## Test plan:
make check
Author: benjaminjkraft
Reviewers: csilvers, dnerdy, aberkan, jvoll, mahtabsabet, MiguelCastillo, StevenACoffman
Required Reviewers:
Approved By: csilvers, dnerdy
Checks: ✅ Test (1.17), ✅ Test (1.16), ✅ Test (1.15), ✅ Test (1.14), ✅ Lint, ✅ Test (1.17), ✅ Test (1.16), ✅ Test (1.15), ✅ Test (1.14), ✅ Lint
Pull Request URL: https://github.com/Khan/genqlient/pull/121
This commit is contained in:
@@ -115,6 +115,15 @@ func (g *generator) convertOperation(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// It's not common to use a fragment-spread for the whole query, but you
|
||||
// can if you want two queries to return the same type!
|
||||
if queryOptions.GetFlatten() {
|
||||
i, err := validateFlattenOption(baseType, operation.SelectionSet, operation.Position)
|
||||
if err == nil {
|
||||
return fields[i].GoType, nil
|
||||
}
|
||||
}
|
||||
|
||||
goType := &goStructType{
|
||||
GoName: name,
|
||||
descriptionInfo: descriptionInfo{
|
||||
@@ -340,6 +349,17 @@ func (g *generator) convertDefinition(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options.GetFlatten() {
|
||||
// As with struct, flatten only applies if valid, important if you
|
||||
// applied it to the whole query.
|
||||
// TODO(benkraft): This is a slightly fragile way to do this;
|
||||
// figure out a good way to do it before/while constructing the
|
||||
// fields, rather than after.
|
||||
i, err := validateFlattenOption(def, selectionSet, pos)
|
||||
if err == nil {
|
||||
return fields[i].GoType, nil
|
||||
}
|
||||
}
|
||||
|
||||
goType := &goStructType{
|
||||
GoName: name,
|
||||
@@ -406,6 +426,14 @@ func (g *generator) convertDefinition(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Flatten can only flatten if there is only one field (plus perhaps
|
||||
// __typename), and it's shared.
|
||||
if options.GetFlatten() {
|
||||
i, err := validateFlattenOption(def, selectionSet, pos)
|
||||
if err == nil {
|
||||
return sharedFields[i].GoType, nil
|
||||
}
|
||||
}
|
||||
|
||||
implementationTypes := g.schema.GetPossibleTypes(def)
|
||||
goType := &goInterfaceType{
|
||||
@@ -705,6 +733,15 @@ func (g *generator) convertNamedFragment(fragment *ast.FragmentDefinition) (goTy
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if directive.GetFlatten() {
|
||||
// Flatten on a fragment-definition is a bit weird -- it makes one
|
||||
// fragment effectively an alias for another -- but no reason we can't
|
||||
// allow it.
|
||||
i, err := validateFlattenOption(typ, fragment.SelectionSet, fragment.Position)
|
||||
if err == nil {
|
||||
return fields[i].GoType, nil
|
||||
}
|
||||
}
|
||||
|
||||
switch typ.Kind {
|
||||
case ast.Object:
|
||||
|
||||
@@ -15,6 +15,7 @@ type genqlientDirective struct {
|
||||
Omitempty *bool
|
||||
Pointer *bool
|
||||
Struct *bool
|
||||
Flatten *bool
|
||||
Bind string
|
||||
TypeName string
|
||||
}
|
||||
@@ -28,6 +29,7 @@ func newGenqlientDirective(pos *ast.Position) *genqlientDirective {
|
||||
func (dir *genqlientDirective) GetOmitempty() bool { return dir.Omitempty != nil && *dir.Omitempty }
|
||||
func (dir *genqlientDirective) GetPointer() bool { return dir.Pointer != nil && *dir.Pointer }
|
||||
func (dir *genqlientDirective) GetStruct() bool { return dir.Struct != nil && *dir.Struct }
|
||||
func (dir *genqlientDirective) GetFlatten() bool { return dir.Flatten != nil && *dir.Flatten }
|
||||
|
||||
func setBool(optionName string, dst **bool, v *ast.Value, pos *ast.Position) error {
|
||||
if *dst != nil {
|
||||
@@ -85,6 +87,8 @@ func (dir *genqlientDirective) add(graphQLDirective *ast.Directive, pos *ast.Pos
|
||||
err = setBool("pointer", &dir.Pointer, arg.Value, pos)
|
||||
case "struct":
|
||||
err = setBool("struct", &dir.Struct, arg.Value, pos)
|
||||
case "flatten":
|
||||
err = setBool("flatten", &dir.Flatten, arg.Value, pos)
|
||||
case "bind":
|
||||
err = setString("bind", &dir.Bind, arg.Value, pos)
|
||||
case "typename":
|
||||
@@ -116,7 +120,7 @@ func (dir *genqlientDirective) validate(node interface{}, schema *ast.Schema) er
|
||||
}
|
||||
|
||||
if dir.Struct != nil {
|
||||
return errorf(dir.pos, "struct is only applicable to fields")
|
||||
return errorf(dir.pos, "struct is only applicable to fields, not frragment-definitions")
|
||||
}
|
||||
|
||||
// Like operations, anything else will just apply to the entire
|
||||
@@ -128,22 +132,32 @@ func (dir *genqlientDirective) validate(node interface{}, schema *ast.Schema) er
|
||||
}
|
||||
|
||||
if dir.Struct != nil {
|
||||
return errorf(dir.pos, "struct is only applicable to fields")
|
||||
return errorf(dir.pos, "struct is only applicable to fields, not variable-definitions")
|
||||
}
|
||||
|
||||
if dir.Flatten != nil {
|
||||
return errorf(dir.pos, "flatten is only applicable to fields, not variable-definitions")
|
||||
}
|
||||
|
||||
return nil
|
||||
case *ast.Field:
|
||||
if dir.Omitempty != nil {
|
||||
return errorf(dir.pos, "omitempty is not applicable to fields")
|
||||
return errorf(dir.pos, "omitempty is not applicable to variables, not fields")
|
||||
}
|
||||
|
||||
typ := schema.Types[node.Definition.Type.Name()]
|
||||
if dir.Struct != nil {
|
||||
typ := schema.Types[node.Definition.Type.Name()]
|
||||
if err := validateStructOption(typ, node.SelectionSet, dir.pos); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if dir.Flatten != nil {
|
||||
if _, err := validateFlattenOption(typ, node.SelectionSet, dir.pos); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
default:
|
||||
return errorf(dir.pos, "invalid @genqlient directive location: %T", node)
|
||||
@@ -178,6 +192,58 @@ func validateStructOption(
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateFlattenOption(
|
||||
typ *ast.Definition,
|
||||
selectionSet ast.SelectionSet,
|
||||
pos *ast.Position,
|
||||
) (index int, err error) {
|
||||
index = -1
|
||||
if len(selectionSet) == 0 {
|
||||
return -1, errorf(pos, "flatten is not allowed for leaf fields")
|
||||
}
|
||||
|
||||
for i, selection := range selectionSet {
|
||||
switch selection := selection.(type) {
|
||||
case *ast.Field:
|
||||
// If the field is auto-added __typename, ignore it for flattening
|
||||
// purposes.
|
||||
if selection.Name == "__typename" && selection.Position == nil {
|
||||
continue
|
||||
}
|
||||
// Type-wise, it's no harder to implement flatten for fields, but
|
||||
// it requires new logic in UnmarshalJSON. We can add that if it
|
||||
// proves useful relative to its complexity.
|
||||
return -1, errorf(pos, "flatten is not yet supported for fields (only fragment spreads)")
|
||||
|
||||
case *ast.InlineFragment:
|
||||
// Inline fragments aren't allowed. In principle there's nothing
|
||||
// stopping us from allowing them (under the same type-match
|
||||
// conditions as fragment spreads), but there's little value to it.
|
||||
return -1, errorf(pos, "flatten is not allowed for selections with inline fragments")
|
||||
|
||||
case *ast.FragmentSpread:
|
||||
if index != -1 {
|
||||
return -1, errorf(pos, "flatten is not allowed for fields with multiple selections")
|
||||
} else if !fragmentMatches(typ, selection.Definition.Definition) {
|
||||
// We don't let you flatten
|
||||
// field { # type: FieldType
|
||||
// ...Fragment # type: FragmentType
|
||||
// }
|
||||
// unless FragmentType implements FieldType, because otherwise
|
||||
// what do we do if we get back a type that doesn't implement
|
||||
// FragmentType?
|
||||
return -1, errorf(pos,
|
||||
"flatten is not allowed for fields with fragment-spreads "+
|
||||
"unless the field-type implements the fragment-type; "+
|
||||
"field-type %s does not implement fragment-type %s",
|
||||
typ.Name, selection.Definition.Definition.Name)
|
||||
}
|
||||
index = i
|
||||
}
|
||||
}
|
||||
return index, nil
|
||||
}
|
||||
|
||||
// merge joins the directive applied to this node (the argument) and the one
|
||||
// applied to the entire operation (the receiver) and returns a new
|
||||
// directive-object representing the options to apply to this node (where in
|
||||
@@ -193,6 +259,9 @@ func (dir *genqlientDirective) merge(other *genqlientDirective) *genqlientDirect
|
||||
if other.Struct != nil {
|
||||
retval.Struct = other.Struct
|
||||
}
|
||||
if other.Flatten != nil {
|
||||
retval.Flatten = other.Flatten
|
||||
}
|
||||
if other.Bind != "" {
|
||||
retval.Bind = other.Bind
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
query FlattenField {
|
||||
# @genqlient(flatten: true)
|
||||
t {
|
||||
f
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
type Query { t: T }
|
||||
type T { f: String }
|
||||
@@ -0,0 +1,7 @@
|
||||
fragment F on T { f }
|
||||
query FlattenImplementation {
|
||||
# @genqlient(flatten: true)
|
||||
i {
|
||||
...F
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
type Query { i: I }
|
||||
interface I { f: String }
|
||||
type T implements I { f: String }
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
# @genqlient(flatten: true)
|
||||
fragment QueryFragment on Query {
|
||||
...InnerQueryFragment
|
||||
}
|
||||
|
||||
fragment InnerQueryFragment on Query {
|
||||
# @genqlient(flatten: true)
|
||||
randomVideo {
|
||||
...VideoFields
|
||||
}
|
||||
# @genqlient(flatten: true)
|
||||
randomItem {
|
||||
...ContentFields
|
||||
}
|
||||
# @genqlient(flatten: true)
|
||||
otherVideo: randomVideo {
|
||||
...ContentFields
|
||||
}
|
||||
}
|
||||
|
||||
fragment VideoFields on Video {
|
||||
id
|
||||
parent {
|
||||
# @genqlient(flatten: true)
|
||||
videoChildren {
|
||||
...ChildVideoFields
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment ChildVideoFields on Video {
|
||||
id name
|
||||
}
|
||||
|
||||
fragment ContentFields on Content {
|
||||
name url
|
||||
}
|
||||
|
||||
# @genqlient(flatten: true)
|
||||
query ComplexNamedFragments {
|
||||
...QueryFragment
|
||||
}
|
||||
+2
@@ -131,6 +131,7 @@ type Topic implements Content {
|
||||
parent: Topic
|
||||
url: String!
|
||||
children: [Content!]!
|
||||
videoChildren: [Video!]!
|
||||
schoolGrade: String
|
||||
}
|
||||
|
||||
@@ -162,6 +163,7 @@ type Query {
|
||||
root: Topic!
|
||||
randomItem: Content!
|
||||
randomLeaf: LeafContent!
|
||||
randomVideo: Video!
|
||||
convert(dt: DateTime!, tz: String): DateTime!
|
||||
maybeConvert(dt: DateTime, tz: String): DateTime
|
||||
getJunk: Junk
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// ChildVideoFields includes the GraphQL fields of Video requested by the fragment ChildVideoFields.
|
||||
type ChildVideoFields struct {
|
||||
// ID is documented in the Content interface.
|
||||
Id testutil.ID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// ContentFields includes the GraphQL fields of Content requested by the fragment ContentFields.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
// Content is implemented by various types like Article, Video, and Topic.
|
||||
//
|
||||
// ContentFields is implemented by the following types:
|
||||
// ContentFieldsArticle
|
||||
// ContentFieldsVideo
|
||||
// ContentFieldsTopic
|
||||
type ContentFields interface {
|
||||
implementsGraphQLInterfaceContentFields()
|
||||
// GetName returns the interface-field "name" from its implementation.
|
||||
GetName() string
|
||||
// GetUrl returns the interface-field "url" from its implementation.
|
||||
GetUrl() string
|
||||
}
|
||||
|
||||
func (v *ContentFieldsArticle) implementsGraphQLInterfaceContentFields() {}
|
||||
|
||||
// GetName is a part of, and documented with, the interface ContentFields.
|
||||
func (v *ContentFieldsArticle) GetName() string { return v.Name }
|
||||
|
||||
// GetUrl is a part of, and documented with, the interface ContentFields.
|
||||
func (v *ContentFieldsArticle) GetUrl() string { return v.Url }
|
||||
|
||||
func (v *ContentFieldsVideo) implementsGraphQLInterfaceContentFields() {}
|
||||
|
||||
// GetName is a part of, and documented with, the interface ContentFields.
|
||||
func (v *ContentFieldsVideo) GetName() string { return v.Name }
|
||||
|
||||
// GetUrl is a part of, and documented with, the interface ContentFields.
|
||||
func (v *ContentFieldsVideo) GetUrl() string { return v.Url }
|
||||
|
||||
func (v *ContentFieldsTopic) implementsGraphQLInterfaceContentFields() {}
|
||||
|
||||
// GetName is a part of, and documented with, the interface ContentFields.
|
||||
func (v *ContentFieldsTopic) GetName() string { return v.Name }
|
||||
|
||||
// GetUrl is a part of, and documented with, the interface ContentFields.
|
||||
func (v *ContentFieldsTopic) GetUrl() string { return v.Url }
|
||||
|
||||
func __unmarshalContentFields(b []byte, v *ContentFields) error {
|
||||
if string(b) == "null" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var tn struct {
|
||||
TypeName string `json:"__typename"`
|
||||
}
|
||||
err := json.Unmarshal(b, &tn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch tn.TypeName {
|
||||
case "Article":
|
||||
*v = new(ContentFieldsArticle)
|
||||
return json.Unmarshal(b, *v)
|
||||
case "Video":
|
||||
*v = new(ContentFieldsVideo)
|
||||
return json.Unmarshal(b, *v)
|
||||
case "Topic":
|
||||
*v = new(ContentFieldsTopic)
|
||||
return json.Unmarshal(b, *v)
|
||||
case "":
|
||||
return fmt.Errorf(
|
||||
"Response was missing Content.__typename")
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
`Unexpected concrete type for ContentFields: "%v"`, tn.TypeName)
|
||||
}
|
||||
}
|
||||
|
||||
func __marshalContentFields(v *ContentFields) ([]byte, error) {
|
||||
|
||||
var typename string
|
||||
switch v := (*v).(type) {
|
||||
case *ContentFieldsArticle:
|
||||
typename = "Article"
|
||||
|
||||
result := struct {
|
||||
TypeName string `json:"__typename"`
|
||||
*ContentFieldsArticle
|
||||
}{typename, v}
|
||||
return json.Marshal(result)
|
||||
case *ContentFieldsVideo:
|
||||
typename = "Video"
|
||||
|
||||
result := struct {
|
||||
TypeName string `json:"__typename"`
|
||||
*ContentFieldsVideo
|
||||
}{typename, v}
|
||||
return json.Marshal(result)
|
||||
case *ContentFieldsTopic:
|
||||
typename = "Topic"
|
||||
|
||||
result := struct {
|
||||
TypeName string `json:"__typename"`
|
||||
*ContentFieldsTopic
|
||||
}{typename, v}
|
||||
return json.Marshal(result)
|
||||
case nil:
|
||||
return []byte("null"), nil
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
`Unexpected concrete type for ContentFields: "%T"`, v)
|
||||
}
|
||||
}
|
||||
|
||||
// ContentFields includes the GraphQL fields of Article requested by the fragment ContentFields.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
// Content is implemented by various types like Article, Video, and Topic.
|
||||
type ContentFieldsArticle struct {
|
||||
Name string `json:"name"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
// ContentFields includes the GraphQL fields of Topic requested by the fragment ContentFields.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
// Content is implemented by various types like Article, Video, and Topic.
|
||||
type ContentFieldsTopic struct {
|
||||
Name string `json:"name"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
// ContentFields includes the GraphQL fields of Video requested by the fragment ContentFields.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
// Content is implemented by various types like Article, Video, and Topic.
|
||||
type ContentFieldsVideo struct {
|
||||
Name string `json:"name"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
// InnerQueryFragment includes the GraphQL fields of Query requested by the fragment InnerQueryFragment.
|
||||
// The GraphQL type's documentation follows.
|
||||
//
|
||||
// Query's description is probably ignored by almost all callers.
|
||||
type InnerQueryFragment struct {
|
||||
RandomVideo VideoFields `json:"randomVideo"`
|
||||
RandomItem ContentFields `json:"-"`
|
||||
OtherVideo ContentFieldsVideo `json:"otherVideo"`
|
||||
}
|
||||
|
||||
func (v *InnerQueryFragment) UnmarshalJSON(b []byte) error {
|
||||
|
||||
if string(b) == "null" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var firstPass struct {
|
||||
*InnerQueryFragment
|
||||
RandomItem json.RawMessage `json:"randomItem"`
|
||||
graphql.NoUnmarshalJSON
|
||||
}
|
||||
firstPass.InnerQueryFragment = v
|
||||
|
||||
err := json.Unmarshal(b, &firstPass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
{
|
||||
dst := &v.RandomItem
|
||||
src := firstPass.RandomItem
|
||||
if len(src) != 0 && string(src) != "null" {
|
||||
err = __unmarshalContentFields(
|
||||
src, dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"Unable to unmarshal InnerQueryFragment.RandomItem: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type __premarshalInnerQueryFragment struct {
|
||||
RandomVideo VideoFields `json:"randomVideo"`
|
||||
|
||||
RandomItem json.RawMessage `json:"randomItem"`
|
||||
|
||||
OtherVideo ContentFieldsVideo `json:"otherVideo"`
|
||||
}
|
||||
|
||||
func (v *InnerQueryFragment) MarshalJSON() ([]byte, error) {
|
||||
premarshaled, err := v.__premarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(premarshaled)
|
||||
}
|
||||
|
||||
func (v *InnerQueryFragment) __premarshalJSON() (*__premarshalInnerQueryFragment, error) {
|
||||
var retval __premarshalInnerQueryFragment
|
||||
|
||||
retval.RandomVideo = v.RandomVideo
|
||||
{
|
||||
|
||||
dst := &retval.RandomItem
|
||||
src := v.RandomItem
|
||||
var err error
|
||||
*dst, err = __marshalContentFields(
|
||||
&src)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"Unable to marshal InnerQueryFragment.RandomItem: %w", err)
|
||||
}
|
||||
}
|
||||
retval.OtherVideo = v.OtherVideo
|
||||
return &retval, 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"`
|
||||
Parent VideoFieldsParentTopic `json:"parent"`
|
||||
}
|
||||
|
||||
// VideoFieldsParentTopic includes the requested fields of the GraphQL type Topic.
|
||||
type VideoFieldsParentTopic struct {
|
||||
VideoChildren []ChildVideoFields `json:"videoChildren"`
|
||||
}
|
||||
|
||||
func ComplexNamedFragments(
|
||||
client graphql.Client,
|
||||
) (*InnerQueryFragment, error) {
|
||||
var err error
|
||||
|
||||
var retval InnerQueryFragment
|
||||
err = client.MakeRequest(
|
||||
nil,
|
||||
"ComplexNamedFragments",
|
||||
`
|
||||
query ComplexNamedFragments {
|
||||
... QueryFragment
|
||||
}
|
||||
fragment QueryFragment on Query {
|
||||
... InnerQueryFragment
|
||||
}
|
||||
fragment InnerQueryFragment on Query {
|
||||
randomVideo {
|
||||
... VideoFields
|
||||
}
|
||||
randomItem {
|
||||
__typename
|
||||
... ContentFields
|
||||
}
|
||||
otherVideo: randomVideo {
|
||||
... ContentFields
|
||||
}
|
||||
}
|
||||
fragment VideoFields on Video {
|
||||
id
|
||||
parent {
|
||||
videoChildren {
|
||||
... ChildVideoFields
|
||||
}
|
||||
}
|
||||
}
|
||||
fragment ContentFields on Content {
|
||||
name
|
||||
url
|
||||
}
|
||||
fragment ChildVideoFields on Video {
|
||||
id
|
||||
name
|
||||
}
|
||||
`,
|
||||
&retval,
|
||||
nil,
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"operations": [
|
||||
{
|
||||
"operationName": "ComplexNamedFragments",
|
||||
"query": "\nquery ComplexNamedFragments {\n\t... QueryFragment\n}\nfragment QueryFragment on Query {\n\t... InnerQueryFragment\n}\nfragment InnerQueryFragment on Query {\n\trandomVideo {\n\t\t... VideoFields\n\t}\n\trandomItem {\n\t\t__typename\n\t\t... ContentFields\n\t}\n\totherVideo: randomVideo {\n\t\t... ContentFields\n\t}\n}\nfragment VideoFields on Video {\n\tid\n\tparent {\n\t\tvideoChildren {\n\t\t\t... ChildVideoFields\n\t\t}\n\t}\n}\nfragment ContentFields on Content {\n\tname\n\turl\n}\nfragment ChildVideoFields on Video {\n\tid\n\tname\n}\n",
|
||||
"sourceLocation": "testdata/queries/Flatten.graphql"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
testdata/errors/FlattenField.graphql:3: flatten is not yet supported for fields (only fragment spreads)
|
||||
@@ -0,0 +1 @@
|
||||
testdata/errors/FlattenImplementation.graphql:4: flatten is not allowed for fields with fragment-spreads unless the field-type implements the fragment-type; field-type I does not implement fragment-type T
|
||||
Reference in New Issue
Block a user