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:
@@ -25,6 +25,7 @@ When releasing a new version:
|
|||||||
### New features:
|
### New features:
|
||||||
|
|
||||||
- genqlient's types are now safe to JSON-marshal, which can be useful for putting them in a cache, for example. See the [docs](FAQ.md#-let-me-json-marshal-my-response-objects) for details.
|
- genqlient's types are now safe to JSON-marshal, which can be useful for putting them in a cache, for example. See the [docs](FAQ.md#-let-me-json-marshal-my-response-objects) for details.
|
||||||
|
- The new `flatten` option in the `# @genqlient` directive allows for a simpler form of type-sharing using fragment spreads. See the [docs](FAQ.md#-shared-types-between-different-parts-of-the-query) for details.
|
||||||
|
|
||||||
### Bug fixes:
|
### Bug fixes:
|
||||||
|
|
||||||
|
|||||||
+18
-4
@@ -260,9 +260,9 @@ type GetMonopolyPlayersGameWinnerUser struct {
|
|||||||
// (others similarly)
|
// (others similarly)
|
||||||
```
|
```
|
||||||
|
|
||||||
But maybe you wanted to be able to pass all those users to a shared function (defined in your code), say `FormatUser(user ???) string`. That's no good; you need to put three different types as the `???`. genqlient has two ways to deal with this.
|
But maybe you wanted to be able to pass all those users to a shared function (defined in your code), say `FormatUser(user ???) string`. That's no good; you need to put three different types as the `???`. genqlient has several ways to deal with this.
|
||||||
|
|
||||||
One option -- the GraphQL Way, perhaps -- is to use fragments. You'd write your query like:
|
**Fragments:** One option -- the GraphQL Way, perhaps -- is to use fragments. You'd write your query like:
|
||||||
|
|
||||||
```graphql
|
```graphql
|
||||||
fragment MonopolyUser on User {
|
fragment MonopolyUser on User {
|
||||||
@@ -319,7 +319,21 @@ query GetMonopolyPlayers {
|
|||||||
|
|
||||||
and you can even spread the fragment into interface types. It also avoids having to list the fields several times.
|
and you can even spread the fragment into interface types. It also avoids having to list the fields several times.
|
||||||
|
|
||||||
Alternately, if you always want exactly the same fields, you can use the simpler but more restrictive genqlient option `typename`:
|
**Fragments, flattened:** The Go field for `winner`, in the first query above, has type `GetMonopolyPlayersGameWinnerUser` which just wraps `MonopolyUser`. If we don't want to add any other fields, that's unnecessary! Instead, we could do
|
||||||
|
```
|
||||||
|
query GetMonopolyPlayers {
|
||||||
|
game {
|
||||||
|
# @genqlient(flatten: true)
|
||||||
|
winner {
|
||||||
|
...MonopolyUser
|
||||||
|
}
|
||||||
|
# (etc.)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
and genqlient will skip the indirection and give the field `Winner` type `MonopolyUser` directly. This is often much more convenient if you put all the fields in the fragment, like the first query did. See the [options documentation](genqlient_directive.graphql) for more details.
|
||||||
|
|
||||||
|
**Type names:** Finally, if you always want exactly the same fields, you can use the simpler but more restrictive genqlient option `typename`:
|
||||||
|
|
||||||
```graphql
|
```graphql
|
||||||
query GetMonopolyPlayers {
|
query GetMonopolyPlayers {
|
||||||
@@ -351,7 +365,7 @@ type User struct {
|
|||||||
|
|
||||||
In this case, genqlient will validate that each type given the name `User` has the exact same fields; see the [full documentation](genqlient_directive.graphql) for details.
|
In this case, genqlient will validate that each type given the name `User` has the exact same fields; see the [full documentation](genqlient_directive.graphql) for details.
|
||||||
|
|
||||||
Note that it's also possible to use the `bindings` option (see [`genqlient.yaml` documentation](genqlient.yaml)) for a similar purpose, but this is not recommended as it typically requires more work for less gain.
|
**Bindings:** It's also possible to use the `bindings` option (see [`genqlient.yaml` documentation](genqlient.yaml)) for a similar purpose, but this is not recommended as it typically requires more work for less gain.
|
||||||
|
|
||||||
### … documentation on the output types?
|
### … documentation on the output types?
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,40 @@ directive genqlient(
|
|||||||
# fragment, you'll have to remove this option, and the types will change.
|
# fragment, you'll have to remove this option, and the types will change.
|
||||||
struct: Boolean
|
struct: Boolean
|
||||||
|
|
||||||
|
# If set, this field's selection must contain a single fragment-spread; we'll
|
||||||
|
# use the type of that fragment-spread as the type of the field.
|
||||||
|
#
|
||||||
|
# For example, given a query like
|
||||||
|
# query MyQuery {
|
||||||
|
# myField {
|
||||||
|
# ...MyFragment
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# by default genqlient will generate these types:
|
||||||
|
# type MyQueryResponse struct {
|
||||||
|
# MyField MyQueryMyFieldMyType
|
||||||
|
# }
|
||||||
|
# type MyQueryMyFieldMyType struct {
|
||||||
|
# MyFragment
|
||||||
|
# }
|
||||||
|
# If we instead do:
|
||||||
|
# query MyQuery {
|
||||||
|
# # @genqlient(flatten: true)
|
||||||
|
# myField {
|
||||||
|
# ...MyFragment
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# genqlient will simplify things:
|
||||||
|
# type MyQueryResponse struct {
|
||||||
|
# MyField MyFragment
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# This is only applicable to fields whose selection is a single
|
||||||
|
# fragment-spread, such that the field-type implements the fragment-type
|
||||||
|
# (i.e. we can't do this if MyFragment is on one implementation of the type
|
||||||
|
# of MyField; what if we got back the other type?).
|
||||||
|
flatten: Boolean
|
||||||
|
|
||||||
# If set, this argument or field will use the given Go type instead of a
|
# If set, this argument or field will use the given Go type instead of a
|
||||||
# genqlient-generated type.
|
# genqlient-generated type.
|
||||||
#
|
#
|
||||||
@@ -128,7 +162,7 @@ directive genqlient(
|
|||||||
# if your type-name conflicts with an autogenerated one (again, unless they
|
# if your type-name conflicts with an autogenerated one (again, unless they
|
||||||
# request the exact same fields). They must even have the fields in the
|
# request the exact same fields). They must even have the fields in the
|
||||||
# same order. Fragments are often easier to use (see the discussion of
|
# same order. Fragments are often easier to use (see the discussion of
|
||||||
# code-sharing in FAQ.md).
|
# code-sharing in FAQ.md, and the "flatten" option above).
|
||||||
#
|
#
|
||||||
# Note that unlike most directives, if applied to the entire operation,
|
# Note that unlike most directives, if applied to the entire operation,
|
||||||
# typename affects the overall response type, rather than being propagated
|
# typename affects the overall response type, rather than being propagated
|
||||||
|
|||||||
@@ -115,6 +115,15 @@ func (g *generator) convertOperation(
|
|||||||
return nil, err
|
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{
|
goType := &goStructType{
|
||||||
GoName: name,
|
GoName: name,
|
||||||
descriptionInfo: descriptionInfo{
|
descriptionInfo: descriptionInfo{
|
||||||
@@ -340,6 +349,17 @@ func (g *generator) convertDefinition(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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{
|
goType := &goStructType{
|
||||||
GoName: name,
|
GoName: name,
|
||||||
@@ -406,6 +426,14 @@ func (g *generator) convertDefinition(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
implementationTypes := g.schema.GetPossibleTypes(def)
|
||||||
goType := &goInterfaceType{
|
goType := &goInterfaceType{
|
||||||
@@ -705,6 +733,15 @@ func (g *generator) convertNamedFragment(fragment *ast.FragmentDefinition) (goTy
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
switch typ.Kind {
|
||||||
case ast.Object:
|
case ast.Object:
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type genqlientDirective struct {
|
|||||||
Omitempty *bool
|
Omitempty *bool
|
||||||
Pointer *bool
|
Pointer *bool
|
||||||
Struct *bool
|
Struct *bool
|
||||||
|
Flatten *bool
|
||||||
Bind string
|
Bind string
|
||||||
TypeName 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) GetOmitempty() bool { return dir.Omitempty != nil && *dir.Omitempty }
|
||||||
func (dir *genqlientDirective) GetPointer() bool { return dir.Pointer != nil && *dir.Pointer }
|
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) 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 {
|
func setBool(optionName string, dst **bool, v *ast.Value, pos *ast.Position) error {
|
||||||
if *dst != nil {
|
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)
|
err = setBool("pointer", &dir.Pointer, arg.Value, pos)
|
||||||
case "struct":
|
case "struct":
|
||||||
err = setBool("struct", &dir.Struct, arg.Value, pos)
|
err = setBool("struct", &dir.Struct, arg.Value, pos)
|
||||||
|
case "flatten":
|
||||||
|
err = setBool("flatten", &dir.Flatten, arg.Value, pos)
|
||||||
case "bind":
|
case "bind":
|
||||||
err = setString("bind", &dir.Bind, arg.Value, pos)
|
err = setString("bind", &dir.Bind, arg.Value, pos)
|
||||||
case "typename":
|
case "typename":
|
||||||
@@ -116,7 +120,7 @@ func (dir *genqlientDirective) validate(node interface{}, schema *ast.Schema) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
if dir.Struct != nil {
|
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
|
// 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 {
|
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
|
return nil
|
||||||
case *ast.Field:
|
case *ast.Field:
|
||||||
if dir.Omitempty != nil {
|
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 {
|
if dir.Struct != nil {
|
||||||
typ := schema.Types[node.Definition.Type.Name()]
|
|
||||||
if err := validateStructOption(typ, node.SelectionSet, dir.pos); err != nil {
|
if err := validateStructOption(typ, node.SelectionSet, dir.pos); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dir.Flatten != nil {
|
||||||
|
if _, err := validateFlattenOption(typ, node.SelectionSet, dir.pos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return errorf(dir.pos, "invalid @genqlient directive location: %T", node)
|
return errorf(dir.pos, "invalid @genqlient directive location: %T", node)
|
||||||
@@ -178,6 +192,58 @@ func validateStructOption(
|
|||||||
return nil
|
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
|
// merge joins the directive applied to this node (the argument) and the one
|
||||||
// applied to the entire operation (the receiver) and returns a new
|
// applied to the entire operation (the receiver) and returns a new
|
||||||
// directive-object representing the options to apply to this node (where in
|
// 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 {
|
if other.Struct != nil {
|
||||||
retval.Struct = other.Struct
|
retval.Struct = other.Struct
|
||||||
}
|
}
|
||||||
|
if other.Flatten != nil {
|
||||||
|
retval.Flatten = other.Flatten
|
||||||
|
}
|
||||||
if other.Bind != "" {
|
if other.Bind != "" {
|
||||||
retval.Bind = 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
|
parent: Topic
|
||||||
url: String!
|
url: String!
|
||||||
children: [Content!]!
|
children: [Content!]!
|
||||||
|
videoChildren: [Video!]!
|
||||||
schoolGrade: String
|
schoolGrade: String
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,6 +163,7 @@ type Query {
|
|||||||
root: Topic!
|
root: Topic!
|
||||||
randomItem: Content!
|
randomItem: Content!
|
||||||
randomLeaf: LeafContent!
|
randomLeaf: LeafContent!
|
||||||
|
randomVideo: Video!
|
||||||
convert(dt: DateTime!, tz: String): DateTime!
|
convert(dt: DateTime!, tz: String): DateTime!
|
||||||
maybeConvert(dt: DateTime, tz: String): DateTime
|
maybeConvert(dt: DateTime, tz: String): DateTime
|
||||||
getJunk: Junk
|
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
|
||||||
@@ -255,6 +255,177 @@ func (v *AnimalFieldsOwnerUser) __premarshalJSON() (*__premarshalAnimalFieldsOwn
|
|||||||
return &retval, nil
|
return &retval, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FriendsFields includes the GraphQL fields of User requested by the fragment FriendsFields.
|
||||||
|
type FriendsFields struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InnerBeingFields includes the GraphQL fields of Being requested by the fragment InnerBeingFields.
|
||||||
|
//
|
||||||
|
// InnerBeingFields is implemented by the following types:
|
||||||
|
// InnerBeingFieldsUser
|
||||||
|
// InnerBeingFieldsAnimal
|
||||||
|
type InnerBeingFields interface {
|
||||||
|
implementsGraphQLInterfaceInnerBeingFields()
|
||||||
|
// GetId returns the interface-field "id" from its implementation.
|
||||||
|
GetId() string
|
||||||
|
// GetName returns the interface-field "name" from its implementation.
|
||||||
|
GetName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *InnerBeingFieldsUser) implementsGraphQLInterfaceInnerBeingFields() {}
|
||||||
|
|
||||||
|
// GetId is a part of, and documented with, the interface InnerBeingFields.
|
||||||
|
func (v *InnerBeingFieldsUser) GetId() string { return v.Id }
|
||||||
|
|
||||||
|
// GetName is a part of, and documented with, the interface InnerBeingFields.
|
||||||
|
func (v *InnerBeingFieldsUser) GetName() string { return v.Name }
|
||||||
|
|
||||||
|
func (v *InnerBeingFieldsAnimal) implementsGraphQLInterfaceInnerBeingFields() {}
|
||||||
|
|
||||||
|
// GetId is a part of, and documented with, the interface InnerBeingFields.
|
||||||
|
func (v *InnerBeingFieldsAnimal) GetId() string { return v.Id }
|
||||||
|
|
||||||
|
// GetName is a part of, and documented with, the interface InnerBeingFields.
|
||||||
|
func (v *InnerBeingFieldsAnimal) GetName() string { return v.Name }
|
||||||
|
|
||||||
|
func __unmarshalInnerBeingFields(b []byte, v *InnerBeingFields) 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 "User":
|
||||||
|
*v = new(InnerBeingFieldsUser)
|
||||||
|
return json.Unmarshal(b, *v)
|
||||||
|
case "Animal":
|
||||||
|
*v = new(InnerBeingFieldsAnimal)
|
||||||
|
return json.Unmarshal(b, *v)
|
||||||
|
case "":
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Response was missing Being.__typename")
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(
|
||||||
|
`Unexpected concrete type for InnerBeingFields: "%v"`, tn.TypeName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func __marshalInnerBeingFields(v *InnerBeingFields) ([]byte, error) {
|
||||||
|
|
||||||
|
var typename string
|
||||||
|
switch v := (*v).(type) {
|
||||||
|
case *InnerBeingFieldsUser:
|
||||||
|
typename = "User"
|
||||||
|
|
||||||
|
result := struct {
|
||||||
|
TypeName string `json:"__typename"`
|
||||||
|
*InnerBeingFieldsUser
|
||||||
|
}{typename, v}
|
||||||
|
return json.Marshal(result)
|
||||||
|
case *InnerBeingFieldsAnimal:
|
||||||
|
typename = "Animal"
|
||||||
|
|
||||||
|
result := struct {
|
||||||
|
TypeName string `json:"__typename"`
|
||||||
|
*InnerBeingFieldsAnimal
|
||||||
|
}{typename, v}
|
||||||
|
return json.Marshal(result)
|
||||||
|
case nil:
|
||||||
|
return []byte("null"), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
`Unexpected concrete type for InnerBeingFields: "%T"`, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InnerBeingFields includes the GraphQL fields of Animal requested by the fragment InnerBeingFields.
|
||||||
|
type InnerBeingFieldsAnimal struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InnerBeingFields includes the GraphQL fields of User requested by the fragment InnerBeingFields.
|
||||||
|
type InnerBeingFieldsUser struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Friends []FriendsFields `json:"friends"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InnerLuckyFields includes the GraphQL fields of Lucky requested by the fragment InnerLuckyFields.
|
||||||
|
//
|
||||||
|
// InnerLuckyFields is implemented by the following types:
|
||||||
|
// InnerLuckyFieldsUser
|
||||||
|
type InnerLuckyFields interface {
|
||||||
|
implementsGraphQLInterfaceInnerLuckyFields()
|
||||||
|
// GetLuckyNumber returns the interface-field "luckyNumber" from its implementation.
|
||||||
|
GetLuckyNumber() int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *InnerLuckyFieldsUser) implementsGraphQLInterfaceInnerLuckyFields() {}
|
||||||
|
|
||||||
|
// GetLuckyNumber is a part of, and documented with, the interface InnerLuckyFields.
|
||||||
|
func (v *InnerLuckyFieldsUser) GetLuckyNumber() int { return v.LuckyNumber }
|
||||||
|
|
||||||
|
func __unmarshalInnerLuckyFields(b []byte, v *InnerLuckyFields) 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 "User":
|
||||||
|
*v = new(InnerLuckyFieldsUser)
|
||||||
|
return json.Unmarshal(b, *v)
|
||||||
|
case "":
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Response was missing Lucky.__typename")
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(
|
||||||
|
`Unexpected concrete type for InnerLuckyFields: "%v"`, tn.TypeName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func __marshalInnerLuckyFields(v *InnerLuckyFields) ([]byte, error) {
|
||||||
|
|
||||||
|
var typename string
|
||||||
|
switch v := (*v).(type) {
|
||||||
|
case *InnerLuckyFieldsUser:
|
||||||
|
typename = "User"
|
||||||
|
|
||||||
|
result := struct {
|
||||||
|
TypeName string `json:"__typename"`
|
||||||
|
*InnerLuckyFieldsUser
|
||||||
|
}{typename, v}
|
||||||
|
return json.Marshal(result)
|
||||||
|
case nil:
|
||||||
|
return []byte("null"), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
`Unexpected concrete type for InnerLuckyFields: "%T"`, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InnerLuckyFields includes the GraphQL fields of User requested by the fragment InnerLuckyFields.
|
||||||
|
type InnerLuckyFieldsUser struct {
|
||||||
|
LuckyNumber int `json:"luckyNumber"`
|
||||||
|
}
|
||||||
|
|
||||||
// LuckyFields includes the GraphQL fields of Lucky requested by the fragment LuckyFields.
|
// LuckyFields includes the GraphQL fields of Lucky requested by the fragment LuckyFields.
|
||||||
//
|
//
|
||||||
// LuckyFields is implemented by the following types:
|
// LuckyFields is implemented by the following types:
|
||||||
@@ -387,6 +558,313 @@ type MoreUserFieldsHair struct {
|
|||||||
Color string `json:"color"`
|
Color string `json:"color"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryFragment includes the GraphQL fields of Query requested by the fragment QueryFragment.
|
||||||
|
type QueryFragment struct {
|
||||||
|
Beings []QueryFragmentBeingsBeing `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *QueryFragment) UnmarshalJSON(b []byte) error {
|
||||||
|
|
||||||
|
if string(b) == "null" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstPass struct {
|
||||||
|
*QueryFragment
|
||||||
|
Beings []json.RawMessage `json:"beings"`
|
||||||
|
graphql.NoUnmarshalJSON
|
||||||
|
}
|
||||||
|
firstPass.QueryFragment = v
|
||||||
|
|
||||||
|
err := json.Unmarshal(b, &firstPass)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
dst := &v.Beings
|
||||||
|
src := firstPass.Beings
|
||||||
|
*dst = make(
|
||||||
|
[]QueryFragmentBeingsBeing,
|
||||||
|
len(src))
|
||||||
|
for i, src := range src {
|
||||||
|
dst := &(*dst)[i]
|
||||||
|
if len(src) != 0 && string(src) != "null" {
|
||||||
|
err = __unmarshalQueryFragmentBeingsBeing(
|
||||||
|
src, dst)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Unable to unmarshal QueryFragment.Beings: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type __premarshalQueryFragment struct {
|
||||||
|
Beings []json.RawMessage `json:"beings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *QueryFragment) MarshalJSON() ([]byte, error) {
|
||||||
|
premarshaled, err := v.__premarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return json.Marshal(premarshaled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *QueryFragment) __premarshalJSON() (*__premarshalQueryFragment, error) {
|
||||||
|
var retval __premarshalQueryFragment
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
dst := &retval.Beings
|
||||||
|
src := v.Beings
|
||||||
|
*dst = make(
|
||||||
|
[]json.RawMessage,
|
||||||
|
len(src))
|
||||||
|
for i, src := range src {
|
||||||
|
dst := &(*dst)[i]
|
||||||
|
var err error
|
||||||
|
*dst, err = __marshalQueryFragmentBeingsBeing(
|
||||||
|
&src)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"Unable to marshal QueryFragment.Beings: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &retval, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryFragmentBeingsAnimal includes the requested fields of the GraphQL type Animal.
|
||||||
|
type QueryFragmentBeingsAnimal struct {
|
||||||
|
Typename string `json:"__typename"`
|
||||||
|
Id string `json:"id"`
|
||||||
|
Owner InnerBeingFields `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *QueryFragmentBeingsAnimal) UnmarshalJSON(b []byte) error {
|
||||||
|
|
||||||
|
if string(b) == "null" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstPass struct {
|
||||||
|
*QueryFragmentBeingsAnimal
|
||||||
|
Owner json.RawMessage `json:"owner"`
|
||||||
|
graphql.NoUnmarshalJSON
|
||||||
|
}
|
||||||
|
firstPass.QueryFragmentBeingsAnimal = v
|
||||||
|
|
||||||
|
err := json.Unmarshal(b, &firstPass)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
dst := &v.Owner
|
||||||
|
src := firstPass.Owner
|
||||||
|
if len(src) != 0 && string(src) != "null" {
|
||||||
|
err = __unmarshalInnerBeingFields(
|
||||||
|
src, dst)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Unable to unmarshal QueryFragmentBeingsAnimal.Owner: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type __premarshalQueryFragmentBeingsAnimal struct {
|
||||||
|
Typename string `json:"__typename"`
|
||||||
|
|
||||||
|
Id string `json:"id"`
|
||||||
|
|
||||||
|
Owner json.RawMessage `json:"owner"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *QueryFragmentBeingsAnimal) MarshalJSON() ([]byte, error) {
|
||||||
|
premarshaled, err := v.__premarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return json.Marshal(premarshaled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *QueryFragmentBeingsAnimal) __premarshalJSON() (*__premarshalQueryFragmentBeingsAnimal, error) {
|
||||||
|
var retval __premarshalQueryFragmentBeingsAnimal
|
||||||
|
|
||||||
|
retval.Typename = v.Typename
|
||||||
|
retval.Id = v.Id
|
||||||
|
{
|
||||||
|
|
||||||
|
dst := &retval.Owner
|
||||||
|
src := v.Owner
|
||||||
|
var err error
|
||||||
|
*dst, err = __marshalInnerBeingFields(
|
||||||
|
&src)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"Unable to marshal QueryFragmentBeingsAnimal.Owner: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &retval, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryFragmentBeingsBeing includes the requested fields of the GraphQL interface Being.
|
||||||
|
//
|
||||||
|
// QueryFragmentBeingsBeing is implemented by the following types:
|
||||||
|
// QueryFragmentBeingsUser
|
||||||
|
// QueryFragmentBeingsAnimal
|
||||||
|
type QueryFragmentBeingsBeing interface {
|
||||||
|
implementsGraphQLInterfaceQueryFragmentBeingsBeing()
|
||||||
|
// 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 *QueryFragmentBeingsUser) implementsGraphQLInterfaceQueryFragmentBeingsBeing() {}
|
||||||
|
|
||||||
|
// GetTypename is a part of, and documented with, the interface QueryFragmentBeingsBeing.
|
||||||
|
func (v *QueryFragmentBeingsUser) GetTypename() string { return v.Typename }
|
||||||
|
|
||||||
|
// GetId is a part of, and documented with, the interface QueryFragmentBeingsBeing.
|
||||||
|
func (v *QueryFragmentBeingsUser) GetId() string { return v.Id }
|
||||||
|
|
||||||
|
func (v *QueryFragmentBeingsAnimal) implementsGraphQLInterfaceQueryFragmentBeingsBeing() {}
|
||||||
|
|
||||||
|
// GetTypename is a part of, and documented with, the interface QueryFragmentBeingsBeing.
|
||||||
|
func (v *QueryFragmentBeingsAnimal) GetTypename() string { return v.Typename }
|
||||||
|
|
||||||
|
// GetId is a part of, and documented with, the interface QueryFragmentBeingsBeing.
|
||||||
|
func (v *QueryFragmentBeingsAnimal) GetId() string { return v.Id }
|
||||||
|
|
||||||
|
func __unmarshalQueryFragmentBeingsBeing(b []byte, v *QueryFragmentBeingsBeing) 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 "User":
|
||||||
|
*v = new(QueryFragmentBeingsUser)
|
||||||
|
return json.Unmarshal(b, *v)
|
||||||
|
case "Animal":
|
||||||
|
*v = new(QueryFragmentBeingsAnimal)
|
||||||
|
return json.Unmarshal(b, *v)
|
||||||
|
case "":
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Response was missing Being.__typename")
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(
|
||||||
|
`Unexpected concrete type for QueryFragmentBeingsBeing: "%v"`, tn.TypeName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func __marshalQueryFragmentBeingsBeing(v *QueryFragmentBeingsBeing) ([]byte, error) {
|
||||||
|
|
||||||
|
var typename string
|
||||||
|
switch v := (*v).(type) {
|
||||||
|
case *QueryFragmentBeingsUser:
|
||||||
|
typename = "User"
|
||||||
|
|
||||||
|
premarshaled, err := v.__premarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := struct {
|
||||||
|
TypeName string `json:"__typename"`
|
||||||
|
*__premarshalQueryFragmentBeingsUser
|
||||||
|
}{typename, premarshaled}
|
||||||
|
return json.Marshal(result)
|
||||||
|
case *QueryFragmentBeingsAnimal:
|
||||||
|
typename = "Animal"
|
||||||
|
|
||||||
|
premarshaled, err := v.__premarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := struct {
|
||||||
|
TypeName string `json:"__typename"`
|
||||||
|
*__premarshalQueryFragmentBeingsAnimal
|
||||||
|
}{typename, premarshaled}
|
||||||
|
return json.Marshal(result)
|
||||||
|
case nil:
|
||||||
|
return []byte("null"), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
`Unexpected concrete type for QueryFragmentBeingsBeing: "%T"`, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryFragmentBeingsUser includes the requested fields of the GraphQL type User.
|
||||||
|
type QueryFragmentBeingsUser struct {
|
||||||
|
Typename string `json:"__typename"`
|
||||||
|
Id string `json:"id"`
|
||||||
|
InnerLuckyFieldsUser `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *QueryFragmentBeingsUser) UnmarshalJSON(b []byte) error {
|
||||||
|
|
||||||
|
if string(b) == "null" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstPass struct {
|
||||||
|
*QueryFragmentBeingsUser
|
||||||
|
graphql.NoUnmarshalJSON
|
||||||
|
}
|
||||||
|
firstPass.QueryFragmentBeingsUser = v
|
||||||
|
|
||||||
|
err := json.Unmarshal(b, &firstPass)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(
|
||||||
|
b, &v.InnerLuckyFieldsUser)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type __premarshalQueryFragmentBeingsUser struct {
|
||||||
|
Typename string `json:"__typename"`
|
||||||
|
|
||||||
|
Id string `json:"id"`
|
||||||
|
|
||||||
|
LuckyNumber int `json:"luckyNumber"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *QueryFragmentBeingsUser) MarshalJSON() ([]byte, error) {
|
||||||
|
premarshaled, err := v.__premarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return json.Marshal(premarshaled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *QueryFragmentBeingsUser) __premarshalJSON() (*__premarshalQueryFragmentBeingsUser, error) {
|
||||||
|
var retval __premarshalQueryFragmentBeingsUser
|
||||||
|
|
||||||
|
retval.Typename = v.Typename
|
||||||
|
retval.Id = v.Id
|
||||||
|
retval.LuckyNumber = v.InnerLuckyFieldsUser.LuckyNumber
|
||||||
|
return &retval, nil
|
||||||
|
}
|
||||||
|
|
||||||
type Species string
|
type Species string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -679,6 +1157,11 @@ func (v *__queryWithCustomMarshalSliceInput) __premarshalJSON() (*__premarshal__
|
|||||||
return &retval, nil
|
return &retval, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// __queryWithFlattenInput is used internally by genqlient
|
||||||
|
type __queryWithFlattenInput struct {
|
||||||
|
Ids []string `json:"ids"`
|
||||||
|
}
|
||||||
|
|
||||||
// __queryWithFragmentsInput is used internally by genqlient
|
// __queryWithFragmentsInput is used internally by genqlient
|
||||||
type __queryWithFragmentsInput struct {
|
type __queryWithFragmentsInput struct {
|
||||||
Ids []string `json:"ids"`
|
Ids []string `json:"ids"`
|
||||||
@@ -2678,3 +3161,66 @@ fragment MoreUserFields on User {
|
|||||||
)
|
)
|
||||||
return &retval, err
|
return &retval, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func queryWithFlatten(
|
||||||
|
ctx context.Context,
|
||||||
|
client graphql.Client,
|
||||||
|
ids []string,
|
||||||
|
) (*QueryFragment, error) {
|
||||||
|
__input := __queryWithFlattenInput{
|
||||||
|
Ids: ids,
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
|
||||||
|
var retval QueryFragment
|
||||||
|
err = client.MakeRequest(
|
||||||
|
ctx,
|
||||||
|
"queryWithFlatten",
|
||||||
|
`
|
||||||
|
query queryWithFlatten ($ids: [ID!]!) {
|
||||||
|
... QueryFragment
|
||||||
|
}
|
||||||
|
fragment QueryFragment on Query {
|
||||||
|
beings(ids: $ids) {
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
... FlattenedUserFields
|
||||||
|
... on Animal {
|
||||||
|
owner {
|
||||||
|
__typename
|
||||||
|
... BeingFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fragment FlattenedUserFields on User {
|
||||||
|
... FlattenedLuckyFields
|
||||||
|
}
|
||||||
|
fragment BeingFields on Being {
|
||||||
|
... InnerBeingFields
|
||||||
|
}
|
||||||
|
fragment FlattenedLuckyFields on Lucky {
|
||||||
|
... InnerLuckyFields
|
||||||
|
}
|
||||||
|
fragment InnerBeingFields on Being {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
... on User {
|
||||||
|
friends {
|
||||||
|
... FriendsFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fragment InnerLuckyFields on Lucky {
|
||||||
|
luckyNumber
|
||||||
|
}
|
||||||
|
fragment FriendsFields on User {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
&retval,
|
||||||
|
&__input,
|
||||||
|
)
|
||||||
|
return &retval, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -554,6 +554,116 @@ func TestNamedFragments(t *testing.T) {
|
|||||||
assert.Nil(t, resp.Beings[2])
|
assert.Nil(t, resp.Beings[2])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFlatten(t *testing.T) {
|
||||||
|
_ = `# @genqlient
|
||||||
|
# @genqlient(flatten: true)
|
||||||
|
fragment BeingFields on Being {
|
||||||
|
...InnerBeingFields
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment InnerBeingFields on Being {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
... on User {
|
||||||
|
# @genqlient(flatten: true)
|
||||||
|
friends {
|
||||||
|
...FriendsFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment FriendsFields on User {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
|
||||||
|
# @genqlient(flatten: true)
|
||||||
|
fragment FlattenedUserFields on User {
|
||||||
|
...FlattenedLuckyFields
|
||||||
|
}
|
||||||
|
|
||||||
|
# @genqlient(flatten: true)
|
||||||
|
fragment FlattenedLuckyFields on Lucky {
|
||||||
|
...InnerLuckyFields
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment InnerLuckyFields on Lucky {
|
||||||
|
luckyNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment QueryFragment on Query {
|
||||||
|
beings(ids: $ids) {
|
||||||
|
__typename id
|
||||||
|
...FlattenedUserFields
|
||||||
|
... on Animal {
|
||||||
|
# @genqlient(flatten: true)
|
||||||
|
owner {
|
||||||
|
...BeingFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# @genqlient(flatten: true)
|
||||||
|
query queryWithFlatten(
|
||||||
|
$ids: [ID!]!,
|
||||||
|
) {
|
||||||
|
...QueryFragment
|
||||||
|
}`
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
server := server.RunServer()
|
||||||
|
defer server.Close()
|
||||||
|
client := newRoundtripClient(t, server.URL)
|
||||||
|
|
||||||
|
resp, err := queryWithFlatten(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 we need to cast for)
|
||||||
|
|
||||||
|
user, ok := resp.Beings[0].(*QueryFragmentBeingsUser)
|
||||||
|
require.Truef(t, ok, "got %T, not User", resp.Beings[0])
|
||||||
|
assert.Equal(t, "1", user.Id)
|
||||||
|
assert.Equal(t, 17, user.InnerLuckyFieldsUser.LuckyNumber)
|
||||||
|
|
||||||
|
// Animal has, in total, the fields:
|
||||||
|
// __typename
|
||||||
|
// id
|
||||||
|
// owner { id name ... on User { friends { id name } } }
|
||||||
|
assert.Equal(t, "Animal", resp.Beings[1].GetTypename())
|
||||||
|
assert.Equal(t, "3", resp.Beings[1].GetId())
|
||||||
|
// (owner.* we have to cast for)
|
||||||
|
|
||||||
|
animal, ok := resp.Beings[1].(*QueryFragmentBeingsAnimal)
|
||||||
|
require.Truef(t, ok, "got %T, not Animal", resp.Beings[1])
|
||||||
|
assert.Equal(t, "3", animal.Id)
|
||||||
|
// on AnimalFields:
|
||||||
|
assert.Equal(t, "1", animal.Owner.GetId())
|
||||||
|
assert.Equal(t, "Yours Truly", animal.Owner.GetName())
|
||||||
|
// (friends.* we have to cast for, again)
|
||||||
|
|
||||||
|
owner, ok := animal.Owner.(*InnerBeingFieldsUser)
|
||||||
|
require.Truef(t, ok, "got %T, not User", animal.Owner)
|
||||||
|
assert.Equal(t, "1", owner.Id)
|
||||||
|
assert.Equal(t, "Yours Truly", owner.Name)
|
||||||
|
assert.Len(t, owner.Friends, 1)
|
||||||
|
assert.Equal(t, "2", owner.Friends[0].Id)
|
||||||
|
assert.Equal(t, "Raven", owner.Friends[0].Name)
|
||||||
|
|
||||||
|
assert.Nil(t, resp.Beings[2])
|
||||||
|
}
|
||||||
|
|
||||||
func TestGeneratedCode(t *testing.T) {
|
func TestGeneratedCode(t *testing.T) {
|
||||||
// TODO(benkraft): Check that gqlgen is up to date too. In practice that's
|
// 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
|
// less likely to be a problem, since it should only change if you update
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type User implements Being & Lucky {
|
|||||||
luckyNumber: Int
|
luckyNumber: Int
|
||||||
hair: Hair
|
hair: Hair
|
||||||
birthdate: Date
|
birthdate: Date
|
||||||
|
friends: [User!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Hair { color: String } # silly name to confuse the name-generator
|
type Hair { color: String } # silly name to confuse the name-generator
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ type ComplexityRoot struct {
|
|||||||
|
|
||||||
User struct {
|
User struct {
|
||||||
Birthdate func(childComplexity int) int
|
Birthdate func(childComplexity int) int
|
||||||
|
Friends func(childComplexity int) int
|
||||||
Hair func(childComplexity int) int
|
Hair func(childComplexity int) int
|
||||||
ID func(childComplexity int) int
|
ID func(childComplexity int) int
|
||||||
LuckyNumber func(childComplexity int) int
|
LuckyNumber func(childComplexity int) int
|
||||||
@@ -260,6 +261,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||||||
|
|
||||||
return e.complexity.User.Birthdate(childComplexity), true
|
return e.complexity.User.Birthdate(childComplexity), true
|
||||||
|
|
||||||
|
case "User.friends":
|
||||||
|
if e.complexity.User.Friends == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.User.Friends(childComplexity), true
|
||||||
|
|
||||||
case "User.hair":
|
case "User.hair":
|
||||||
if e.complexity.User.Hair == nil {
|
if e.complexity.User.Hair == nil {
|
||||||
break
|
break
|
||||||
@@ -358,6 +366,7 @@ type User implements Being & Lucky {
|
|||||||
luckyNumber: Int
|
luckyNumber: Int
|
||||||
hair: Hair
|
hair: Hair
|
||||||
birthdate: Date
|
birthdate: Date
|
||||||
|
friends: [User!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Hair { color: String } # silly name to confuse the name-generator
|
type Hair { color: String } # silly name to confuse the name-generator
|
||||||
@@ -1379,6 +1388,41 @@ func (ec *executionContext) _User_birthdate(ctx context.Context, field graphql.C
|
|||||||
return ec.marshalODate2ᚖstring(ctx, field.Selections, res)
|
return ec.marshalODate2ᚖstring(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _User_friends(ctx context.Context, field graphql.CollectedField, obj *User) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "User",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: false,
|
||||||
|
IsResolver: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Friends, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
if !graphql.HasFieldError(ctx, fc) {
|
||||||
|
ec.Errorf(ctx, "must not be null")
|
||||||
|
}
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.([]*User)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNUser2ᚕᚖgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐUserᚄ(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) {
|
func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@@ -2770,6 +2814,11 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj
|
|||||||
out.Values[i] = ec._User_hair(ctx, field, obj)
|
out.Values[i] = ec._User_hair(ctx, field, obj)
|
||||||
case "birthdate":
|
case "birthdate":
|
||||||
out.Values[i] = ec._User_birthdate(ctx, field, obj)
|
out.Values[i] = ec._User_birthdate(ctx, field, obj)
|
||||||
|
case "friends":
|
||||||
|
out.Values[i] = ec._User_friends(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
invalids++
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
panic("unknown field " + strconv.Quote(field.Name))
|
panic("unknown field " + strconv.Quote(field.Name))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ type User struct {
|
|||||||
LuckyNumber *int `json:"luckyNumber"`
|
LuckyNumber *int `json:"luckyNumber"`
|
||||||
Hair *Hair `json:"hair"`
|
Hair *Hair `json:"hair"`
|
||||||
Birthdate *string `json:"birthdate"`
|
Birthdate *string `json:"birthdate"`
|
||||||
|
Friends []*User `json:"friends"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (User) IsBeing() {}
|
func (User) IsBeing() {}
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ var users = []*User{
|
|||||||
{ID: "2", Name: "Raven", LuckyNumber: intptr(-1), Hair: nil},
|
{ID: "2", Name: "Raven", LuckyNumber: intptr(-1), Hair: nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
users[0].Friends = []*User{users[1]} // (obviously a lie, but)
|
||||||
|
users[1].Friends = users // try to crash the system
|
||||||
|
}
|
||||||
|
|
||||||
var animals = []*Animal{
|
var animals = []*Animal{
|
||||||
{
|
{
|
||||||
ID: "3", Name: "Fido", Species: SpeciesDog, Owner: userByID("1"),
|
ID: "3", Name: "Fido", Species: SpeciesDog, Owner: userByID("1"),
|
||||||
|
|||||||
Reference in New Issue
Block a user