diff --git a/codegen/interface.gotpl b/codegen/interface.gotpl index 47145500e48..bfb42b25d64 100644 --- a/codegen/interface.gotpl +++ b/codegen/interface.gotpl @@ -10,6 +10,9 @@ func (ec *executionContext) _{{$interface.Name}}(ctx context.Context, sel ast.Se return ec._{{$implementor.Name}}(ctx, sel, &obj) {{- end}} case *{{$implementor.Type | ref}}: + if obj == nil { + return graphql.Null + } return ec._{{$implementor.Name}}(ctx, sel, obj) {{- end }} default: diff --git a/codegen/testserver/generated.go b/codegen/testserver/generated.go index 888a61bdf6d..739ca301b0b 100644 --- a/codegen/testserver/generated.go +++ b/codegen/testserver/generated.go @@ -64,6 +64,8 @@ type DirectiveRoot struct { MakeNil func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) + MakeTypedNil func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) + Range func(ctx context.Context, obj interface{}, next graphql.Resolver, min *int, max *int) (res interface{}, err error) ToNull func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) @@ -96,6 +98,11 @@ type ComplexityRoot struct { ID func(childComplexity int) int } + Cat struct { + CatBreed func(childComplexity int) int + Species func(childComplexity int) int + } + CheckIssue896 struct { ID func(childComplexity int) int } @@ -113,6 +120,11 @@ type ComplexityRoot struct { Foo func(childComplexity int) int } + Dog struct { + DogBreed func(childComplexity int) int + Species func(childComplexity int) int + } + EmbeddedCase1 struct { ExportedEmbeddedPointerExportedMethod func(childComplexity int) int } @@ -225,6 +237,7 @@ type ComplexityRoot struct { } Query struct { + Animal func(childComplexity int) int Autobind func(childComplexity int) int Collision func(childComplexity int) int DefaultScalar func(childComplexity int, arg string) int @@ -257,6 +270,7 @@ type ComplexityRoot struct { NestedInputs func(childComplexity int, input [][]*OuterInput) int NestedOutputs func(childComplexity int) int NoShape func(childComplexity int) int + NoShapeTypedNil func(childComplexity int) int NullableArg func(childComplexity int, arg *int) int OptionalUnion func(childComplexity int) int Overlapping func(childComplexity int) int @@ -394,6 +408,8 @@ type QueryResolver interface { EnumInInput(ctx context.Context, input *InputWithEnumValue) (EnumTest, error) Shapes(ctx context.Context) ([]Shape, error) NoShape(ctx context.Context) (Shape, error) + NoShapeTypedNil(ctx context.Context) (Shape, error) + Animal(ctx context.Context) (Animal, error) Issue896a(ctx context.Context) ([]*CheckIssue896, error) MapStringInterface(ctx context.Context, in map[string]interface{}) (map[string]interface{}, error) MapNestedStringInterface(ctx context.Context, in *NestedMapInput) (map[string]interface{}, error) @@ -503,6 +519,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.B.ID(childComplexity), true + case "Cat.catBreed": + if e.complexity.Cat.CatBreed == nil { + break + } + + return e.complexity.Cat.CatBreed(childComplexity), true + + case "Cat.species": + if e.complexity.Cat.Species == nil { + break + } + + return e.complexity.Cat.Species(childComplexity), true + case "CheckIssue896.id": if e.complexity.CheckIssue896.ID == nil { break @@ -538,6 +568,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ContentUser.Foo(childComplexity), true + case "Dog.dogBreed": + if e.complexity.Dog.DogBreed == nil { + break + } + + return e.complexity.Dog.DogBreed(childComplexity), true + + case "Dog.species": + if e.complexity.Dog.Species == nil { + break + } + + return e.complexity.Dog.Species(childComplexity), true + case "EmbeddedCase1.exportedEmbeddedPointerExportedMethod": if e.complexity.EmbeddedCase1.ExportedEmbeddedPointerExportedMethod == nil { break @@ -842,6 +886,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.PrimitiveString.Value(childComplexity), true + case "Query.animal": + if e.complexity.Query.Animal == nil { + break + } + + return e.complexity.Query.Animal(childComplexity), true + case "Query.autobind": if e.complexity.Query.Autobind == nil { break @@ -1136,6 +1187,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.NoShape(childComplexity), true + case "Query.noShapeTypedNil": + if e.complexity.Query.NoShapeTypedNil == nil { + break + } + + return e.complexity.Query.NoShapeTypedNil(childComplexity), true + case "Query.nullableArg": if e.complexity.Query.NullableArg == nil { break @@ -1656,6 +1714,22 @@ extend type Query { &ast.Source{Name: "interfaces.graphql", Input: `extend type Query { shapes: [Shape] noShape: Shape @makeNil + noShapeTypedNil: Shape @makeTypedNil + animal: Animal @makeTypedNil +} + +interface Animal { + species: String! +} + +type Dog implements Animal { + species: String! + dogBreed: String! +} + +type Cat implements Animal { + species: String! + catBreed: String! } interface Shape { @@ -1673,6 +1747,7 @@ type Rectangle implements Shape { union ShapeUnion @goModel(model:"testserver.ShapeUnion") = Circle | Rectangle directive @makeNil on FIELD_DEFINITION +directive @makeTypedNil on FIELD_DEFINITION `}, &ast.Source{Name: "issue896.graphql", Input: `# This example should build stable output. If the file content starts # alternating nondeterministically between two outputs, then see @@ -3197,6 +3272,74 @@ func (ec *executionContext) _B_id(ctx context.Context, field graphql.CollectedFi return ec.marshalNID2string(ctx, field.Selections, res) } +func (ec *executionContext) _Cat_species(ctx context.Context, field graphql.CollectedField, obj *Cat) (ret graphql.Marshaler) { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() + rctx := &graphql.ResolverContext{ + Object: "Cat", + Field: field, + Args: nil, + IsMethod: false, + } + ctx = graphql.WithResolverContext(ctx, rctx) + ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Species, nil + }) + + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _Cat_catBreed(ctx context.Context, field graphql.CollectedField, obj *Cat) (ret graphql.Marshaler) { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() + rctx := &graphql.ResolverContext{ + Object: "Cat", + Field: field, + Args: nil, + IsMethod: false, + } + ctx = graphql.WithResolverContext(ctx, rctx) + ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.CatBreed, nil + }) + + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalNString2string(ctx, field.Selections, res) +} + func (ec *executionContext) _CheckIssue896_id(ctx context.Context, field graphql.CollectedField, obj *CheckIssue896) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { @@ -3352,6 +3495,74 @@ func (ec *executionContext) _Content_User_foo(ctx context.Context, field graphql return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } +func (ec *executionContext) _Dog_species(ctx context.Context, field graphql.CollectedField, obj *Dog) (ret graphql.Marshaler) { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() + rctx := &graphql.ResolverContext{ + Object: "Dog", + Field: field, + Args: nil, + IsMethod: false, + } + ctx = graphql.WithResolverContext(ctx, rctx) + ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Species, nil + }) + + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _Dog_dogBreed(ctx context.Context, field graphql.CollectedField, obj *Dog) (ret graphql.Marshaler) { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() + rctx := &graphql.ResolverContext{ + Object: "Dog", + Field: field, + Args: nil, + IsMethod: false, + } + ctx = graphql.WithResolverContext(ctx, rctx) + ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.DogBreed, nil + }) + + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalNString2string(ctx, field.Selections, res) +} + func (ec *executionContext) _EmbeddedCase1_exportedEmbeddedPointerExportedMethod(ctx context.Context, field graphql.CollectedField, obj *EmbeddedCase1) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { @@ -6089,6 +6300,108 @@ func (ec *executionContext) _Query_noShape(ctx context.Context, field graphql.Co return ec.marshalOShape2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐShape(ctx, field.Selections, res) } +func (ec *executionContext) _Query_noShapeTypedNil(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() + rctx := &graphql.ResolverContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + ctx = graphql.WithResolverContext(ctx, rctx) + ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) + resTmp := ec._fieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().NoShapeTypedNil(rctx) + } + directive1 := func(ctx context.Context) (interface{}, error) { + if ec.directives.MakeTypedNil == nil { + return nil, errors.New("directive makeTypedNil is not implemented") + } + return ec.directives.MakeTypedNil(ctx, nil, directive0) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, err + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(Shape); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be github.com/99designs/gqlgen/codegen/testserver.Shape`, tmp) + }) + + if resTmp == nil { + return graphql.Null + } + res := resTmp.(Shape) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalOShape2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐShape(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query_animal(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + ec.Tracer.EndFieldExecution(ctx) + }() + rctx := &graphql.ResolverContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + ctx = graphql.WithResolverContext(ctx, rctx) + ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) + resTmp := ec._fieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Animal(rctx) + } + directive1 := func(ctx context.Context) (interface{}, error) { + if ec.directives.MakeTypedNil == nil { + return nil, errors.New("directive makeTypedNil is not implemented") + } + return ec.directives.MakeTypedNil(ctx, nil, directive0) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, err + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(Animal); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be github.com/99designs/gqlgen/codegen/testserver.Animal`, tmp) + }) + + if resTmp == nil { + return graphql.Null + } + res := resTmp.(Animal) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalOAnimal2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐAnimal(ctx, field.Selections, res) +} + func (ec *executionContext) _Query_issue896a(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { @@ -9222,6 +9535,29 @@ func (ec *executionContext) unmarshalInputValidInput(ctx context.Context, obj in // region ************************** interface.gotpl *************************** +func (ec *executionContext) _Animal(ctx context.Context, sel ast.SelectionSet, obj Animal) graphql.Marshaler { + switch obj := (obj).(type) { + case nil: + return graphql.Null + case Dog: + return ec._Dog(ctx, sel, &obj) + case *Dog: + if obj == nil { + return graphql.Null + } + return ec._Dog(ctx, sel, obj) + case Cat: + return ec._Cat(ctx, sel, &obj) + case *Cat: + if obj == nil { + return graphql.Null + } + return ec._Cat(ctx, sel, obj) + default: + panic(fmt.Errorf("unexpected type %T", obj)) + } +} + func (ec *executionContext) _Content_Child(ctx context.Context, sel ast.SelectionSet, obj ContentChild) graphql.Marshaler { switch obj := (obj).(type) { case nil: @@ -9229,10 +9565,16 @@ func (ec *executionContext) _Content_Child(ctx context.Context, sel ast.Selectio case ContentUser: return ec._Content_User(ctx, sel, &obj) case *ContentUser: + if obj == nil { + return graphql.Null + } return ec._Content_User(ctx, sel, obj) case ContentPost: return ec._Content_Post(ctx, sel, &obj) case *ContentPost: + if obj == nil { + return graphql.Null + } return ec._Content_Post(ctx, sel, obj) default: panic(fmt.Errorf("unexpected type %T", obj)) @@ -9244,8 +9586,14 @@ func (ec *executionContext) _Shape(ctx context.Context, sel ast.SelectionSet, ob case nil: return graphql.Null case *Circle: + if obj == nil { + return graphql.Null + } return ec._Circle(ctx, sel, obj) case *Rectangle: + if obj == nil { + return graphql.Null + } return ec._Rectangle(ctx, sel, obj) default: panic(fmt.Errorf("unexpected type %T", obj)) @@ -9257,8 +9605,14 @@ func (ec *executionContext) _ShapeUnion(ctx context.Context, sel ast.SelectionSe case nil: return graphql.Null case *Circle: + if obj == nil { + return graphql.Null + } return ec._Circle(ctx, sel, obj) case *Rectangle: + if obj == nil { + return graphql.Null + } return ec._Rectangle(ctx, sel, obj) default: panic(fmt.Errorf("unexpected type %T", obj)) @@ -9272,10 +9626,16 @@ func (ec *executionContext) _TestUnion(ctx context.Context, sel ast.SelectionSet case A: return ec._A(ctx, sel, &obj) case *A: + if obj == nil { + return graphql.Null + } return ec._A(ctx, sel, obj) case B: return ec._B(ctx, sel, &obj) case *B: + if obj == nil { + return graphql.Null + } return ec._B(ctx, sel, obj) default: panic(fmt.Errorf("unexpected type %T", obj)) @@ -9441,6 +9801,38 @@ func (ec *executionContext) _B(ctx context.Context, sel ast.SelectionSet, obj *B return out } +var catImplementors = []string{"Cat", "Animal"} + +func (ec *executionContext) _Cat(ctx context.Context, sel ast.SelectionSet, obj *Cat) graphql.Marshaler { + fields := graphql.CollectFields(ec.RequestContext, sel, catImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Cat") + case "species": + out.Values[i] = ec._Cat_species(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "catBreed": + out.Values[i] = ec._Cat_catBreed(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var checkIssue896Implementors = []string{"CheckIssue896"} func (ec *executionContext) _CheckIssue896(ctx context.Context, sel ast.SelectionSet, obj *CheckIssue896) graphql.Marshaler { @@ -9539,6 +9931,38 @@ func (ec *executionContext) _Content_User(ctx context.Context, sel ast.Selection return out } +var dogImplementors = []string{"Dog", "Animal"} + +func (ec *executionContext) _Dog(ctx context.Context, sel ast.SelectionSet, obj *Dog) graphql.Marshaler { + fields := graphql.CollectFields(ec.RequestContext, sel, dogImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Dog") + case "species": + out.Values[i] = ec._Dog_species(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "dogBreed": + out.Values[i] = ec._Dog_dogBreed(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var embeddedCase1Implementors = []string{"EmbeddedCase1"} func (ec *executionContext) _EmbeddedCase1(ctx context.Context, sel ast.SelectionSet, obj *EmbeddedCase1) graphql.Marshaler { @@ -10747,6 +11171,28 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr res = ec._Query_noShape(ctx, field) return res }) + case "noShapeTypedNil": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_noShapeTypedNil(ctx, field) + return res + }) + case "animal": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_animal(ctx, field) + return res + }) case "issue896a": field := field out.Concurrently(i, func() (res graphql.Marshaler) { @@ -12331,6 +12777,13 @@ func (ec *executionContext) marshalN__TypeKind2string(ctx context.Context, sel a return res } +func (ec *executionContext) marshalOAnimal2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐAnimal(ctx context.Context, sel ast.SelectionSet, v Animal) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._Animal(ctx, sel, v) +} + func (ec *executionContext) marshalOAutobind2githubᚗcomᚋ99designsᚋgqlgenᚋcodegenᚋtestserverᚐAutobind(ctx context.Context, sel ast.SelectionSet, v Autobind) graphql.Marshaler { return ec._Autobind(ctx, sel, &v) } diff --git a/codegen/testserver/interfaces.graphql b/codegen/testserver/interfaces.graphql index 5a66c236a23..177e2a096c9 100644 --- a/codegen/testserver/interfaces.graphql +++ b/codegen/testserver/interfaces.graphql @@ -1,6 +1,22 @@ extend type Query { shapes: [Shape] noShape: Shape @makeNil + noShapeTypedNil: Shape @makeTypedNil + animal: Animal @makeTypedNil +} + +interface Animal { + species: String! +} + +type Dog implements Animal { + species: String! + dogBreed: String! +} + +type Cat implements Animal { + species: String! + catBreed: String! } interface Shape { @@ -18,3 +34,4 @@ type Rectangle implements Shape { union ShapeUnion @goModel(model:"testserver.ShapeUnion") = Circle | Rectangle directive @makeNil on FIELD_DEFINITION +directive @makeTypedNil on FIELD_DEFINITION diff --git a/codegen/testserver/interfaces_test.go b/codegen/testserver/interfaces_test.go index 88c3fe61ee9..62d1c26210c 100644 --- a/codegen/testserver/interfaces_test.go +++ b/codegen/testserver/interfaces_test.go @@ -40,4 +40,52 @@ func TestInterfaces(t *testing.T) { var resp interface{} c.MustPost(`{ noShape { area } }`, &resp) }) + + t.Run("interfaces can be typed nil", func(t *testing.T) { + resolvers := &Stub{} + resolvers.QueryResolver.NoShapeTypedNil = func(ctx context.Context) (shapes Shape, e error) { + panic("should not be called") + } + + srv := handler.GraphQL( + NewExecutableSchema(Config{ + Resolvers: resolvers, + Directives: DirectiveRoot{ + MakeTypedNil: func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) { + var circle *Circle + return circle, nil + }, + }, + }), + ) + + c := client.New(srv) + + var resp interface{} + c.MustPost(`{ noShapeTypedNil { area } }`, &resp) + }) + + t.Run("interfaces can be nil (test with code-generated resolver)", func(t *testing.T) { + resolvers := &Stub{} + resolvers.QueryResolver.Animal = func(ctx context.Context) (animal Animal, e error) { + panic("should not be called") + } + + srv := handler.GraphQL( + NewExecutableSchema(Config{ + Resolvers: resolvers, + Directives: DirectiveRoot{ + MakeTypedNil: func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) { + var dog *Dog // return a typed nil, not just nil + return dog, nil + }, + }, + }), + ) + + c := client.New(srv) + + var resp interface{} + c.MustPost(`{ animal { species } }`, &resp) + }) } diff --git a/codegen/testserver/models-gen.go b/codegen/testserver/models-gen.go index ecdb402ce10..29f360cfa6e 100644 --- a/codegen/testserver/models-gen.go +++ b/codegen/testserver/models-gen.go @@ -9,6 +9,10 @@ import ( "time" ) +type Animal interface { + IsAnimal() +} + type ContentChild interface { IsContentChild() } @@ -37,6 +41,13 @@ type B struct { func (B) IsTestUnion() {} +type Cat struct { + Species string `json:"species"` + CatBreed string `json:"catBreed"` +} + +func (Cat) IsAnimal() {} + type CheckIssue896 struct { ID *int `json:"id"` } @@ -53,6 +64,13 @@ type ContentUser struct { func (ContentUser) IsContentChild() {} +type Dog struct { + Species string `json:"species"` + DogBreed string `json:"dogBreed"` +} + +func (Dog) IsAnimal() {} + type EmbeddedDefaultScalar struct { Value *string `json:"value"` } diff --git a/codegen/testserver/resolver.go b/codegen/testserver/resolver.go index b6f155d0087..9ad0b7bb1fb 100644 --- a/codegen/testserver/resolver.go +++ b/codegen/testserver/resolver.go @@ -197,6 +197,12 @@ func (r *queryResolver) Shapes(ctx context.Context) ([]Shape, error) { func (r *queryResolver) NoShape(ctx context.Context) (Shape, error) { panic("not implemented") } +func (r *queryResolver) NoShapeTypedNil(ctx context.Context) (Shape, error) { + panic("not implemented") +} +func (r *queryResolver) Animal(ctx context.Context) (Animal, error) { + panic("not implemented") +} func (r *queryResolver) Issue896a(ctx context.Context) ([]*CheckIssue896, error) { panic("not implemented") } diff --git a/codegen/testserver/stub.go b/codegen/testserver/stub.go index d05709ad3a9..eeeaeb20974 100644 --- a/codegen/testserver/stub.go +++ b/codegen/testserver/stub.go @@ -69,6 +69,8 @@ type Stub struct { EnumInInput func(ctx context.Context, input *InputWithEnumValue) (EnumTest, error) Shapes func(ctx context.Context) ([]Shape, error) NoShape func(ctx context.Context) (Shape, error) + NoShapeTypedNil func(ctx context.Context) (Shape, error) + Animal func(ctx context.Context) (Animal, error) Issue896a func(ctx context.Context) ([]*CheckIssue896, error) MapStringInterface func(ctx context.Context, in map[string]interface{}) (map[string]interface{}, error) MapNestedStringInterface func(ctx context.Context, in *NestedMapInput) (map[string]interface{}, error) @@ -287,6 +289,12 @@ func (r *stubQuery) Shapes(ctx context.Context) ([]Shape, error) { func (r *stubQuery) NoShape(ctx context.Context) (Shape, error) { return r.QueryResolver.NoShape(ctx) } +func (r *stubQuery) NoShapeTypedNil(ctx context.Context) (Shape, error) { + return r.QueryResolver.NoShapeTypedNil(ctx) +} +func (r *stubQuery) Animal(ctx context.Context) (Animal, error) { + return r.QueryResolver.Animal(ctx) +} func (r *stubQuery) Issue896a(ctx context.Context) ([]*CheckIssue896, error) { return r.QueryResolver.Issue896a(ctx) } diff --git a/example/selection/generated.go b/example/selection/generated.go index 23dc3fbfbfa..269cb011f9c 100644 --- a/example/selection/generated.go +++ b/example/selection/generated.go @@ -1828,10 +1828,16 @@ func (ec *executionContext) _Event(ctx context.Context, sel ast.SelectionSet, ob case Post: return ec._Post(ctx, sel, &obj) case *Post: + if obj == nil { + return graphql.Null + } return ec._Post(ctx, sel, obj) case Like: return ec._Like(ctx, sel, &obj) case *Like: + if obj == nil { + return graphql.Null + } return ec._Like(ctx, sel, obj) default: panic(fmt.Errorf("unexpected type %T", obj)) diff --git a/example/starwars/generated/exec.go b/example/starwars/generated/exec.go index b7aac040081..e8e00aa2efc 100644 --- a/example/starwars/generated/exec.go +++ b/example/starwars/generated/exec.go @@ -3645,10 +3645,16 @@ func (ec *executionContext) _Character(ctx context.Context, sel ast.SelectionSet case models.Human: return ec._Human(ctx, sel, &obj) case *models.Human: + if obj == nil { + return graphql.Null + } return ec._Human(ctx, sel, obj) case models.Droid: return ec._Droid(ctx, sel, &obj) case *models.Droid: + if obj == nil { + return graphql.Null + } return ec._Droid(ctx, sel, obj) default: panic(fmt.Errorf("unexpected type %T", obj)) @@ -3662,14 +3668,23 @@ func (ec *executionContext) _SearchResult(ctx context.Context, sel ast.Selection case models.Human: return ec._Human(ctx, sel, &obj) case *models.Human: + if obj == nil { + return graphql.Null + } return ec._Human(ctx, sel, obj) case models.Droid: return ec._Droid(ctx, sel, &obj) case *models.Droid: + if obj == nil { + return graphql.Null + } return ec._Droid(ctx, sel, obj) case models.Starship: return ec._Starship(ctx, sel, &obj) case *models.Starship: + if obj == nil { + return graphql.Null + } return ec._Starship(ctx, sel, obj) default: panic(fmt.Errorf("unexpected type %T", obj)) diff --git a/example/type-system-extension/generated.go b/example/type-system-extension/generated.go index 117ca100f71..b325f0d70b0 100644 --- a/example/type-system-extension/generated.go +++ b/example/type-system-extension/generated.go @@ -1921,6 +1921,9 @@ func (ec *executionContext) _Data(ctx context.Context, sel ast.SelectionSet, obj case Todo: return ec._Todo(ctx, sel, &obj) case *Todo: + if obj == nil { + return graphql.Null + } return ec._Todo(ctx, sel, obj) default: panic(fmt.Errorf("unexpected type %T", obj)) @@ -1934,6 +1937,9 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj case Todo: return ec._Todo(ctx, sel, &obj) case *Todo: + if obj == nil { + return graphql.Null + } return ec._Todo(ctx, sel, obj) default: panic(fmt.Errorf("unexpected type %T", obj))