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:
Ben Kraft
2021-09-29 17:52:06 -07:00
committed by GitHub
parent f4c981031e
commit c6d087c29b
21 changed files with 1246 additions and 9 deletions
+35 -1
View File
@@ -82,6 +82,40 @@ directive genqlient(
# fragment, you'll have to remove this option, and the types will change.
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
# genqlient-generated type.
#
@@ -128,7 +162,7 @@ directive genqlient(
# 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
# 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,
# typename affects the overall response type, rather than being propagated