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:
@@ -255,6 +255,177 @@ func (v *AnimalFieldsOwnerUser) __premarshalJSON() (*__premarshalAnimalFieldsOwn
|
||||
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 is implemented by the following types:
|
||||
@@ -387,6 +558,313 @@ type MoreUserFieldsHair struct {
|
||||
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
|
||||
|
||||
const (
|
||||
@@ -679,6 +1157,11 @@ func (v *__queryWithCustomMarshalSliceInput) __premarshalJSON() (*__premarshal__
|
||||
return &retval, nil
|
||||
}
|
||||
|
||||
// __queryWithFlattenInput is used internally by genqlient
|
||||
type __queryWithFlattenInput struct {
|
||||
Ids []string `json:"ids"`
|
||||
}
|
||||
|
||||
// __queryWithFragmentsInput is used internally by genqlient
|
||||
type __queryWithFragmentsInput struct {
|
||||
Ids []string `json:"ids"`
|
||||
@@ -2678,3 +3161,66 @@ fragment MoreUserFields on User {
|
||||
)
|
||||
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])
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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
|
||||
|
||||
@@ -18,6 +18,7 @@ type User implements Being & Lucky {
|
||||
luckyNumber: Int
|
||||
hair: Hair
|
||||
birthdate: Date
|
||||
friends: [User!]!
|
||||
}
|
||||
|
||||
type Hair { color: String } # silly name to confuse the name-generator
|
||||
|
||||
@@ -72,6 +72,7 @@ type ComplexityRoot struct {
|
||||
|
||||
User struct {
|
||||
Birthdate func(childComplexity int) int
|
||||
Friends func(childComplexity int) int
|
||||
Hair func(childComplexity int) int
|
||||
ID 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
|
||||
|
||||
case "User.friends":
|
||||
if e.complexity.User.Friends == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.User.Friends(childComplexity), true
|
||||
|
||||
case "User.hair":
|
||||
if e.complexity.User.Hair == nil {
|
||||
break
|
||||
@@ -358,6 +366,7 @@ type User implements Being & Lucky {
|
||||
luckyNumber: Int
|
||||
hair: Hair
|
||||
birthdate: Date
|
||||
friends: [User!]!
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
defer func() {
|
||||
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)
|
||||
case "birthdate":
|
||||
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:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ type User struct {
|
||||
LuckyNumber *int `json:"luckyNumber"`
|
||||
Hair *Hair `json:"hair"`
|
||||
Birthdate *string `json:"birthdate"`
|
||||
Friends []*User `json:"friends"`
|
||||
}
|
||||
|
||||
func (User) IsBeing() {}
|
||||
|
||||
@@ -21,6 +21,11 @@ var users = []*User{
|
||||
{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{
|
||||
{
|
||||
ID: "3", Name: "Fido", Species: SpeciesDog, Owner: userByID("1"),
|
||||
|
||||
Reference in New Issue
Block a user