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:
@@ -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