diff --git a/_examples/federation/accounts/graph/generated.go b/_examples/federation/accounts/graph/generated.go index 1ac71034fdb..d435d47c089 100644 --- a/_examples/federation/accounts/graph/generated.go +++ b/_examples/federation/accounts/graph/generated.go @@ -2942,15 +2942,27 @@ func (ec *executionContext) __Entity(ctx context.Context, sel ast.SelectionSet, case nil: return graphql.Null case model.EmailHost: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "EmailHost"})) == 0 { + return graphql.Empty{} + } return ec._EmailHost(ctx, sel, &obj) case *model.EmailHost: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "EmailHost"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._EmailHost(ctx, sel, obj) case model.User: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "User"})) == 0 { + return graphql.Empty{} + } return ec._User(ctx, sel, &obj) case *model.User: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "User"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } diff --git a/_examples/federation/products/graph/generated.go b/_examples/federation/products/graph/generated.go index 848a8e8d679..fe6215b2f34 100644 --- a/_examples/federation/products/graph/generated.go +++ b/_examples/federation/products/graph/generated.go @@ -3131,15 +3131,27 @@ func (ec *executionContext) __Entity(ctx context.Context, sel ast.SelectionSet, case nil: return graphql.Null case model.Manufacturer: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "Manufacturer"})) == 0 { + return graphql.Empty{} + } return ec._Manufacturer(ctx, sel, &obj) case *model.Manufacturer: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "Manufacturer"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Manufacturer(ctx, sel, obj) case model.Product: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "Product"})) == 0 { + return graphql.Empty{} + } return ec._Product(ctx, sel, &obj) case *model.Product: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "Product"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } diff --git a/_examples/federation/reviews/graph/generated.go b/_examples/federation/reviews/graph/generated.go index f77205bbc99..72ba7d7e4ae 100644 --- a/_examples/federation/reviews/graph/generated.go +++ b/_examples/federation/reviews/graph/generated.go @@ -3298,29 +3298,53 @@ func (ec *executionContext) __Entity(ctx context.Context, sel ast.SelectionSet, case nil: return graphql.Null case model.EmailHost: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "EmailHost"})) == 0 { + return graphql.Empty{} + } return ec._EmailHost(ctx, sel, &obj) case *model.EmailHost: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "EmailHost"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._EmailHost(ctx, sel, obj) case model.Manufacturer: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "Manufacturer"})) == 0 { + return graphql.Empty{} + } return ec._Manufacturer(ctx, sel, &obj) case *model.Manufacturer: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "Manufacturer"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Manufacturer(ctx, sel, obj) case model.Product: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "Product"})) == 0 { + return graphql.Empty{} + } return ec._Product(ctx, sel, &obj) case *model.Product: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "Product"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Product(ctx, sel, obj) case model.User: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "User"})) == 0 { + return graphql.Empty{} + } return ec._User(ctx, sel, &obj) case *model.User: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "User"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } diff --git a/_examples/selection/generated.go b/_examples/selection/generated.go index 79f349b9973..a1d0090fa8f 100644 --- a/_examples/selection/generated.go +++ b/_examples/selection/generated.go @@ -2604,15 +2604,27 @@ func (ec *executionContext) _Event(ctx context.Context, sel ast.SelectionSet, ob case nil: return graphql.Null case Post: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Event", "Post"})) == 0 { + return graphql.Empty{} + } return ec._Post(ctx, sel, &obj) case *Post: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Event", "Post"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Post(ctx, sel, obj) case Like: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Event", "Like"})) == 0 { + return graphql.Empty{} + } return ec._Like(ctx, sel, &obj) case *Like: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Event", "Like"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } diff --git a/_examples/starwars/generated/exec.go b/_examples/starwars/generated/exec.go index c6f909fd0a8..ae184ca2f39 100644 --- a/_examples/starwars/generated/exec.go +++ b/_examples/starwars/generated/exec.go @@ -4924,15 +4924,27 @@ func (ec *executionContext) _Character(ctx context.Context, sel ast.SelectionSet case nil: return graphql.Null case models.Human: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Character", "Human"})) == 0 { + return graphql.Empty{} + } return ec._Human(ctx, sel, &obj) case *models.Human: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Character", "Human"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Human(ctx, sel, obj) case models.Droid: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Character", "Droid"})) == 0 { + return graphql.Empty{} + } return ec._Droid(ctx, sel, &obj) case *models.Droid: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Character", "Droid"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } @@ -4947,22 +4959,40 @@ func (ec *executionContext) _SearchResult(ctx context.Context, sel ast.Selection case nil: return graphql.Null case models.Human: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"SearchResult", "Human"})) == 0 { + return graphql.Empty{} + } return ec._Human(ctx, sel, &obj) case *models.Human: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"SearchResult", "Human"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Human(ctx, sel, obj) case models.Droid: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"SearchResult", "Droid"})) == 0 { + return graphql.Empty{} + } return ec._Droid(ctx, sel, &obj) case *models.Droid: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"SearchResult", "Droid"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Droid(ctx, sel, obj) case models.Starship: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"SearchResult", "Starship"})) == 0 { + return graphql.Empty{} + } return ec._Starship(ctx, sel, &obj) case *models.Starship: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"SearchResult", "Starship"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } diff --git a/_examples/type-system-extension/generated.go b/_examples/type-system-extension/generated.go index f484dfd1e83..0255ad7830b 100644 --- a/_examples/type-system-extension/generated.go +++ b/_examples/type-system-extension/generated.go @@ -2760,8 +2760,14 @@ func (ec *executionContext) _Data(ctx context.Context, sel ast.SelectionSet, obj case nil: return graphql.Null case Todo: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Data", "Todo"})) == 0 { + return graphql.Empty{} + } return ec._Todo(ctx, sel, &obj) case *Todo: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Data", "Todo"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } @@ -2776,8 +2782,14 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj case nil: return graphql.Null case Todo: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Node", "Todo"})) == 0 { + return graphql.Empty{} + } return ec._Todo(ctx, sel, &obj) case *Todo: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Node", "Todo"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } diff --git a/codegen/interface.gotpl b/codegen/interface.gotpl index e9d560c8f64..f8055cd1b08 100644 --- a/codegen/interface.gotpl +++ b/codegen/interface.gotpl @@ -6,6 +6,9 @@ func (ec *executionContext) _{{$interface.Name}}(ctx context.Context, sel ast.Se return graphql.Null {{- range $implementor := $interface.Implementors }} case {{$implementor.Type | ref}}: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"{{$interface.Type | typeName }}", "{{$implementor.Type | typeName}}"})) == 0 { + return graphql.Empty{} + } {{- if $implementor.CanBeNil }} if obj == nil { return graphql.Null diff --git a/codegen/templates/templates.go b/codegen/templates/templates.go index 4de30761513..185d2e93095 100644 --- a/codegen/templates/templates.go +++ b/codegen/templates/templates.go @@ -88,15 +88,15 @@ func Render(cfg Options) error { } roots := make([]string, 0, len(t.Templates())) - for _, template := range t.Templates() { + for _, templ := range t.Templates() { // templates that end with _.gotpl are special files we don't want to include - if strings.HasSuffix(template.Name(), "_.gotpl") || + if strings.HasSuffix(templ.Name(), "_.gotpl") || // filter out templates added with {{ template xxx }} syntax inside the template file - !strings.HasSuffix(template.Name(), ".gotpl") { + !strings.HasSuffix(templ.Name(), ".gotpl") { continue } - roots = append(roots, template.Name()) + roots = append(roots, templ.Name()) } // then execute all the important looking ones in order, adding them to the same file @@ -220,6 +220,7 @@ func Funcs() template.FuncMap { "render": func(filename string, tpldata any) (*bytes.Buffer, error) { return render(resolveName(filename, 0), tpldata) }, + "typeName": typeName, } } @@ -647,6 +648,16 @@ func resolveName(name string, skip int) string { return filepath.Join(filepath.Dir(callerFile), name) } +func typeName(t types.Type) string { + name := types.TypeString(t, func(*types.Package) string { + return "" + }) + if name != "" && strings.HasPrefix(name, "*") { + return name[1:] + } + return name +} + func render(filename string, tpldata any) (*bytes.Buffer, error) { t := template.New("").Funcs(Funcs()) @@ -672,7 +683,7 @@ func write(filename string, b []byte, packages *code.Packages) error { formatted, err := imports.Prune(filename, b, packages) if err != nil { - fmt.Fprintf(os.Stderr, "gofmt failed on %s: %s\n", filepath.Base(filename), err.Error()) + _, _ = fmt.Fprintf(os.Stderr, "gofmt failed on %s: %s\n", filepath.Base(filename), err.Error()) formatted = b } diff --git a/codegen/templates/templates_test.go b/codegen/templates/templates_test.go index 6ca3d824842..d8b8ebf7b3b 100644 --- a/codegen/templates/templates_test.go +++ b/codegen/templates/templates_test.go @@ -3,6 +3,7 @@ package templates import ( "embed" "fmt" + "go/types" "os" "path/filepath" "testing" @@ -355,3 +356,28 @@ func TestRenderFS(t *testing.T) { // don't look at last character since it's \n on Linux and \r\n on Windows assert.Equal(t, expectedString, actualContentsStr[:len(expectedString)]) } + +func TestTypeName(t *testing.T) { + testType := types.NewNamed( + types.NewTypeName(0, types.NewPackage( + "github.com/99designs/gqlgen/codegen/templates", + "templates", + ), "testType", nil), + types.NewStruct(nil, nil), + nil, + ) + + tests := []struct { + input types.Type + expected string + }{ + {testType, "testType"}, + {types.NewPointer(testType), "testType"}, + {types.NewPointer(types.NewPointer(testType)), "*testType"}, + } + + for _, test := range tests { + result := typeName(test.input) + assert.Equal(t, test.expected, result) + } +} diff --git a/codegen/testserver/followschema/generated_test.go b/codegen/testserver/followschema/generated_test.go index 1792b0b4e40..292ba08b0fd 100644 --- a/codegen/testserver/followschema/generated_test.go +++ b/codegen/testserver/followschema/generated_test.go @@ -37,6 +37,12 @@ func TestUnionFragments(t *testing.T) { resolvers.QueryResolver.ShapeUnion = func(ctx context.Context) (ShapeUnion, error) { return &Circle{Radius: 32}, nil } + resolvers.QueryResolver.Shapes = func(ctx context.Context) ([]Shape, error) { + return []Shape{ + &Circle{Radius: 45}, + &Circle{Radius: 54}, + }, nil + } srv := handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolvers})) c := client.New(srv) @@ -78,4 +84,23 @@ func TestUnionFragments(t *testing.T) { `, &resp) require.NotEmpty(t, resp.ShapeUnion.Radius) }) + + t.Run("without circle", func(t *testing.T) { + var resp struct { + Shapes []struct { + Length, Width float64 + } + } + require.Empty(t, resp.Shapes) + c.MustPost(`query { + shapes { + ... on Rectangle { + length + width + } + } + } + `, &resp) + require.Empty(t, resp.Shapes) + }) } diff --git a/codegen/testserver/followschema/interfaces.generated.go b/codegen/testserver/followschema/interfaces.generated.go index 6c76eb570cf..689fa5ee878 100644 --- a/codegen/testserver/followschema/interfaces.generated.go +++ b/codegen/testserver/followschema/interfaces.generated.go @@ -1202,27 +1202,48 @@ func (ec *executionContext) _Animal(ctx context.Context, sel ast.SelectionSet, o case nil: return graphql.Null case Horse: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Animal", "Horse"})) == 0 { + return graphql.Empty{} + } return ec._Horse(ctx, sel, &obj) case *Horse: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Animal", "Horse"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Horse(ctx, sel, obj) case Dog: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Animal", "Dog"})) == 0 { + return graphql.Empty{} + } return ec._Dog(ctx, sel, &obj) case *Dog: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Animal", "Dog"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Dog(ctx, sel, obj) case Cat: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Animal", "Cat"})) == 0 { + return graphql.Empty{} + } return ec._Cat(ctx, sel, &obj) case *Cat: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Animal", "Cat"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Cat(ctx, sel, obj) case Mammalian: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Animal", "Mammalian"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } @@ -1237,8 +1258,14 @@ func (ec *executionContext) _Mammalian(ctx context.Context, sel ast.SelectionSet case nil: return graphql.Null case Horse: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Mammalian", "Horse"})) == 0 { + return graphql.Empty{} + } return ec._Horse(ctx, sel, &obj) case *Horse: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Mammalian", "Horse"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } @@ -1253,11 +1280,17 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj case nil: return graphql.Null case *ConcreteNodeA: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Node", "ConcreteNodeA"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._ConcreteNodeA(ctx, sel, obj) case ConcreteNodeInterface: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Node", "ConcreteNodeInterface"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } @@ -1272,11 +1305,17 @@ func (ec *executionContext) _Shape(ctx context.Context, sel ast.SelectionSet, ob case nil: return graphql.Null case *Circle: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Shape", "Circle"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Circle(ctx, sel, obj) case *Rectangle: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Shape", "Rectangle"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } @@ -1291,11 +1330,17 @@ func (ec *executionContext) _ShapeUnion(ctx context.Context, sel ast.SelectionSe case nil: return graphql.Null case *Circle: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"ShapeUnion", "Circle"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Circle(ctx, sel, obj) case *Rectangle: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"ShapeUnion", "Rectangle"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } diff --git a/codegen/testserver/followschema/useptr.generated.go b/codegen/testserver/followschema/useptr.generated.go index 7a5f999d675..30617119279 100644 --- a/codegen/testserver/followschema/useptr.generated.go +++ b/codegen/testserver/followschema/useptr.generated.go @@ -122,15 +122,27 @@ func (ec *executionContext) _TestUnion(ctx context.Context, sel ast.SelectionSet case nil: return graphql.Null case A: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"TestUnion", "A"})) == 0 { + return graphql.Empty{} + } return ec._A(ctx, sel, &obj) case *A: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"TestUnion", "A"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._A(ctx, sel, obj) case B: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"TestUnion", "B"})) == 0 { + return graphql.Empty{} + } return ec._B(ctx, sel, &obj) case *B: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"TestUnion", "B"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } diff --git a/codegen/testserver/followschema/validtypes.generated.go b/codegen/testserver/followschema/validtypes.generated.go index ed5bfcc9075..7c73ddd0e6d 100644 --- a/codegen/testserver/followschema/validtypes.generated.go +++ b/codegen/testserver/followschema/validtypes.generated.go @@ -759,15 +759,27 @@ func (ec *executionContext) _Content_Child(ctx context.Context, sel ast.Selectio case nil: return graphql.Null case ContentUser: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"ContentChild", "ContentUser"})) == 0 { + return graphql.Empty{} + } return ec._Content_User(ctx, sel, &obj) case *ContentUser: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"ContentChild", "ContentUser"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Content_User(ctx, sel, obj) case ContentPost: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"ContentChild", "ContentPost"})) == 0 { + return graphql.Empty{} + } return ec._Content_Post(ctx, sel, &obj) case *ContentPost: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"ContentChild", "ContentPost"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } diff --git a/codegen/testserver/singlefile/generated.go b/codegen/testserver/singlefile/generated.go index e7f91ec9516..cd1612fd229 100644 --- a/codegen/testserver/singlefile/generated.go +++ b/codegen/testserver/singlefile/generated.go @@ -16065,27 +16065,48 @@ func (ec *executionContext) _Animal(ctx context.Context, sel ast.SelectionSet, o case nil: return graphql.Null case Horse: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Animal", "Horse"})) == 0 { + return graphql.Empty{} + } return ec._Horse(ctx, sel, &obj) case *Horse: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Animal", "Horse"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Horse(ctx, sel, obj) case Dog: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Animal", "Dog"})) == 0 { + return graphql.Empty{} + } return ec._Dog(ctx, sel, &obj) case *Dog: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Animal", "Dog"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Dog(ctx, sel, obj) case Cat: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Animal", "Cat"})) == 0 { + return graphql.Empty{} + } return ec._Cat(ctx, sel, &obj) case *Cat: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Animal", "Cat"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Cat(ctx, sel, obj) case Mammalian: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Animal", "Mammalian"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } @@ -16100,15 +16121,27 @@ func (ec *executionContext) _Content_Child(ctx context.Context, sel ast.Selectio case nil: return graphql.Null case ContentUser: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"ContentChild", "ContentUser"})) == 0 { + return graphql.Empty{} + } return ec._Content_User(ctx, sel, &obj) case *ContentUser: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"ContentChild", "ContentUser"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Content_User(ctx, sel, obj) case ContentPost: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"ContentChild", "ContentPost"})) == 0 { + return graphql.Empty{} + } return ec._Content_Post(ctx, sel, &obj) case *ContentPost: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"ContentChild", "ContentPost"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } @@ -16123,8 +16156,14 @@ func (ec *executionContext) _Mammalian(ctx context.Context, sel ast.SelectionSet case nil: return graphql.Null case Horse: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Mammalian", "Horse"})) == 0 { + return graphql.Empty{} + } return ec._Horse(ctx, sel, &obj) case *Horse: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Mammalian", "Horse"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } @@ -16139,11 +16178,17 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj case nil: return graphql.Null case *ConcreteNodeA: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Node", "ConcreteNodeA"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._ConcreteNodeA(ctx, sel, obj) case ConcreteNodeInterface: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Node", "ConcreteNodeInterface"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } @@ -16158,11 +16203,17 @@ func (ec *executionContext) _Shape(ctx context.Context, sel ast.SelectionSet, ob case nil: return graphql.Null case *Circle: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Shape", "Circle"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Circle(ctx, sel, obj) case *Rectangle: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Shape", "Rectangle"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } @@ -16177,11 +16228,17 @@ func (ec *executionContext) _ShapeUnion(ctx context.Context, sel ast.SelectionSe case nil: return graphql.Null case *Circle: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"ShapeUnion", "Circle"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Circle(ctx, sel, obj) case *Rectangle: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"ShapeUnion", "Rectangle"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } @@ -16196,15 +16253,27 @@ func (ec *executionContext) _TestUnion(ctx context.Context, sel ast.SelectionSet case nil: return graphql.Null case A: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"TestUnion", "A"})) == 0 { + return graphql.Empty{} + } return ec._A(ctx, sel, &obj) case *A: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"TestUnion", "A"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._A(ctx, sel, obj) case B: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"TestUnion", "B"})) == 0 { + return graphql.Empty{} + } return ec._B(ctx, sel, &obj) case *B: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"TestUnion", "B"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } diff --git a/codegen/testserver/singlefile/generated_test.go b/codegen/testserver/singlefile/generated_test.go index 63a6a4f2a1b..36b755b6b2e 100644 --- a/codegen/testserver/singlefile/generated_test.go +++ b/codegen/testserver/singlefile/generated_test.go @@ -37,6 +37,12 @@ func TestUnionFragments(t *testing.T) { resolvers.QueryResolver.ShapeUnion = func(ctx context.Context) (ShapeUnion, error) { return &Circle{Radius: 32}, nil } + resolvers.QueryResolver.Shapes = func(ctx context.Context) ([]Shape, error) { + return []Shape{ + &Circle{Radius: 45}, + &Circle{Radius: 54}, + }, nil + } srv := handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolvers})) c := client.New(srv) @@ -78,4 +84,23 @@ func TestUnionFragments(t *testing.T) { `, &resp) require.NotEmpty(t, resp.ShapeUnion.Radius) }) + + t.Run("without circle", func(t *testing.T) { + var resp struct { + Shapes []struct { + Length, Width float64 + } + } + require.Empty(t, resp.Shapes) + c.MustPost(`query { + shapes { + ... on Rectangle { + length + width + } + } + } + `, &resp) + require.Empty(t, resp.Shapes) + }) } diff --git a/graphql/jsonw.go b/graphql/jsonw.go index 16bb63b730d..636476aaebd 100644 --- a/graphql/jsonw.go +++ b/graphql/jsonw.go @@ -72,11 +72,16 @@ type Array []Marshaler func (a Array) MarshalGQL(writer io.Writer) { writer.Write(openBracket) - for i, val := range a { - if i != 0 { + var notEmpty bool + for _, val := range a { + if _, ok := val.(Empty); ok { + continue + } + if notEmpty { writer.Write(comma) } val.MarshalGQL(writer) + notEmpty = true } writer.Write(closeBracket) } @@ -87,7 +92,15 @@ func (l lit) MarshalGQL(w io.Writer) { w.Write(l.b) } -func (l lit) MarshalGQLContext(ctx context.Context, w io.Writer) error { +func (l lit) MarshalGQLContext(_ context.Context, w io.Writer) error { w.Write(l.b) return nil } + +type Empty struct{} + +func (e Empty) MarshalGQL(_ io.Writer) {} + +func (e Empty) MarshalGQLContext(_ context.Context, _ io.Writer) error { + return nil +} diff --git a/graphql/recovery.go b/graphql/recovery.go index 4aae69195d1..009c25560c6 100644 --- a/graphql/recovery.go +++ b/graphql/recovery.go @@ -11,7 +11,7 @@ import ( type RecoverFunc func(ctx context.Context, err any) (userMessage error) -func DefaultRecover(ctx context.Context, err any) error { +func DefaultRecover(_ context.Context, err any) error { fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr) debug.PrintStack() diff --git a/plugin/federation/testdata/entityresolver/generated/exec.go b/plugin/federation/testdata/entityresolver/generated/exec.go index 8b13a61bb7f..9b5289be35c 100644 --- a/plugin/federation/testdata/entityresolver/generated/exec.go +++ b/plugin/federation/testdata/entityresolver/generated/exec.go @@ -5946,99 +5946,183 @@ func (ec *executionContext) __Entity(ctx context.Context, sel ast.SelectionSet, case nil: return graphql.Null case model.Hello: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "Hello"})) == 0 { + return graphql.Empty{} + } return ec._Hello(ctx, sel, &obj) case *model.Hello: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "Hello"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Hello(ctx, sel, obj) case model.HelloMultiSingleKeys: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "HelloMultiSingleKeys"})) == 0 { + return graphql.Empty{} + } return ec._HelloMultiSingleKeys(ctx, sel, &obj) case *model.HelloMultiSingleKeys: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "HelloMultiSingleKeys"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._HelloMultiSingleKeys(ctx, sel, obj) case model.HelloWithErrors: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "HelloWithErrors"})) == 0 { + return graphql.Empty{} + } return ec._HelloWithErrors(ctx, sel, &obj) case *model.HelloWithErrors: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "HelloWithErrors"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._HelloWithErrors(ctx, sel, obj) case model.MultiHello: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiHello"})) == 0 { + return graphql.Empty{} + } return ec._MultiHello(ctx, sel, &obj) case *model.MultiHello: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiHello"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._MultiHello(ctx, sel, obj) case model.MultiHelloMultipleRequires: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiHelloMultipleRequires"})) == 0 { + return graphql.Empty{} + } return ec._MultiHelloMultipleRequires(ctx, sel, &obj) case *model.MultiHelloMultipleRequires: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiHelloMultipleRequires"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._MultiHelloMultipleRequires(ctx, sel, obj) case model.MultiHelloRequires: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiHelloRequires"})) == 0 { + return graphql.Empty{} + } return ec._MultiHelloRequires(ctx, sel, &obj) case *model.MultiHelloRequires: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiHelloRequires"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._MultiHelloRequires(ctx, sel, obj) case model.MultiHelloWithError: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiHelloWithError"})) == 0 { + return graphql.Empty{} + } return ec._MultiHelloWithError(ctx, sel, &obj) case *model.MultiHelloWithError: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiHelloWithError"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._MultiHelloWithError(ctx, sel, obj) case model.MultiPlanetRequiresNested: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiPlanetRequiresNested"})) == 0 { + return graphql.Empty{} + } return ec._MultiPlanetRequiresNested(ctx, sel, &obj) case *model.MultiPlanetRequiresNested: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiPlanetRequiresNested"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._MultiPlanetRequiresNested(ctx, sel, obj) case model.PlanetMultipleRequires: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "PlanetMultipleRequires"})) == 0 { + return graphql.Empty{} + } return ec._PlanetMultipleRequires(ctx, sel, &obj) case *model.PlanetMultipleRequires: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "PlanetMultipleRequires"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._PlanetMultipleRequires(ctx, sel, obj) case model.PlanetRequires: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "PlanetRequires"})) == 0 { + return graphql.Empty{} + } return ec._PlanetRequires(ctx, sel, &obj) case *model.PlanetRequires: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "PlanetRequires"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._PlanetRequires(ctx, sel, obj) case model.PlanetRequiresNested: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "PlanetRequiresNested"})) == 0 { + return graphql.Empty{} + } return ec._PlanetRequiresNested(ctx, sel, &obj) case *model.PlanetRequiresNested: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "PlanetRequiresNested"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._PlanetRequiresNested(ctx, sel, obj) case model.World: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "World"})) == 0 { + return graphql.Empty{} + } return ec._World(ctx, sel, &obj) case *model.World: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "World"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._World(ctx, sel, obj) case model.WorldName: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "WorldName"})) == 0 { + return graphql.Empty{} + } return ec._WorldName(ctx, sel, &obj) case *model.WorldName: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "WorldName"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._WorldName(ctx, sel, obj) case model.WorldWithMultipleKeys: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "WorldWithMultipleKeys"})) == 0 { + return graphql.Empty{} + } return ec._WorldWithMultipleKeys(ctx, sel, &obj) case *model.WorldWithMultipleKeys: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "WorldWithMultipleKeys"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } diff --git a/plugin/federation/testdata/explicitrequires/generated/exec.go b/plugin/federation/testdata/explicitrequires/generated/exec.go index 7f30d6ca5e3..e61fddda486 100644 --- a/plugin/federation/testdata/explicitrequires/generated/exec.go +++ b/plugin/federation/testdata/explicitrequires/generated/exec.go @@ -5493,99 +5493,183 @@ func (ec *executionContext) __Entity(ctx context.Context, sel ast.SelectionSet, case nil: return graphql.Null case Hello: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "Hello"})) == 0 { + return graphql.Empty{} + } return ec._Hello(ctx, sel, &obj) case *Hello: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "Hello"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._Hello(ctx, sel, obj) case HelloMultiSingleKeys: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "HelloMultiSingleKeys"})) == 0 { + return graphql.Empty{} + } return ec._HelloMultiSingleKeys(ctx, sel, &obj) case *HelloMultiSingleKeys: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "HelloMultiSingleKeys"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._HelloMultiSingleKeys(ctx, sel, obj) case HelloWithErrors: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "HelloWithErrors"})) == 0 { + return graphql.Empty{} + } return ec._HelloWithErrors(ctx, sel, &obj) case *HelloWithErrors: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "HelloWithErrors"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._HelloWithErrors(ctx, sel, obj) case MultiHello: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiHello"})) == 0 { + return graphql.Empty{} + } return ec._MultiHello(ctx, sel, &obj) case *MultiHello: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiHello"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._MultiHello(ctx, sel, obj) case MultiHelloMultipleRequires: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiHelloMultipleRequires"})) == 0 { + return graphql.Empty{} + } return ec._MultiHelloMultipleRequires(ctx, sel, &obj) case *MultiHelloMultipleRequires: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiHelloMultipleRequires"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._MultiHelloMultipleRequires(ctx, sel, obj) case MultiHelloRequires: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiHelloRequires"})) == 0 { + return graphql.Empty{} + } return ec._MultiHelloRequires(ctx, sel, &obj) case *MultiHelloRequires: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiHelloRequires"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._MultiHelloRequires(ctx, sel, obj) case MultiHelloWithError: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiHelloWithError"})) == 0 { + return graphql.Empty{} + } return ec._MultiHelloWithError(ctx, sel, &obj) case *MultiHelloWithError: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiHelloWithError"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._MultiHelloWithError(ctx, sel, obj) case MultiPlanetRequiresNested: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiPlanetRequiresNested"})) == 0 { + return graphql.Empty{} + } return ec._MultiPlanetRequiresNested(ctx, sel, &obj) case *MultiPlanetRequiresNested: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "MultiPlanetRequiresNested"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._MultiPlanetRequiresNested(ctx, sel, obj) case PlanetMultipleRequires: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "PlanetMultipleRequires"})) == 0 { + return graphql.Empty{} + } return ec._PlanetMultipleRequires(ctx, sel, &obj) case *PlanetMultipleRequires: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "PlanetMultipleRequires"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._PlanetMultipleRequires(ctx, sel, obj) case PlanetRequires: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "PlanetRequires"})) == 0 { + return graphql.Empty{} + } return ec._PlanetRequires(ctx, sel, &obj) case *PlanetRequires: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "PlanetRequires"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._PlanetRequires(ctx, sel, obj) case PlanetRequiresNested: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "PlanetRequiresNested"})) == 0 { + return graphql.Empty{} + } return ec._PlanetRequiresNested(ctx, sel, &obj) case *PlanetRequiresNested: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "PlanetRequiresNested"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._PlanetRequiresNested(ctx, sel, obj) case World: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "World"})) == 0 { + return graphql.Empty{} + } return ec._World(ctx, sel, &obj) case *World: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "World"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._World(ctx, sel, obj) case WorldName: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "WorldName"})) == 0 { + return graphql.Empty{} + } return ec._WorldName(ctx, sel, &obj) case *WorldName: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "WorldName"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null } return ec._WorldName(ctx, sel, obj) case WorldWithMultipleKeys: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "WorldWithMultipleKeys"})) == 0 { + return graphql.Empty{} + } return ec._WorldWithMultipleKeys(ctx, sel, &obj) case *WorldWithMultipleKeys: + if len(graphql.CollectFields(ec.OperationContext, sel, []string{"Entity", "WorldWithMultipleKeys"})) == 0 { + return graphql.Empty{} + } if obj == nil { return graphql.Null }