From 4057080f8dcb75e6207fb9f1e357329738dcda50 Mon Sep 17 00:00:00 2001 From: Richard Musiol Date: Tue, 18 Oct 2016 17:18:04 +0200 Subject: [PATCH] add support for union types --- example/starwars/starwars.go | 48 ++++++++++++++++++++++++++++++++++-- graphql_test.go | 36 +++++++++++++++++++++++++++ internal/exec/exec.go | 36 ++++++++++++++++----------- 3 files changed, 103 insertions(+), 17 deletions(-) diff --git a/example/starwars/starwars.go b/example/starwars/starwars.go index b286d24bdaa..263585ceb55 100644 --- a/example/starwars/starwars.go +++ b/example/starwars/starwars.go @@ -3,6 +3,8 @@ // Source: https://github.com/graphql/graphql.github.io/blob/source/site/_core/swapiSchema.js package starwars +import "strings" + var Schema = ` schema { query: Query @@ -281,8 +283,24 @@ func (r *Resolver) Reviews(args struct{ Episode string }) []*reviewResolver { panic("TODO") } -func (r *Resolver) Search(args struct{ Text string }) []characterResolver { - panic("TODO") +func (r *Resolver) Search(args struct{ Text string }) []searchResultResolver { + var l []searchResultResolver + for _, h := range humans { + if strings.Contains(h.Name, args.Text) { + l = append(l, &humanResolver{h}) + } + } + for _, d := range droids { + if strings.Contains(d.Name, args.Text) { + l = append(l, &droidResolver{d}) + } + } + for _, s := range starships { + if strings.Contains(s.Name, args.Text) { + l = append(l, &starshipResolver{s}) + } + } + return l } func (r *Resolver) Character(args struct{ ID string }) characterResolver { @@ -379,6 +397,10 @@ func (r *humanResolver) ToDroid() (*droidResolver, bool) { return nil, false } +func (r *humanResolver) ToStarship() (*starshipResolver, bool) { + return nil, false +} + type droidResolver struct { d *droid } @@ -415,6 +437,10 @@ func (r *droidResolver) ToDroid() (*droidResolver, bool) { return r, true } +func (r *droidResolver) ToStarship() (*starshipResolver, bool) { + return nil, false +} + type starshipResolver struct { s *starship } @@ -431,6 +457,24 @@ func (r *starshipResolver) Length(args struct{ Unit string }) float64 { return convertLength(r.s.Length, args.Unit) } +func (r *starshipResolver) ToHuman() (*humanResolver, bool) { + return nil, false +} + +func (r *starshipResolver) ToDroid() (*droidResolver, bool) { + return nil, false +} + +func (r *starshipResolver) ToStarship() (*starshipResolver, bool) { + return r, true +} + +type searchResultResolver interface { + ToHuman() (*humanResolver, bool) + ToDroid() (*droidResolver, bool) + ToStarship() (*starshipResolver, bool) +} + func convertLength(meters float64, unit string) float64 { switch unit { case "METER": diff --git a/graphql_test.go b/graphql_test.go index 289541a5880..ef89bce2600 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -471,6 +471,42 @@ var tests = []struct { } `, }, + + { + name: "StarWarsTypeName", + schema: starwars.Schema, + resolver: &starwars.Resolver{}, + query: ` + { + search(text: "an") { + ... on Human { + name + } + ... on Droid { + name + } + ... on Starship { + name + } + } + } + `, + result: ` + { + "search": [ + { + "name": "Han Solo" + }, + { + "name": "Leia Organa" + }, + { + "name": "TIE Advanced x1" + } + ] + } + `, + }, } func TestAll(t *testing.T) { diff --git a/internal/exec/exec.go b/internal/exec/exec.go index e482075ee3d..70456cbe070 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -54,27 +54,17 @@ func makeExec(s *schema.Schema, t schema.Type, resolverType reflect.Type, implem } } - typeAssertions := make(map[string]*typeAssertExec) - for _, impl := range implementsMap[t.Name] { - methodIndex := findMethod(resolverType, "to"+impl) - if methodIndex == -1 { - panic(fmt.Errorf("%s does not resolve %q: missing method %q to convert to %q", resolverType, t.Name, "to"+impl, impl)) // TODO proper error handling - } - e := resolveType(s, impl, resolverType.Method(methodIndex).Type.Out(0), implementsMap, typeRefMap) - typeAssertions[impl] = &typeAssertExec{ - methodIndex: methodIndex, - typeExec: e, - } - } - return &objectExec{ name: t.Name, fields: fields, - typeAssertions: typeAssertions, + typeAssertions: makeTypeAssertions(s, t.Name, implementsMap[t.Name], resolverType, implementsMap, typeRefMap), } case *schema.Union: - return nil // TODO + return &objectExec{ + name: t.Name, + typeAssertions: makeTypeAssertions(s, t.Name, t.Types, resolverType, implementsMap, typeRefMap), + } case *schema.Enum: return &scalarExec{} @@ -95,6 +85,22 @@ func makeExec(s *schema.Schema, t schema.Type, resolverType reflect.Type, implem } } +func makeTypeAssertions(s *schema.Schema, typeName string, impls []string, resolverType reflect.Type, implementsMap map[string][]string, typeRefMap map[typeRefMapKey]*typeRefExec) map[string]*typeAssertExec { + typeAssertions := make(map[string]*typeAssertExec) + for _, impl := range impls { + methodIndex := findMethod(resolverType, "to"+impl) + if methodIndex == -1 { + panic(fmt.Errorf("%s does not resolve %q: missing method %q to convert to %q", resolverType, typeName, "to"+impl, impl)) // TODO proper error handling + } + e := resolveType(s, impl, resolverType.Method(methodIndex).Type.Out(0), implementsMap, typeRefMap) + typeAssertions[impl] = &typeAssertExec{ + methodIndex: methodIndex, + typeExec: e, + } + } + return typeAssertions +} + func resolveType(s *schema.Schema, name string, resolverType reflect.Type, implementsMap map[string][]string, typeRefMap map[typeRefMapKey]*typeRefExec) *typeRefExec { refT, ok := s.Types[name] if !ok {