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:
|
||||
|
||||
- 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:
|
||||
|
||||
|
||||
+18
-4
@@ -260,9 +260,9 @@ type GetMonopolyPlayersGameWinnerUser struct {
|
||||
// (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
|
||||
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.
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
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?
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user