diff --git a/example/starwars/starwars.go b/example/starwars/starwars.go index f52cc39d71..2a9c5d3460 100644 --- a/example/starwars/starwars.go +++ b/example/starwars/starwars.go @@ -3,7 +3,12 @@ // Source: https://github.com/graphql/graphql.github.io/blob/source/site/_core/swapiSchema.js package starwars -import "strings" +import ( + "encoding/base64" + "fmt" + "strconv" + "strings" +) var Schema = ` schema { @@ -343,7 +348,7 @@ type characterResolver interface { ID() string Name() string Friends() *[]characterResolver - FriendsConnection(friendsConenctionArgs) *friendsConnectionResolver + FriendsConnection(friendsConenctionArgs) (*friendsConnectionResolver, error) AppearsIn() []string ToHuman() (*humanResolver, bool) ToDroid() (*droidResolver, bool) @@ -377,8 +382,8 @@ func (r *humanResolver) Friends() *[]characterResolver { return resolveCharacters(r.h.Friends) } -func (r *humanResolver) FriendsConnection(args friendsConenctionArgs) *friendsConnectionResolver { - panic("TODO") +func (r *humanResolver) FriendsConnection(args friendsConenctionArgs) (*friendsConnectionResolver, error) { + return newFriendsConnectionResolver(r.h.Friends, args) } func (r *humanResolver) AppearsIn() []string { @@ -421,8 +426,8 @@ func (r *droidResolver) Friends() *[]characterResolver { return resolveCharacters(r.d.Friends) } -func (r *droidResolver) FriendsConnection(args friendsConenctionArgs) *friendsConnectionResolver { - panic("TODO") +func (r *droidResolver) FriendsConnection(args friendsConenctionArgs) (*friendsConnectionResolver, error) { + return newFriendsConnectionResolver(r.d.Friends, args) } func (r *droidResolver) AppearsIn() []string { @@ -496,16 +501,23 @@ func convertLength(meters float64, unit string) float64 { func resolveCharacters(ids []string) *[]characterResolver { var characters []characterResolver for _, id := range ids { - if h, ok := humanData[id]; ok { - characters = append(characters, &humanResolver{h}) - } - if d, ok := droidData[id]; ok { - characters = append(characters, &droidResolver{d}) + if c := resolveCharacter(id); c != nil { + characters = append(characters, c) } } return &characters } +func resolveCharacter(id string) characterResolver { + if h, ok := humanData[id]; ok { + return &humanResolver{h} + } + if d, ok := droidData[id]; ok { + return &droidResolver{d} + } + return nil +} + type reviewResolver struct { } @@ -518,46 +530,98 @@ func (r *reviewResolver) Commentary() *string { } type friendsConnectionResolver struct { + ids []string + from int + to int +} + +func newFriendsConnectionResolver(ids []string, args friendsConenctionArgs) (*friendsConnectionResolver, error) { + from := 0 + if args.After != "" { + b, err := base64.StdEncoding.DecodeString(args.After) + if err != nil { + return nil, err + } + i, err := strconv.Atoi(strings.TrimPrefix(string(b), "cursor")) + if err != nil { + return nil, err + } + from = i + } + + to := len(ids) + if args.First != 0 { + to = from + int(args.First) + } + if to > len(ids)-from { + to = len(ids) - from + } + + return &friendsConnectionResolver{ + ids: ids, + from: from, + to: to, + }, nil } func (r *friendsConnectionResolver) TotalCount() int32 { - panic("TODO") + return int32(len(r.ids)) } func (r *friendsConnectionResolver) Edges() *[]*friendsEdgeResolver { - panic("TODO") + l := make([]*friendsEdgeResolver, r.to-r.from) + for i := range l { + l[i] = &friendsEdgeResolver{ + cursor: encodeCursor(r.from + i), + id: r.ids[r.from+i], + } + } + return &l } func (r *friendsConnectionResolver) Friends() *[]characterResolver { - panic("TODO") + return resolveCharacters(r.ids[r.from:r.to]) } func (r *friendsConnectionResolver) PageInfo() *pageInfoResolver { - panic("TODO") + return &pageInfoResolver{ + startCursor: encodeCursor(r.from), + endCursor: encodeCursor(r.to - 1), + hasNextPage: r.to < len(r.ids), + } +} + +func encodeCursor(i int) string { + return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("cursor%d", i+1))) } type friendsEdgeResolver struct { + cursor string + id string } func (r *friendsEdgeResolver) Cursor() string { - panic("TODO") + return r.cursor } func (r *friendsEdgeResolver) Node() characterResolver { - panic("TODO") + return resolveCharacter(r.id) } type pageInfoResolver struct { + startCursor string + endCursor string + hasNextPage bool } func (r *pageInfoResolver) StartCursor() *string { - panic("TODO") + return &r.startCursor } func (r *pageInfoResolver) EndCursor() *string { - panic("TODO") + return &r.endCursor } func (r *pageInfoResolver) HasNextPage() bool { - panic("TODO") + return r.hasNextPage } diff --git a/graphql_test.go b/graphql_test.go index 461921bfae..3dd38d0dce 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -543,6 +543,118 @@ var tests = []struct { `, }, + { + name: "StarWarsConnections1", + schema: starwars.Schema, + resolver: &starwars.Resolver{}, + query: ` + { + hero { + name + friendsConnection { + totalCount + pageInfo { + startCursor + endCursor + hasNextPage + } + edges { + cursor + node { + name + } + } + } + } + } + `, + result: ` + { + "hero": { + "name": "R2-D2", + "friendsConnection": { + "totalCount": 3, + "pageInfo": { + "startCursor": "Y3Vyc29yMQ==", + "endCursor": "Y3Vyc29yMw==", + "hasNextPage": false + }, + "edges": [ + { + "cursor": "Y3Vyc29yMQ==", + "node": { + "name": "Luke Skywalker" + } + }, + { + "cursor": "Y3Vyc29yMg==", + "node": { + "name": "Han Solo" + } + }, + { + "cursor": "Y3Vyc29yMw==", + "node": { + "name": "Leia Organa" + } + } + ] + } + } + } + `, + }, + + { + name: "StarWarsConnections2", + schema: starwars.Schema, + resolver: &starwars.Resolver{}, + query: ` + { + hero { + name + friendsConnection(first: 1, after: "Y3Vyc29yMQ==") { + totalCount + pageInfo { + startCursor + endCursor + hasNextPage + } + edges { + cursor + node { + name + } + } + } + } + } + `, + result: ` + { + "hero": { + "name": "R2-D2", + "friendsConnection": { + "totalCount": 3, + "pageInfo": { + "startCursor": "Y3Vyc29yMg==", + "endCursor": "Y3Vyc29yMg==", + "hasNextPage": true + }, + "edges": [ + { + "cursor": "Y3Vyc29yMg==", + "node": { + "name": "Han Solo" + } + } + ] + } + } + } + `, + }, + { name: "StarWarsIntrospection1", schema: starwars.Schema, @@ -845,6 +957,10 @@ func TestAll(t *testing.T) { t.Fatal(err) } + if len(result.Errors) != 0 { + t.Fatal(result.Errors[0]) + } + got, err := json.Marshal(result.Data) if err != nil { t.Fatal(err) diff --git a/internal/exec/exec.go b/internal/exec/exec.go index f956eba5d8..1ea6eefa50 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -233,8 +233,6 @@ func makeFieldExecs(s *schema.Schema, typeName string, fields map[string]*schema return nil, fmt.Errorf("method %q of %s has too many return values", m.Name, resolverType) } - // TODO type check result - hasError := m.Type.NumOut() == 2 if hasError { if m.Type.Out(1) != errorType { diff --git a/internal/query/query.go b/internal/query/query.go index 99d0a0108d..af2b1836eb 100644 --- a/internal/query/query.go +++ b/internal/query/query.go @@ -221,7 +221,6 @@ func parseArguments(l *lexer.Lexer) map[string]Value { name, value := parseArgument(l) args[name] = value for l.Peek() != ')' { - l.ConsumeToken(',') name, value := parseArgument(l) args[name] = value }