diff --git a/example/starwars/starwars.go b/example/starwars/starwars.go index 526caa7a68..263585ceb5 100644 --- a/example/starwars/starwars.go +++ b/example/starwars/starwars.go @@ -3,10 +3,7 @@ // Source: https://github.com/graphql/graphql.github.io/blob/source/site/_core/swapiSchema.js package starwars -import ( - "context" - "strings" -) +import "strings" var Schema = ` schema { @@ -275,18 +272,18 @@ func init() { type Resolver struct{} -func (r *Resolver) Hero(ctx context.Context, args struct{ Episode string }) characterResolver { +func (r *Resolver) Hero(args struct{ Episode string }) characterResolver { if args.Episode == "EMPIRE" { return &humanResolver{humanData["1000"]} } return &droidResolver{droidData["2001"]} } -func (r *Resolver) Reviews(ctx context.Context, args struct{ Episode string }) []*reviewResolver { +func (r *Resolver) Reviews(args struct{ Episode string }) []*reviewResolver { panic("TODO") } -func (r *Resolver) Search(ctx context.Context, args struct{ Text string }) []searchResultResolver { +func (r *Resolver) Search(args struct{ Text string }) []searchResultResolver { var l []searchResultResolver for _, h := range humans { if strings.Contains(h.Name, args.Text) { @@ -306,7 +303,7 @@ func (r *Resolver) Search(ctx context.Context, args struct{ Text string }) []sea return l } -func (r *Resolver) Character(ctx context.Context, args struct{ ID string }) characterResolver { +func (r *Resolver) Character(args struct{ ID string }) characterResolver { if h := humanData[args.ID]; h != nil { return &humanResolver{h} } @@ -316,21 +313,21 @@ func (r *Resolver) Character(ctx context.Context, args struct{ ID string }) char return nil } -func (r *Resolver) Human(ctx context.Context, args struct{ ID string }) *humanResolver { +func (r *Resolver) Human(args struct{ ID string }) *humanResolver { if h := humanData[args.ID]; h != nil { return &humanResolver{h} } return nil } -func (r *Resolver) Droid(ctx context.Context, args struct{ ID string }) *droidResolver { +func (r *Resolver) Droid(args struct{ ID string }) *droidResolver { if d := droidData[args.ID]; d != nil { return &droidResolver{d} } return nil } -func (r *Resolver) Starship(ctx context.Context, args struct{ ID string }) *starshipResolver { +func (r *Resolver) Starship(args struct{ ID string }) *starshipResolver { if s := starshipData[args.ID]; s != nil { return &starshipResolver{s} } @@ -343,48 +340,48 @@ type friendsConenctionArgs struct { } type characterResolver interface { - ID(context.Context) string - Name(context.Context) string - Friends(context.Context) []characterResolver - FriendsConnection(context.Context, friendsConenctionArgs) *friendsConnectionResolver - AppearsIn(context.Context) []string - ToHuman(context.Context) (*humanResolver, bool) - ToDroid(context.Context) (*droidResolver, bool) + ID() string + Name() string + Friends() []characterResolver + FriendsConnection(friendsConenctionArgs) *friendsConnectionResolver + AppearsIn() []string + ToHuman() (*humanResolver, bool) + ToDroid() (*droidResolver, bool) } type humanResolver struct { h *human } -func (r *humanResolver) ID(ctx context.Context) string { +func (r *humanResolver) ID() string { return r.h.ID } -func (r *humanResolver) Name(ctx context.Context) string { +func (r *humanResolver) Name() string { return r.h.Name } -func (r *humanResolver) Height(ctx context.Context, args struct{ Unit string }) float64 { +func (r *humanResolver) Height(args struct{ Unit string }) float64 { return convertLength(r.h.Height, args.Unit) } -func (r *humanResolver) Mass(ctx context.Context) float64 { +func (r *humanResolver) Mass() float64 { return float64(r.h.Mass) } -func (r *humanResolver) Friends(ctx context.Context) []characterResolver { +func (r *humanResolver) Friends() []characterResolver { return resolveCharacters(r.h.Friends) } -func (r *humanResolver) FriendsConnection(ctx context.Context, args friendsConenctionArgs) *friendsConnectionResolver { +func (r *humanResolver) FriendsConnection(args friendsConenctionArgs) *friendsConnectionResolver { panic("TODO") } -func (r *humanResolver) AppearsIn(ctx context.Context) []string { +func (r *humanResolver) AppearsIn() []string { return r.h.AppearsIn } -func (r *humanResolver) Starships(ctx context.Context) []*starshipResolver { +func (r *humanResolver) Starships() []*starshipResolver { l := make([]*starshipResolver, len(r.h.Starships)) for i, id := range r.h.Starships { l[i] = &starshipResolver{starshipData[id]} @@ -392,15 +389,15 @@ func (r *humanResolver) Starships(ctx context.Context) []*starshipResolver { return l } -func (r *humanResolver) ToHuman(ctx context.Context) (*humanResolver, bool) { +func (r *humanResolver) ToHuman() (*humanResolver, bool) { return r, true } -func (r *humanResolver) ToDroid(ctx context.Context) (*droidResolver, bool) { +func (r *humanResolver) ToDroid() (*droidResolver, bool) { return nil, false } -func (r *humanResolver) ToStarship(ctx context.Context) (*starshipResolver, bool) { +func (r *humanResolver) ToStarship() (*starshipResolver, bool) { return nil, false } @@ -408,39 +405,39 @@ type droidResolver struct { d *droid } -func (r *droidResolver) ID(ctx context.Context) string { +func (r *droidResolver) ID() string { return r.d.ID } -func (r *droidResolver) Name(ctx context.Context) string { +func (r *droidResolver) Name() string { return r.d.Name } -func (r *droidResolver) Friends(ctx context.Context) []characterResolver { +func (r *droidResolver) Friends() []characterResolver { return resolveCharacters(r.d.Friends) } -func (r *droidResolver) FriendsConnection(ctx context.Context, args friendsConenctionArgs) *friendsConnectionResolver { +func (r *droidResolver) FriendsConnection(args friendsConenctionArgs) *friendsConnectionResolver { panic("TODO") } -func (r *droidResolver) AppearsIn(ctx context.Context) []string { +func (r *droidResolver) AppearsIn() []string { return r.d.AppearsIn } -func (r *droidResolver) PrimaryFunction(ctx context.Context) string { +func (r *droidResolver) PrimaryFunction() string { return r.d.PrimaryFunction } -func (r *droidResolver) ToHuman(ctx context.Context) (*humanResolver, bool) { +func (r *droidResolver) ToHuman() (*humanResolver, bool) { return nil, false } -func (r *droidResolver) ToDroid(ctx context.Context) (*droidResolver, bool) { +func (r *droidResolver) ToDroid() (*droidResolver, bool) { return r, true } -func (r *droidResolver) ToStarship(ctx context.Context) (*starshipResolver, bool) { +func (r *droidResolver) ToStarship() (*starshipResolver, bool) { return nil, false } @@ -448,34 +445,34 @@ type starshipResolver struct { s *starship } -func (r *starshipResolver) ID(ctx context.Context) string { +func (r *starshipResolver) ID() string { return r.s.ID } -func (r *starshipResolver) Name(ctx context.Context) string { +func (r *starshipResolver) Name() string { return r.s.Name } -func (r *starshipResolver) Length(ctx context.Context, args struct{ Unit string }) float64 { +func (r *starshipResolver) Length(args struct{ Unit string }) float64 { return convertLength(r.s.Length, args.Unit) } -func (r *starshipResolver) ToHuman(ctx context.Context) (*humanResolver, bool) { +func (r *starshipResolver) ToHuman() (*humanResolver, bool) { return nil, false } -func (r *starshipResolver) ToDroid(ctx context.Context) (*droidResolver, bool) { +func (r *starshipResolver) ToDroid() (*droidResolver, bool) { return nil, false } -func (r *starshipResolver) ToStarship(ctx context.Context) (*starshipResolver, bool) { +func (r *starshipResolver) ToStarship() (*starshipResolver, bool) { return r, true } type searchResultResolver interface { - ToHuman(context.Context) (*humanResolver, bool) - ToDroid(context.Context) (*droidResolver, bool) - ToStarship(context.Context) (*starshipResolver, bool) + ToHuman() (*humanResolver, bool) + ToDroid() (*droidResolver, bool) + ToStarship() (*starshipResolver, bool) } func convertLength(meters float64, unit string) float64 { @@ -505,55 +502,55 @@ func resolveCharacters(ids []string) []characterResolver { type reviewResolver struct { } -func (r *reviewResolver) Stars(ctx context.Context) int { +func (r *reviewResolver) Stars() int { panic("TODO") } -func (r *reviewResolver) Commentary(ctx context.Context) string { +func (r *reviewResolver) Commentary() string { panic("TODO") } type friendsConnectionResolver struct { } -func (r *friendsConnectionResolver) TotalCount(ctx context.Context) int { +func (r *friendsConnectionResolver) TotalCount() int { panic("TODO") } -func (r *friendsConnectionResolver) Edges(ctx context.Context) []*friendsEdgeResolver { +func (r *friendsConnectionResolver) Edges() []*friendsEdgeResolver { panic("TODO") } -func (r *friendsConnectionResolver) Friends(ctx context.Context) []characterResolver { +func (r *friendsConnectionResolver) Friends() []characterResolver { panic("TODO") } -func (r *friendsConnectionResolver) PageInfo(ctx context.Context) *pageInfoResolver { +func (r *friendsConnectionResolver) PageInfo() *pageInfoResolver { panic("TODO") } type friendsEdgeResolver struct { } -func (r *friendsEdgeResolver) Cursor(ctx context.Context) string { +func (r *friendsEdgeResolver) Cursor() string { panic("TODO") } -func (r *friendsEdgeResolver) Node(ctx context.Context) characterResolver { +func (r *friendsEdgeResolver) Node() characterResolver { panic("TODO") } type pageInfoResolver struct { } -func (r *pageInfoResolver) StartCursor(ctx context.Context) string { +func (r *pageInfoResolver) StartCursor() string { panic("TODO") } -func (r *pageInfoResolver) EndCursor(ctx context.Context) string { +func (r *pageInfoResolver) EndCursor() string { panic("TODO") } -func (r *pageInfoResolver) HasNextPage(ctx context.Context) bool { +func (r *pageInfoResolver) HasNextPage() bool { panic("TODO") } diff --git a/graphql_test.go b/graphql_test.go index 786c87395b..bc636f5b83 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -9,9 +9,15 @@ import ( "github.com/neelance/graphql-go/example/starwars" ) -type helloWorldResolver struct{} +type helloWorldResolver1 struct{} -func (r *helloWorldResolver) Hello(ctx context.Context) string { +func (r *helloWorldResolver1) Hello() string { + return "Hello world!" +} + +type helloWorldResolver2 struct{} + +func (r *helloWorldResolver2) Hello(ctx context.Context) string { return "Hello world!" } @@ -24,7 +30,31 @@ var tests = []struct { result string }{ { - name: "HelloWorld", + name: "HelloWorld1", + schema: ` + schema { + query: Query + } + + type Query { + hello: String + } + `, + resolver: &helloWorldResolver1{}, + query: ` + { + hello + } + `, + result: ` + { + "hello": "Hello world!" + } + `, + }, + + { + name: "HelloWorld2", schema: ` schema { query: Query @@ -34,7 +64,7 @@ var tests = []struct { hello: String } `, - resolver: &helloWorldResolver{}, + resolver: &helloWorldResolver2{}, query: ` { hello diff --git a/internal/exec/exec.go b/internal/exec/exec.go index 9d2c4560aa..a9b72cc149 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -112,6 +112,8 @@ func makeExec(s *schema.Schema, t schema.Type, resolverType reflect.Type, typeRe } } +var contextType = reflect.TypeOf((*context.Context)(nil)).Elem() + func makeFieldExecs(s *schema.Schema, typeName string, fields map[string]*schema.Field, resolverType reflect.Type, typeRefMap map[typeRefMapKey]*typeRefExec) (map[string]*fieldExec, error) { fieldExecs := make(map[string]*fieldExec) for name, f := range fields { @@ -121,17 +123,33 @@ func makeFieldExecs(s *schema.Schema, typeName string, fields map[string]*schema } m := resolverType.Method(methodIndex) - numIn := m.Type.NumIn() + in := make([]reflect.Type, m.Type.NumIn()) + for i := range in { + in[i] = m.Type.In(i) + } if resolverType.Kind() != reflect.Interface { - numIn-- // first parameter is receiver + in = in[1:] // first parameter is receiver + } + + hasContext := len(in) > 0 && in[0] == contextType + if hasContext { + in = in[1:] } - if len(f.Parameters) == 0 && numIn != 1 { - return nil, fmt.Errorf("method %q of %s must have exactly one parameter", m.Name, resolverType) + + var argumentsType reflect.Type + if len(f.Parameters) > 0 { + if len(in) == 0 { + return nil, fmt.Errorf("method %q of %s is missing a parameter for field arguments", m.Name, resolverType) + } + argumentsType = in[0] + // TODO type check arguments + in = in[1:] } - if len(f.Parameters) > 0 && numIn != 2 { - return nil, fmt.Errorf("method %q of %s must have exactly two parameters", m.Name, resolverType) + + if len(in) > 0 { + return nil, fmt.Errorf("method %q of %s has too many parameters", m.Name, resolverType) } - // TODO check parameter types + if m.Type.NumOut() != 1 { return nil, fmt.Errorf("method %q of %s must have exactly one return value", m.Name, resolverType) } @@ -141,9 +159,11 @@ func makeFieldExecs(s *schema.Schema, typeName string, fields map[string]*schema return nil, err } fieldExecs[name] = &fieldExec{ - field: f, - methodIndex: methodIndex, - valueExec: ve, + field: f, + methodIndex: methodIndex, + hasContext: hasContext, + argumentsType: argumentsType, + valueExec: ve, } } return fieldExecs, nil @@ -306,7 +326,7 @@ func (e *objectExec) execSelectionSet(r *request, selSet *query.SelectionSet, re switch f.Name { case "__typename": for name, a := range e.typeAssertions { - out := resolver.Method(a.methodIndex).Call([]reflect.Value{reflect.ValueOf(r.ctx)}) + out := resolver.Method(a.methodIndex).Call(nil) if out[1].Bool() { addResult(f.Alias, name) return @@ -366,7 +386,7 @@ func (e *objectExec) execFragment(r *request, frag *query.Fragment, resolver ref if !ok { panic(fmt.Errorf("%q does not implement %q", frag.On, e.name)) // TODO proper error handling } - out := resolver.Method(a.methodIndex).Call([]reflect.Value{reflect.ValueOf(r.ctx)}) + out := resolver.Method(a.methodIndex).Call(nil) if !out[1].Bool() { return } @@ -377,16 +397,22 @@ func (e *objectExec) execFragment(r *request, frag *query.Fragment, resolver ref } type fieldExec struct { - field *schema.Field - methodIndex int - valueExec iExec + field *schema.Field + methodIndex int + hasContext bool + argumentsType reflect.Type + valueExec iExec } func (e *fieldExec) execField(r *request, f *query.Field, resolver reflect.Value, addResult addResultFn) { - m := resolver.Method(e.methodIndex) - in := []reflect.Value{reflect.ValueOf(r.ctx)} - if len(e.field.Parameters) != 0 { - args := reflect.New(m.Type().In(1)) + var in []reflect.Value + + if e.hasContext { + in = append(in, reflect.ValueOf(r.ctx)) + } + + if e.argumentsType != nil { + args := reflect.New(e.argumentsType) for name, param := range e.field.Parameters { value, ok := f.Arguments[name] if !ok { @@ -397,6 +423,8 @@ func (e *fieldExec) execField(r *request, f *query.Field, resolver reflect.Value } in = append(in, args.Elem()) } + + m := resolver.Method(e.methodIndex) addResult(f.Alias, e.valueExec.exec(r, f.SelSet, m.Call(in)[0])) } diff --git a/internal/exec/introspection.go b/internal/exec/introspection.go index 7b1e1b4bdf..f30064755f 100644 --- a/internal/exec/introspection.go +++ b/internal/exec/introspection.go @@ -1,7 +1,6 @@ package exec import ( - "context" "reflect" "sort" @@ -139,7 +138,7 @@ type schemaResolver struct { schema *schema.Schema } -func (r *schemaResolver) Types(ctx context.Context) []*typeResolver { +func (r *schemaResolver) Types() []*typeResolver { var l []*typeResolver addTypes := func(s *schema.Schema) { var names []string @@ -159,15 +158,15 @@ func (r *schemaResolver) Types(ctx context.Context) []*typeResolver { return l } -func (r *schemaResolver) QueryType(ctx context.Context) *typeResolver { +func (r *schemaResolver) QueryType() *typeResolver { return &typeResolver{typ: r.schema.AllTypes[r.schema.EntryPoints["query"]]} } -func (r *schemaResolver) MutationType(ctx context.Context) *typeResolver { +func (r *schemaResolver) MutationType() *typeResolver { return &typeResolver{typ: r.schema.AllTypes[r.schema.EntryPoints["mutation"]]} } -func (r *schemaResolver) Directives(ctx context.Context) []*directiveResolver { +func (r *schemaResolver) Directives() []*directiveResolver { panic("TODO") } @@ -176,7 +175,7 @@ type typeResolver struct { typ schema.Type } -func (r *typeResolver) Kind(ctx context.Context) string { +func (r *typeResolver) Kind() string { if r.scalar != "" { return "SCALAR" } @@ -200,7 +199,7 @@ func (r *typeResolver) Kind(ctx context.Context) string { } } -func (r *typeResolver) Name(ctx context.Context) string { +func (r *typeResolver) Name() string { if r.scalar != "" { return r.scalar } @@ -220,114 +219,114 @@ func (r *typeResolver) Name(ctx context.Context) string { } } -func (r *typeResolver) Description(ctx context.Context) string { +func (r *typeResolver) Description() string { panic("TODO") } -func (r *typeResolver) Fields(ctx context.Context, args struct{ IncludeDeprecated bool }) []*fieldResolver { +func (r *typeResolver) Fields(args struct{ IncludeDeprecated bool }) []*fieldResolver { panic("TODO") } -func (r *typeResolver) Interfaces(ctx context.Context) []*typeResolver { +func (r *typeResolver) Interfaces() []*typeResolver { panic("TODO") } -func (r *typeResolver) PossibleTypes(ctx context.Context) []*typeResolver { +func (r *typeResolver) PossibleTypes() []*typeResolver { panic("TODO") } -func (r *typeResolver) EnumValues(ctx context.Context, args struct{ IncludeDeprecated bool }) []*enumValueResolver { +func (r *typeResolver) EnumValues(args struct{ IncludeDeprecated bool }) []*enumValueResolver { panic("TODO") } -func (r *typeResolver) InputFields(ctx context.Context) []*inputValueResolver { +func (r *typeResolver) InputFields() []*inputValueResolver { panic("TODO") } -func (r *typeResolver) OfType(ctx context.Context) *typeResolver { +func (r *typeResolver) OfType() *typeResolver { panic("TODO") } type fieldResolver struct { } -func (r *fieldResolver) Name(ctx context.Context) string { +func (r *fieldResolver) Name() string { panic("TODO") } -func (r *fieldResolver) Description(ctx context.Context) string { +func (r *fieldResolver) Description() string { panic("TODO") } -func (r *fieldResolver) Args(ctx context.Context) []*inputValueResolver { +func (r *fieldResolver) Args() []*inputValueResolver { panic("TODO") } -func (r *fieldResolver) Type(ctx context.Context) *typeResolver { +func (r *fieldResolver) Type() *typeResolver { panic("TODO") } -func (r *fieldResolver) IsDeprecated(ctx context.Context) bool { +func (r *fieldResolver) IsDeprecated() bool { panic("TODO") } -func (r *fieldResolver) DeprecationReason(ctx context.Context) string { +func (r *fieldResolver) DeprecationReason() string { panic("TODO") } type inputValueResolver struct { } -func (r *inputValueResolver) Name(ctx context.Context) string { +func (r *inputValueResolver) Name() string { panic("TODO") } -func (r *inputValueResolver) Description(ctx context.Context) string { +func (r *inputValueResolver) Description() string { panic("TODO") } -func (r *inputValueResolver) Type(ctx context.Context) *typeResolver { +func (r *inputValueResolver) Type() *typeResolver { panic("TODO") } -func (r *inputValueResolver) DefaultValue(ctx context.Context) string { +func (r *inputValueResolver) DefaultValue() string { panic("TODO") } type enumValueResolver struct { } -func (r *enumValueResolver) Name(ctx context.Context) string { +func (r *enumValueResolver) Name() string { panic("TODO") } -func (r *enumValueResolver) Description(ctx context.Context) string { +func (r *enumValueResolver) Description() string { panic("TODO") } -func (r *enumValueResolver) IsDeprecated(ctx context.Context) bool { +func (r *enumValueResolver) IsDeprecated() bool { panic("TODO") } -func (r *enumValueResolver) DeprecationReason(ctx context.Context) string { +func (r *enumValueResolver) DeprecationReason() string { panic("TODO") } type directiveResolver struct { } -func (r *directiveResolver) Name(ctx context.Context) string { +func (r *directiveResolver) Name() string { panic("TODO") } -func (r *directiveResolver) Description(ctx context.Context) string { +func (r *directiveResolver) Description() string { panic("TODO") } -func (r *directiveResolver) Locations(ctx context.Context) []string { +func (r *directiveResolver) Locations() []string { panic("TODO") } -func (r *directiveResolver) Args(ctx context.Context) []*inputValueResolver { +func (r *directiveResolver) Args() []*inputValueResolver { panic("TODO") }