Add support for "flattening" fragment-spreads (#121)
## Summary:
One common use of fragment spreads is as the entirety of a field's
selection, e.g.
```graphql
query MyQuery {
myField {
...MyFragment
}
}
```
In this case, by default, genqlient generates a wrapper type
`MyQueryMyFieldMyType`, which just embeds `MyFragment`. This makes
sense if you later want to add more fields in addition to the fragment
spread. But if you don't -- and you did the fragment because you want
to share types, it's an extra layer of indirection. (Which becomes
especially onerous if `myField` has list type (`[MyType!]`), such that
it's not just an extra attribute-access to get to `MyFragment`.)
The new option `# @genqlient(flatten: true)` simplifies this situation:
if applied to `myField` is skips the wrapper type;
`MyQueryResponse.MyField` will simply have type `MyFragment` (or
`[]MyFragment`, or whatever). This should hopefully make the `typename`
option, which has more limitations, less necessary.
Note that in #30 the initial idea was to support this for fields as
well. This would require significant additional complexity in the
JSON-(un)marshaling code, and has proven less necessary, so I
implemented this option only for fragment-spreads for now. With that
restriction, it was shockingly simple; we have to hook into a bunch of
different places, but they're all quite simple, since the structure of
the Go types still matches the structure in GraphQL.
Issue: https://github.com/Khan/genqlient/issues/30
## Test plan:
make check
Author: benjaminjkraft
Reviewers: csilvers, dnerdy, aberkan, jvoll, mahtabsabet, MiguelCastillo, StevenACoffman
Required Reviewers:
Approved By: csilvers, dnerdy
Checks: ✅ Test (1.17), ✅ Test (1.16), ✅ Test (1.15), ✅ Test (1.14), ✅ Lint, ✅ Test (1.17), ✅ Test (1.16), ✅ Test (1.15), ✅ Test (1.14), ✅ Lint
Pull Request URL: https://github.com/Khan/genqlient/pull/121
This commit is contained in:
@@ -115,6 +115,15 @@ func (g *generator) convertOperation(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// It's not common to use a fragment-spread for the whole query, but you
|
||||
// can if you want two queries to return the same type!
|
||||
if queryOptions.GetFlatten() {
|
||||
i, err := validateFlattenOption(baseType, operation.SelectionSet, operation.Position)
|
||||
if err == nil {
|
||||
return fields[i].GoType, nil
|
||||
}
|
||||
}
|
||||
|
||||
goType := &goStructType{
|
||||
GoName: name,
|
||||
descriptionInfo: descriptionInfo{
|
||||
@@ -340,6 +349,17 @@ func (g *generator) convertDefinition(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options.GetFlatten() {
|
||||
// As with struct, flatten only applies if valid, important if you
|
||||
// applied it to the whole query.
|
||||
// TODO(benkraft): This is a slightly fragile way to do this;
|
||||
// figure out a good way to do it before/while constructing the
|
||||
// fields, rather than after.
|
||||
i, err := validateFlattenOption(def, selectionSet, pos)
|
||||
if err == nil {
|
||||
return fields[i].GoType, nil
|
||||
}
|
||||
}
|
||||
|
||||
goType := &goStructType{
|
||||
GoName: name,
|
||||
@@ -406,6 +426,14 @@ func (g *generator) convertDefinition(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Flatten can only flatten if there is only one field (plus perhaps
|
||||
// __typename), and it's shared.
|
||||
if options.GetFlatten() {
|
||||
i, err := validateFlattenOption(def, selectionSet, pos)
|
||||
if err == nil {
|
||||
return sharedFields[i].GoType, nil
|
||||
}
|
||||
}
|
||||
|
||||
implementationTypes := g.schema.GetPossibleTypes(def)
|
||||
goType := &goInterfaceType{
|
||||
@@ -705,6 +733,15 @@ func (g *generator) convertNamedFragment(fragment *ast.FragmentDefinition) (goTy
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if directive.GetFlatten() {
|
||||
// Flatten on a fragment-definition is a bit weird -- it makes one
|
||||
// fragment effectively an alias for another -- but no reason we can't
|
||||
// allow it.
|
||||
i, err := validateFlattenOption(typ, fragment.SelectionSet, fragment.Position)
|
||||
if err == nil {
|
||||
return fields[i].GoType, nil
|
||||
}
|
||||
}
|
||||
|
||||
switch typ.Kind {
|
||||
case ast.Object:
|
||||
|
||||
Reference in New Issue
Block a user