diff --git a/graphql_test.go b/graphql_test.go index d1a05d6ab..996d32762 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -6329,3 +6329,96 @@ query Q { } }) } + +// https://github.com/graph-gophers/graphql-go/issues/763 +type issue763ThingResolver interface { + Alpha(ctx context.Context, args struct { + Query *string + Limit *int32 + }) (string, error) + Beta(ctx context.Context, args struct{ Query *string }) (string, error) +} + +type issue763ConcreteThingResolver struct{} + +// sorts before Alpha, shifting method indices on the concrete type to reproduce #763 +func (r *issue763ConcreteThingResolver) AaExtra() string { + return "" +} + +func (r *issue763ConcreteThingResolver) Alpha(_ context.Context, args struct { + Query *string + Limit *int32 +}, +) (string, error) { + return "alpha", nil +} + +func (r *issue763ConcreteThingResolver) Beta(_ context.Context, args struct{ Query *string }) (string, error) { + return "beta", nil +} + +type issueRootResolver763 struct{} + +func (r *issueRootResolver763) Thing() (issue763ThingResolver, error) { + return &issue763ConcreteThingResolver{}, nil +} + +func TestInterfaceResolverMethodIndex(t *testing.T) { + t.Parallel() + + gqltesting.RunTest(t, &gqltesting.Test{ + Schema: graphql.MustParseSchema(` + type Query { thing: Thing! } + type Thing { + alpha(query: String, limit: Int): String! + beta(query: String): String! + } + `, &issueRootResolver763{}), + Query: `{ thing { beta(query: "test") } }`, + ExpectedResult: `{"thing":{"beta":"beta"}}`, + }) +} + +type issue763ListUnionNodeResolver interface { + Label(ctx context.Context, args struct{ Query *string }) (string, error) +} + +type issue763ListUnionConcreteNodeResolver struct{} + +// sorts before Label, shifting method indices on the concrete type to reproduce #763 +func (r *issue763ListUnionConcreteNodeResolver) AaExtra() string { + return "" +} + +func (r *issue763ListUnionConcreteNodeResolver) Label(_ context.Context, args struct{ Query *string }) (string, error) { + return "label:" + *args.Query, nil +} + +type issue763ListUnionSearchResultResolver struct{} + +func (r *issue763ListUnionSearchResultResolver) ToNodeImpl() (issue763ListUnionNodeResolver, bool) { + return &issue763ListUnionConcreteNodeResolver{}, true +} + +type issue763ListUnionRootResolver struct{} + +func (r *issue763ListUnionRootResolver) Search() ([]*issue763ListUnionSearchResultResolver, error) { + return []*issue763ListUnionSearchResultResolver{{}}, nil +} + +func TestIssue763InterfaceMethodIndexInListUnion(t *testing.T) { + t.Parallel() + + gqltesting.RunTest(t, &gqltesting.Test{ + Schema: graphql.MustParseSchema(` + type Query { search: [Search!]! } + union Search = NodeImpl + type NodeImpl { + label(query: String): String! + } + `, &issue763ListUnionRootResolver{}), + Query: `{ search { ... on NodeImpl { label(query: "x") } } }`, + ExpectedResult: `{"search":[{"label":"label:x"}]}`, + }) +} diff --git a/internal/exec/exec.go b/internal/exec/exec.go index 702da93c1..f68699204 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -95,7 +95,7 @@ type fieldToExec struct { out *bytes.Buffer } -func (f *fieldToExec) resolve(ctx context.Context) (output any, err error) { +func (f *fieldToExec) resolve(ctx context.Context) (reflect.Value, error) { return f.field.Resolve(ctx, f.resolver) } @@ -271,7 +271,8 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f if len(f.sels) > 0 && !r.DisableFieldSelections { ctx = selections.With(traceCtx, f.sels) } - res, resolverErr := f.resolve(ctx) + var resolverErr error + result, resolverErr = f.resolve(ctx) if resolverErr != nil { err := errors.Errorf("%s", resolverErr) err.Path = path.toSlice() @@ -282,8 +283,6 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f return err } - result = reflect.ValueOf(res) - return nil }() diff --git a/internal/exec/resolvable/resolvable.go b/internal/exec/resolvable/resolvable.go index c8f83872e..6a783fff2 100644 --- a/internal/exec/resolvable/resolvable.go +++ b/internal/exec/resolvable/resolvable.go @@ -63,7 +63,7 @@ func (f *Field) UseMethodResolver() bool { return f.MethodIndex != -1 || f.IsFieldFunc } -func (f *Field) Resolve(ctx context.Context, resolver reflect.Value, args map[string]any, packedArgs reflect.Value) (output any, err error) { +func (f *Field) Resolve(ctx context.Context, resolver reflect.Value, args map[string]any, packedArgs reflect.Value) (reflect.Value, error) { if len(f.Implementations) > 0 { for _, impl := range f.Implementations { out := resolver.Method(impl.MethodIndex).Call(nil) @@ -77,10 +77,10 @@ func (f *Field) Resolve(ctx context.Context, resolver reflect.Value, args map[st return f.resolve(ctx, resolver, args, packedArgs) } -func (f *Field) resolve(ctx context.Context, resolver reflect.Value, args map[string]any, packedArgs reflect.Value) (output any, err error) { +func (f *Field) resolve(ctx context.Context, resolver reflect.Value, args map[string]any, packedArgs reflect.Value) (reflect.Value, error) { if !f.UseMethodResolver() { if len(f.FieldIndex) == 0 { - return nil, fmt.Errorf("missing resolver for field %q", f.Name) + return reflect.Value{}, fmt.Errorf("missing resolver for field %q", f.Name) } res := resolver @@ -89,7 +89,7 @@ func (f *Field) resolve(ctx context.Context, resolver reflect.Value, args map[st res = res.Elem() } - return res.FieldByIndex(f.FieldIndex).Interface(), nil + return res.FieldByIndex(f.FieldIndex), nil } var in []reflect.Value @@ -103,7 +103,7 @@ func (f *Field) resolve(ctx context.Context, resolver reflect.Value, args map[st if !packedArgs.IsValid() { packed, packErr := f.ArgsPacker.Pack(args) if packErr != nil { - return nil, packErr + return reflect.Value{}, packErr } packedArgs = packed } @@ -123,10 +123,10 @@ func (f *Field) resolve(ctx context.Context, resolver reflect.Value, args map[st if f.HasError && !callOut[1].IsNil() { resolverErr := callOut[1].Interface().(error) - return result.Interface(), resolverErr + return result, resolverErr } - return result.Interface(), nil + return result, nil } type TypeAssertion struct { diff --git a/internal/exec/selected/selected.go b/internal/exec/selected/selected.go index 70b7d1e30..c53435895 100644 --- a/internal/exec/selected/selected.go +++ b/internal/exec/selected/selected.go @@ -56,7 +56,7 @@ type SchemaField struct { FixedResult reflect.Value } -func (f *SchemaField) Resolve(ctx context.Context, resolver reflect.Value) (output any, err error) { +func (f *SchemaField) Resolve(ctx context.Context, resolver reflect.Value) (reflect.Value, error) { return f.Field.Resolve(ctx, resolver, f.Args, f.PackedArgs) }