diff --git a/codegen/interface.gotpl b/codegen/interface.gotpl index 47145500e4..bfb42b25d6 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 888a61bdf6..57fab826b6 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) @@ -257,6 +259,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 +397,7 @@ 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) 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) @@ -1136,6 +1140,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 +1667,7 @@ extend type Query { &ast.Source{Name: "interfaces.graphql", Input: `extend type Query { shapes: [Shape] noShape: Shape @makeNil + noShapeTypedNil: Shape @makeTypedNil } interface Shape { @@ -1673,6 +1685,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 @@ -6089,6 +6102,57 @@ 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_issue896a(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { @@ -9229,10 +9293,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 +9314,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 +9333,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 +9354,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)) @@ -10747,6 +10835,17 @@ 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 "issue896a": field := field out.Concurrently(i, func() (res graphql.Marshaler) { diff --git a/codegen/testserver/interfaces.graphql b/codegen/testserver/interfaces.graphql index 5a66c236a2..e732d11b77 100644 --- a/codegen/testserver/interfaces.graphql +++ b/codegen/testserver/interfaces.graphql @@ -1,6 +1,7 @@ extend type Query { shapes: [Shape] noShape: Shape @makeNil + noShapeTypedNil: Shape @makeTypedNil } interface Shape { @@ -18,3 +19,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 88c3fe61ee..b6558215d7 100644 --- a/codegen/testserver/interfaces_test.go +++ b/codegen/testserver/interfaces_test.go @@ -40,4 +40,28 @@ 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) + }) } diff --git a/codegen/testserver/resolver.go b/codegen/testserver/resolver.go index b6f155d008..35bb53a693 100644 --- a/codegen/testserver/resolver.go +++ b/codegen/testserver/resolver.go @@ -197,6 +197,9 @@ 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) Issue896a(ctx context.Context) ([]*CheckIssue896, error) { panic("not implemented") } diff --git a/codegen/testserver/stub.go b/codegen/testserver/stub.go index d05709ad3a..51ed7e454c 100644 --- a/codegen/testserver/stub.go +++ b/codegen/testserver/stub.go @@ -69,6 +69,7 @@ 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) 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 +288,9 @@ 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) Issue896a(ctx context.Context) ([]*CheckIssue896, error) { return r.QueryResolver.Issue896a(ctx) }