diff --git a/example/starwars/starwars.go b/example/starwars/starwars.go index 38284078040..a19ef539418 100644 --- a/example/starwars/starwars.go +++ b/example/starwars/starwars.go @@ -18,8 +18,8 @@ var Schema = ` # The query type, represents all of the entry points into our object graph type Query { hero(episode: Episode): Character - reviews(episode: Episode!): [Review] - search(text: String): [SearchResult] + reviews(episode: Episode!): [Review]! + search(text: String): [SearchResult]! character(id: ID!): Character droid(id: ID!): Droid human(id: ID!): Human @@ -275,20 +275,31 @@ func init() { } } +type review struct { + stars int32 + commentary *string +} + +var reviews = make(map[string][]*review) + type Resolver struct{} -func (r *Resolver) Hero(args struct{ Episode string }) characterResolver { +func (r *Resolver) Hero(args *struct{ Episode string }) characterResolver { if args.Episode == "EMPIRE" { return &humanResolver{h: humanData["1000"]} } return &droidResolver{d: droidData["2001"]} } -func (r *Resolver) Reviews(args struct{ Episode string }) *[]*reviewResolver { - panic("TODO") +func (r *Resolver) Reviews(args *struct{ Episode string }) []*reviewResolver { + var l []*reviewResolver + for _, review := range reviews[args.Episode] { + l = append(l, &reviewResolver{review}) + } + return l } -func (r *Resolver) Search(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) { @@ -305,10 +316,10 @@ func (r *Resolver) Search(args struct{ Text string }) *[]searchResultResolver { l = append(l, &starshipResolver{s: s}) } } - return &l + return l } -func (r *Resolver) Character(args struct{ ID string }) characterResolver { +func (r *Resolver) Character(args *struct{ ID string }) characterResolver { if h := humanData[args.ID]; h != nil { return &humanResolver{h: h} } @@ -318,32 +329,37 @@ func (r *Resolver) Character(args struct{ ID string }) characterResolver { return nil } -func (r *Resolver) Human(args struct{ ID string }) *humanResolver { +func (r *Resolver) Human(args *struct{ ID string }) *humanResolver { if h := humanData[args.ID]; h != nil { return &humanResolver{h: h} } return nil } -func (r *Resolver) Droid(args struct{ ID string }) *droidResolver { +func (r *Resolver) Droid(args *struct{ ID string }) *droidResolver { if d := droidData[args.ID]; d != nil { return &droidResolver{d: d} } return nil } -func (r *Resolver) Starship(args struct{ ID string }) *starshipResolver { +func (r *Resolver) Starship(args *struct{ ID string }) *starshipResolver { if s := starshipData[args.ID]; s != nil { return &starshipResolver{s: s} } return nil } -func (r *Resolver) CreateReview(args struct { +func (r *Resolver) CreateReview(args *struct { Episode string Review *reviewInput }) *reviewResolver { - panic("TODO") + review := &review{ + stars: args.Review.Stars, + commentary: &args.Review.Commentary, + } + reviews[args.Episode] = append(reviews[args.Episode], review) + return &reviewResolver{review} } type friendsConenctionArgs struct { @@ -355,7 +371,7 @@ type characterResolver interface { ID() string Name() string Friends() *[]characterResolver - FriendsConnection(friendsConenctionArgs) (*friendsConnectionResolver, error) + FriendsConnection(*friendsConenctionArgs) (*friendsConnectionResolver, error) AppearsIn() []string ToHuman() (*humanResolver, bool) ToDroid() (*droidResolver, bool) @@ -388,7 +404,7 @@ func (r *humanResolver) Name() string { return r.h.Name } -func (r *humanResolver) Height(args struct{ Unit string }) float64 { +func (r *humanResolver) Height(args *struct{ Unit string }) float64 { return convertLength(r.h.Height, args.Unit) } @@ -404,7 +420,7 @@ func (r *humanResolver) Friends() *[]characterResolver { return resolveCharacters(r.h.Friends) } -func (r *humanResolver) FriendsConnection(args friendsConenctionArgs) (*friendsConnectionResolver, error) { +func (r *humanResolver) FriendsConnection(args *friendsConenctionArgs) (*friendsConnectionResolver, error) { return newFriendsConnectionResolver(r.h.Friends, args) } @@ -441,7 +457,7 @@ func (r *droidResolver) Friends() *[]characterResolver { return resolveCharacters(r.d.Friends) } -func (r *droidResolver) FriendsConnection(args friendsConenctionArgs) (*friendsConnectionResolver, error) { +func (r *droidResolver) FriendsConnection(args *friendsConenctionArgs) (*friendsConnectionResolver, error) { return newFriendsConnectionResolver(r.d.Friends, args) } @@ -473,7 +489,7 @@ func (r *starshipResolver) Name() string { return r.s.Name } -func (r *starshipResolver) Length(args struct{ Unit string }) float64 { +func (r *starshipResolver) Length(args *struct{ Unit string }) float64 { return convertLength(r.s.Length, args.Unit) } @@ -519,14 +535,15 @@ func resolveCharacter(id string) characterResolver { } type reviewResolver struct { + r *review } func (r *reviewResolver) Stars() int32 { - panic("TODO") + return r.r.stars } func (r *reviewResolver) Commentary() *string { - panic("TODO") + return r.r.commentary } type friendsConnectionResolver struct { @@ -535,7 +552,7 @@ type friendsConnectionResolver struct { to int } -func newFriendsConnectionResolver(ids []string, args friendsConenctionArgs) (*friendsConnectionResolver, error) { +func newFriendsConnectionResolver(ids []string, args *friendsConenctionArgs) (*friendsConnectionResolver, error) { from := 0 if args.After != "" { b, err := base64.StdEncoding.DecodeString(args.After) diff --git a/graphql_test.go b/graphql_test.go index ca76488ffbf..476c57e8016 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -655,34 +655,75 @@ var tests = []struct { `, }, - // { - // name: "StarWarsMutation1", - // schema: starwars.Schema, - // resolver: &starwars.Resolver{}, - // query: ` - // mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { - // createReview(episode: $ep, review: $review) { - // stars - // commentary - // } - // } - // `, - // variables: map[string]interface{}{ - // "ep": "JEDI", - // "review": map[string]interface{}{ - // "stars": 5, - // "commentary": "This is a great movie!", - // }, - // }, - // result: ` - // { - // "createReview": { - // "stars": 5, - // "commentary": "This is a great movie!" - // } - // } - // `, - // }, + { + name: "StarWarsMutation1", + schema: starwars.Schema, + resolver: &starwars.Resolver{}, + query: ` + { + reviews(episode: "JEDI") { + stars + commentary + } + } + `, + result: ` + { + "reviews": [] + } + `, + }, + + { + name: "StarWarsMutation2", + schema: starwars.Schema, + resolver: &starwars.Resolver{}, + query: ` + mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { + createReview(episode: $ep, review: $review) { + stars + commentary + } + } + `, + variables: map[string]interface{}{ + "ep": "JEDI", + "review": map[string]interface{}{ + "stars": 5, + "commentary": "This is a great movie!", + }, + }, + result: ` + { + "createReview": { + "stars": 5, + "commentary": "This is a great movie!" + } + } + `, + }, + + { + name: "StarWarsMutation3", + schema: starwars.Schema, + resolver: &starwars.Resolver{}, + query: ` + { + reviews(episode: "JEDI") { + stars + commentary + } + } + `, + result: ` + { + "reviews": [{ + "stars": 5, + "commentary": "This is a great movie!" + }] + } + `, + }, { name: "StarWarsIntrospection1", diff --git a/internal/exec/exec.go b/internal/exec/exec.go index 0ed21dd28b0..205a0f67318 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -225,7 +225,7 @@ func makeFieldExec(s *schema.Schema, f *schema.Field, m reflect.Method, methodIn return nil, fmt.Errorf("must have parameter for field arguments") } var err error - argsExec, err = makeInputObjectExec(in[0], &f.Args) + argsExec, err = makeInputObjectExec(&f.Args, in[0]) if err != nil { return nil, err } @@ -260,34 +260,63 @@ func makeFieldExec(s *schema.Schema, f *schema.Field, m reflect.Method, methodIn return fe, nil } -func makeInputObjectExec(typ reflect.Type, obj *schema.InputObject) (*inputObjectExec, error) { - e := &inputObjectExec{ - typ: typ, +func makeInputObjectExec(obj *schema.InputObject, typ reflect.Type) (*inputObjectExec, error) { + if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Struct { + return nil, fmt.Errorf("expected pointer to struct, got %s", typ) } + structType := typ.Elem() - for _, arg := range obj.InputFields { + var fields []*inputFieldExec + defaultStruct := reflect.New(structType).Elem() + for _, f := range obj.InputFields { fe := &inputFieldExec{ - name: arg.Name, - typ: arg.Type, + name: f.Name, } - sf, ok := e.typ.FieldByNameFunc(func(n string) bool { return strings.EqualFold(n, arg.Name) }) + sf, ok := structType.FieldByNameFunc(func(n string) bool { return strings.EqualFold(n, f.Name) }) if !ok { - return nil, fmt.Errorf("missing argument %q", arg.Name) + return nil, fmt.Errorf("missing argument %q", f.Name) } fe.fieldIndex = sf.Index - if !checkType(arg.Type, sf.Type) { - return nil, fmt.Errorf("argument %q with wrong type", arg.Name) + + ft := f.Type + if nn, ok := ft.(*common.NonNull); ok { + ft = nn.OfType + } + typeErr := fmt.Errorf("argument %q with wrong type", f.Name) + switch ft := ft.(type) { + case *schema.Scalar: + if sf.Type != scalarTypes[ft.Name] { + return nil, typeErr + } + fe.exec = &scalarInputExec{ft} + case *schema.Enum: + if sf.Type != scalarTypes["String"] { + return nil, typeErr + } + fe.exec = &scalarInputExec{&schema.Scalar{Name: "String"}} + case *schema.InputObject: + e, err := makeInputObjectExec(ft, sf.Type) + if err != nil { + return nil, err + } + fe.exec = e + default: + panic("TODO") } - if arg.Default != nil { - fe.defaultVal = reflect.ValueOf(arg.Default) + if f.Default != nil { + defaultStruct.FieldByIndex(fe.fieldIndex).Set(fe.exec.eval(f.Default)) } - e.fields = append(e.fields, fe) + fields = append(fields, fe) } - return e, nil + return &inputObjectExec{ + structType: structType, + defaultStruct: defaultStruct, + fields: fields, + }, nil } func makeTypeAssertions(s *schema.Schema, typeName string, impls []*schema.Object, resolverType reflect.Type, typeRefMap map[typeRefMapKey]*typeRef) (map[string]*typeAssertExec, error) { @@ -555,32 +584,46 @@ type typeAssertExec struct { } type inputObjectExec struct { - typ reflect.Type - fields []*inputFieldExec + structType reflect.Type + defaultStruct reflect.Value + fields []*inputFieldExec +} + +type inputExec interface { + eval(value interface{}) reflect.Value } type inputFieldExec struct { name string - typ common.Type fieldIndex []int - defaultVal reflect.Value + exec inputExec } -func (e *inputObjectExec) eval(values map[string]interface{}) reflect.Value { - v := reflect.New(e.typ).Elem() +func (e *inputObjectExec) eval(value interface{}) reflect.Value { + values := value.(map[string]interface{}) + v := reflect.New(e.structType) + v.Elem().Set(e.defaultStruct) for _, f := range e.fields { - value, ok := values[f.name] - if !ok { - if f.defaultVal.IsValid() { - v.FieldByIndex(f.fieldIndex).Set(f.defaultVal) - } - continue + if value, ok := values[f.name]; ok { + v.Elem().FieldByIndex(f.fieldIndex).Set(f.exec.eval(value)) } - v.FieldByIndex(f.fieldIndex).Set(reflect.ValueOf(value)) } return v } +type scalarInputExec struct { + scalar *schema.Scalar +} + +func (e *scalarInputExec) eval(value interface{}) reflect.Value { + switch e.scalar.Name { + case "Int": + return reflect.ValueOf(int32(value.(int))) + default: + return reflect.ValueOf(value) + } +} + func skipByDirective(r *request, d map[string]*query.Directive) bool { if skip, ok := d["skip"]; ok { if skip.Arguments["if"].Eval(r.vars).(bool) { @@ -594,18 +637,3 @@ func skipByDirective(r *request, d map[string]*query.Directive) bool { } return false } - -func checkType(st common.Type, rt reflect.Type) bool { - if nn, ok := st.(*common.NonNull); ok { - st = nn.OfType - } - - switch st := st.(type) { - case *schema.Scalar: - return rt == scalarTypes[st.Name] - case *schema.Enum: - return rt == scalarTypes["String"] - default: - return true - } -} diff --git a/internal/exec/introspection.go b/internal/exec/introspection.go index 5686f1a6b95..4886c8bdd4f 100644 --- a/internal/exec/introspection.go +++ b/internal/exec/introspection.go @@ -269,7 +269,7 @@ func (r *typeResolver) Description() *string { return nil } -func (r *typeResolver) Fields(args struct{ IncludeDeprecated bool }) *[]*fieldResolver { +func (r *typeResolver) Fields(args *struct{ IncludeDeprecated bool }) *[]*fieldResolver { var fields map[string]*schema.Field var fieldOrder []string switch t := r.typ.(type) { @@ -321,7 +321,7 @@ func (r *typeResolver) PossibleTypes() *[]*typeResolver { return &l } -func (r *typeResolver) EnumValues(args struct{ IncludeDeprecated bool }) *[]*enumValueResolver { +func (r *typeResolver) EnumValues(args *struct{ IncludeDeprecated bool }) *[]*enumValueResolver { t, ok := r.typ.(*schema.Enum) if !ok { return nil diff --git a/internal/lexer/lexer.go b/internal/lexer/lexer.go index 19b313acee7..1696f730bba 100644 --- a/internal/lexer/lexer.go +++ b/internal/lexer/lexer.go @@ -71,11 +71,11 @@ func (l *Lexer) ConsumeKeyword(keyword string) { l.Consume() } -func (l *Lexer) ConsumeInt() int32 { +func (l *Lexer) ConsumeInt() int { text := l.sc.TokenText() l.ConsumeToken(scanner.Int) value, _ := strconv.Atoi(text) - return int32(value) + return value } func (l *Lexer) ConsumeFloat() float64 {