diff --git a/cmd/ggraphqlc/dumper.go b/cmd/ggraphqlc/dumper.go new file mode 100644 index 00000000000..05c8837b4a7 --- /dev/null +++ b/cmd/ggraphqlc/dumper.go @@ -0,0 +1,176 @@ +package main + +import ( + "fmt" + "io" + "strconv" + "strings" +) + +type writer struct { + extractor + out io.Writer + indent int +} + +func write(extractor extractor, out io.Writer) { + wr := writer{extractor, out, 0} + + wr.writePackage() + wr.writeImports() + wr.writeInterface() + wr.writeResolver() + wr.writeSchema() +} + +func (w *writer) emit(format string, args ...interface{}) { + io.WriteString(w.out, fmt.Sprintf(format, args...)) +} + +func (w *writer) emitIndent() { + io.WriteString(w.out, strings.Repeat(" ", w.indent)) +} + +func (w *writer) begin(format string, args ...interface{}) { + w.emitIndent() + w.emit(format, args...) + w.lf() + w.indent++ +} + +func (w *writer) end(format string, args ...interface{}) { + w.indent-- + w.emitIndent() + w.emit(format, args...) + w.lf() +} + +func (w *writer) line(format string, args ...interface{}) { + w.emitIndent() + w.emit(format, args...) + w.lf() +} + +func (w *writer) lf() { + w.out.Write([]byte("\n")) +} + +func (w *writer) writePackage() { + w.line("package %s", w.PackageName) + w.lf() +} + +func (w *writer) writeImports() { + w.begin("import (") + for local, pkg := range w.Imports { + w.line("%s %s", local, strconv.Quote(pkg)) + } + w.end(")") + w.lf() +} + +func (w *writer) writeInterface() { + w.begin("type Resolvers interface {") + for _, o := range w.Objects { + for _, f := range o.Fields { + if f.Bind != "" { + continue + } + + w.emitIndent() + w.emit("%s_%s(", o.Name, f.Name) + + first := true + for _, arg := range f.Args { + if !first { + w.emit(",") + } + first = false + w.emit("%s %s", arg.Name, arg.Type.Local()) + } + w.emit(") (%s, error)", f.Type.Local()) + w.lf() + } + } + w.end("}") + w.lf() +} + +func (w *writer) writeResolver() { + w.begin("func NewResolver(r Resolvers) exec.Root {") + w.line("return &resolvers{r}") + w.end("}") + w.lf() + + w.begin("type resolvers struct {") + w.line("resolvers Resolvers") + w.end("}") + w.lf() + + for _, object := range w.Objects { + w.writeObjectResolver(object) + } +} + +func (w *writer) writeObjectResolver(object object) { + objectName := "it" + if object.Type.Name != "interface{}" { + objectName = "object" + } + + w.begin("func (r *resolvers) %s(ec *exec.ExecutionContext, %s interface{}, field string, arguments map[string]interface{}, sels []query.Selection) jsonw.Encodable {", object.Type.GraphQLName, objectName) + if object.Type.Name != "interface{}" { + w.line("it := object.(*%s)", object.Type.Local()) + } + + w.line("switch field {") + + for _, field := range object.Fields { + w.begin("case %s:", strconv.Quote(field.Name)) + + if field.Bind != "" { + w.writeFieldBind(field) + } else { + w.writeFieldResolver(object, field) + } + + w.end("") + } + + w.line("}") + w.line(`panic("unknown field " + field)`) + w.end("}") + w.lf() +} + +func (w *writer) writeFieldBind(field Field) { + w.line("return jsonw.%s(it.%s)", field.Type.GraphQLName, field.Bind) +} + +func (w *writer) writeFieldResolver(object object, field Field) { + call := fmt.Sprintf("result, err := r.resolvers.%s_%s", object.Name, field.Name) + if len(field.Args) == 0 { + w.line(call + "()") + } else { + w.begin(call + "(") + for _, arg := range field.Args { + w.line("arguments[%s].(%s),", strconv.Quote(arg.Name), arg.Type.Local()) + } + w.end(")") + } + + w.line("if err != nil {") + w.line(" ec.Error(err)") + w.line(" return jsonw.Null") + w.line("}") + + result := "result" + if !strings.HasPrefix(field.Type.Prefix, "*") { + result = "&result" + } + w.line("return ec.ExecuteSelectionSet(sels, r.%s, %s)", field.Type.Name, result) +} + +func (w *writer) writeSchema() { + w.line("var Schema = schema.MustParse(%s)", strconv.Quote(w.schemaRaw)) +} diff --git a/cmd/ggraphqlc/extractor.go b/cmd/ggraphqlc/extractor.go new file mode 100644 index 00000000000..836d7fb8c4e --- /dev/null +++ b/cmd/ggraphqlc/extractor.go @@ -0,0 +1,282 @@ +package main + +import ( + "bytes" + "fmt" + "go/types" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/vektah/graphql-go/common" + "github.com/vektah/graphql-go/schema" + "golang.org/x/tools/go/loader" +) + +type extractor struct { + Errors []string + PackageName string + Objects []object + goTypeMap map[string]string + Imports map[string]string // local -> full path + schemaRaw string +} + +func (e *extractor) errorf(format string, args ...interface{}) { + e.Errors = append(e.Errors, fmt.Sprintf(format, args...)) +} + +// getType to put in a file for a given fully resolved type, and add any Imports required +// eg name = github.com/my/pkg.myType will return `pkg.myType` and add an import for `github.com/my/pkg` +func (e *extractor) getType(name string) Type { + if fieldType, ok := e.goTypeMap[name]; ok { + parts := strings.Split(fieldType, ".") + if len(parts) == 1 { + return Type{ + GraphQLName: name, + Name: parts[0], + } + } + + packageName := strings.Join(parts[:len(parts)-1], ".") + typeName := parts[len(parts)-1] + + localName := filepath.Base(packageName) + i := 0 + for pkg, found := e.Imports[localName]; found && pkg != packageName; localName = filepath.Base(packageName) + strconv.Itoa(i) { + i++ + if i > 10 { + panic("too many collisions") + } + } + + e.Imports[localName] = packageName + return Type{ + GraphQLName: name, + ImportedAs: localName, + Name: typeName, + Package: packageName, + } + } + fmt.Fprintf(os.Stderr, "unknown go type for %s, using interface{}. you should add it to types.json\n", name) + return Type{ + GraphQLName: name, + Name: "interface{}", + } +} + +func (e *extractor) buildGoTypeString(t common.Type) Type { + prefix := "" + usePtr := true + for { + if _, nonNull := t.(*common.NonNull); nonNull { + usePtr = false + } else if _, nonNull := t.(*common.List); nonNull { + usePtr = false + } else { + if usePtr { + prefix += "*" + } + usePtr = true + } + + switch val := t.(type) { + case *common.NonNull: + t = val.OfType + case *common.List: + prefix += "[]" + t = val.OfType + case *schema.Scalar: + switch val.Name { + case "String": + return Type{Prefix: prefix, GraphQLName: "String", Name: "string"} + case "Boolean": + return Type{Prefix: prefix, GraphQLName: "Boolean", Name: "bool"} + case "ID": + return Type{Prefix: prefix, GraphQLName: "ID", Name: "int"} + case "Int": + return Type{Prefix: prefix, GraphQLName: "Int", Name: "int"} + default: + panic(fmt.Errorf("unknown scalar %s", val.Name)) + } + case *schema.Object: + t := e.getType(val.Name) + t.Prefix = prefix + return t + case *common.TypeName: + t := e.getType(val.Name) + t.Prefix = prefix + return t + default: + panic(fmt.Errorf("unknown type %T", t)) + } + + } +} + +func (e *extractor) extract(s *schema.Schema) { + for _, schemaType := range s.Types { + + switch schemaType := schemaType.(type) { + case *schema.Object: + if strings.HasPrefix(schemaType.Name, "__") { + continue + } + object := object{ + Name: schemaType.Name, + Type: e.getType(schemaType.Name), + } + for _, field := range schemaType.Fields { + var args []Arg + for _, arg := range field.Args { + args = append(args, Arg{ + Name: arg.Name.Name, + Type: e.buildGoTypeString(arg.Type), + }) + } + + object.Fields = append(object.Fields, Field{ + Name: field.Name, + Type: e.buildGoTypeString(field.Type), + Args: args, + }) + } + e.Objects = append(e.Objects, object) + } + } +} + +func (e *extractor) introspect() error { + var conf loader.Config + for _, name := range e.Imports { + conf.Import(name) + } + + prog, err := conf.Load() + if err != nil { + return err + } + + for _, o := range e.Objects { + if o.Type.Package == "" { + continue + } + pkg := prog.Package(o.Type.Package) + + for ast, object := range pkg.Defs { + if ast.Name != o.Type.Name { + continue + } + + e.findBindTargets(object.Type(), o) + } + } + + return nil +} + +func (e *extractor) findBindTargets(t types.Type, object object) { + switch t := t.(type) { + case *types.Named: + // Todo: bind to funcs? + e.findBindTargets(t.Underlying(), object) + + case *types.Struct: + for i := 0; i < t.NumFields(); i++ { + field := t.Field(i) + // Todo: struct tags, name and - at least + + // Todo: check for type matches before binding too? + if objectField := object.GetField(field.Name()); objectField != nil { + objectField.Bind = field.Name() + } + } + t.Underlying() + + default: + panic(fmt.Errorf("unknown type %T", t)) + } + +} + +func (e *extractor) String() string { + b := &bytes.Buffer{} + + b.WriteString("Imports:\n") + for local, pkg := range e.Imports { + b.WriteString("\t" + local + " " + strconv.Quote(pkg) + "\n") + } + b.WriteString("\n") + + for _, o := range e.Objects { + b.WriteString("object " + o.Name + ":\n") + + for _, f := range o.Fields { + if f.Bind != "" { + b.WriteString("\t" + f.Bind + " " + f.Type.Local() + "\n") + continue + } + b.WriteString("\t" + o.Name + "_" + f.Name) + + b.WriteString("(") + first := true + for _, arg := range f.Args { + if !first { + b.WriteString(", ") + } + first = false + b.WriteString(arg.Name + " " + arg.Type.Local()) + } + b.WriteString(")") + + b.WriteString(" " + f.Type.Local() + "\n") + } + + b.WriteString("\n") + } + + return b.String() +} + +type Type struct { + GraphQLName string + Name string + Package string + ImportedAs string + Prefix string +} + +func (t Type) Local() string { + if t.ImportedAs == "" { + return t.Prefix + t.Name + } + return t.Prefix + t.ImportedAs + "." + t.Name +} + +type object struct { + Name string + Fields []Field + Type Type +} + +type Field struct { + Name string + Type Type + Args []Arg + Bind string +} + +func (o *object) GetField(name string) *Field { + for i, field := range o.Fields { + if strings.EqualFold(field.Name, name) { + return &o.Fields[i] + } + } + return nil +} + +type Arg struct { + Name string + Type Type +} diff --git a/cmd/ggraphqlc/main.go b/cmd/ggraphqlc/main.go index 1be928744c7..dc270f29dad 100644 --- a/cmd/ggraphqlc/main.go +++ b/cmd/ggraphqlc/main.go @@ -6,18 +6,12 @@ import ( "fmt" "io/ioutil" "os" - "strings" - "path/filepath" - "strconv" - - "bytes" - - "github.com/vektah/graphql-go/common" "github.com/vektah/graphql-go/schema" ) var output = flag.String("out", "gen.go", "the file to write to") +var schemaFilename = flag.String("schema", "schema.graphql", "the graphql schema to generate types from") var typemap = flag.String("typemap", "types.json", "a json map going from graphql to golang types") var packageName = flag.String("package", "graphql", "the package name") var help = flag.Bool("h", false, "this usage text") @@ -29,32 +23,25 @@ func main() { } flag.Parse() - args := flag.Args() if *help { flag.Usage() os.Exit(1) } - if len(args) != 1 { - flag.Usage() - fmt.Fprintln(os.Stderr, "\npath to schema is required") - os.Exit(1) - } - schema := schema.New() - b, err := ioutil.ReadFile(args[0]) + schemaRaw, err := ioutil.ReadFile(*schemaFilename) if err != nil { fmt.Fprintln(os.Stderr, "unable to open schema: "+err.Error()) os.Exit(1) } - if err = schema.Parse(string(b)); err != nil { + if err = schema.Parse(string(schemaRaw)); err != nil { fmt.Fprintln(os.Stderr, "unable to parse schema: "+err.Error()) os.Exit(1) } - b, err = ioutil.ReadFile(*typemap) + b, err := ioutil.ReadFile(*typemap) if err != nil { fmt.Fprintln(os.Stderr, "unable to open typemap: "+err.Error()) os.Exit(1) @@ -67,8 +54,15 @@ func main() { } e := extractor{ - goTypeMap: goTypes, - imports: map[string]string{}, + PackageName: *packageName, + goTypeMap: goTypes, + schemaRaw: string(schemaRaw), + Imports: map[string]string{ + "exec": "github.com/vektah/graphql-go/exec", + "jsonw": "github.com/vektah/graphql-go/jsonw", + "query": "github.com/vektah/graphql-go/query", + "schema": "github.com/vektah/graphql-go/schema", + }, } e.extract(schema) @@ -79,172 +73,16 @@ func main() { os.Exit(1) } - fmt.Println(e.String()) -} - -type extractor struct { - Errors []string - Objects []Object - goTypeMap map[string]string - imports map[string]string // local -> full path -} - -func (e *extractor) errorf(format string, args ...interface{}) { - e.Errors = append(e.Errors, fmt.Sprintf(format, args...)) -} - -// get the type name to put in a file for a given fully resolved type, and add any imports required -// eg name = github.com/my/pkg.myType will return `pkg.myType` and add an import for `github.com/my/pkg` -func (e *extractor) get(name string) string { - if fieldType, ok := e.goTypeMap[name]; ok { - parts := strings.Split(fieldType, ".") - if len(parts) == 1 { - return parts[0] - } - - packageName := strings.Join(parts[:len(parts)-1], ".") - typeName := parts[len(parts)-1] - - localName := filepath.Base(packageName) - i := 0 - for pkg, found := e.imports[localName]; found && pkg != packageName; localName = filepath.Base(packageName) + strconv.Itoa(i) { - i++ - if i > 10 { - panic("too many collisions") - } - } - - e.imports[localName] = packageName - return localName + "." + typeName - - } - fmt.Fprintf(os.Stderr, "unknown go type for %s, using interface{}. you should add it to types.json\n", name) - return "interface{}" -} - -func (e *extractor) buildGoTypeString(t common.Type) string { - name := "" - usePtr := true - for { - if _, nonNull := t.(*common.NonNull); nonNull { - usePtr = false - } else if _, nonNull := t.(*common.List); nonNull { - usePtr = false - } else { - if usePtr { - name += "*" - } - usePtr = true - } - - switch val := t.(type) { - case *common.NonNull: - t = val.OfType - case *common.List: - name += "[]" - t = val.OfType - case *schema.Scalar: - switch val.Name { - case "String": - return "string" - case "Boolean": - return "boolean" - case "ID": - return "string" - case "Int": - return "int" - default: - panic(fmt.Errorf("unknown scalar %s", val.Name)) - } - return val.Name - case *schema.Object: - return name + e.get(val.Name) - case *common.TypeName: - return name + e.get(val.Name) - default: - panic(fmt.Errorf("unknown type %T", t)) - } - - } -} - -func (e *extractor) extract(s *schema.Schema) { - for _, schemaType := range s.Types { - - switch schemaType := schemaType.(type) { - case *schema.Object: - if strings.HasPrefix(schemaType.Name, "__") { - continue - } - object := Object{ - Name: schemaType.Name, - } - for _, field := range schemaType.Fields { - var args []Arg - for _, arg := range field.Args { - args = append(args, Arg{ - Name: arg.Name.Name, - Type: e.buildGoTypeString(arg.Type), - }) - } - - object.Fields = append(object.Fields, Field{ - Name: field.Name, - ReturnType: e.buildGoTypeString(field.Type), - Args: args, - }) - } - e.Objects = append(e.Objects, object) - } - } -} - -func (e *extractor) String() string { - b := &bytes.Buffer{} - - b.WriteString("imports:\n") - for local, pkg := range e.imports { - b.WriteString("\t" + local + " " + strconv.Quote(pkg) + "\n") + if err := e.introspect(); err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) } - b.WriteString("\n") - - for _, o := range e.Objects { - b.WriteString("object " + o.Name + ":\n") - - for _, f := range o.Fields { - b.WriteString("\t" + f.Name + "(") - first := true - for _, arg := range f.Args { - if !first { - b.WriteString(", ") - } - first = false - b.WriteString(arg.Name + " " + arg.Type) - } - - b.WriteString(") " + f.ReturnType + "\n") - } - - b.WriteString("\n") + outFile, err := os.Create(*output) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) } - - return b.String() -} - -type Object struct { - Name string - Fields []Field - goType string -} - -type Field struct { - Name string - ReturnType string - Args []Arg -} - -type Arg struct { - Name string - Type string + defer outFile.Close() + write(e, outFile) } diff --git a/example/todo/gen/generated.go b/example/todo/gen/generated.go index 38492f13cac..608b10f2cfb 100644 --- a/example/todo/gen/generated.go +++ b/example/todo/gen/generated.go @@ -1,22 +1,19 @@ package gen import ( - "fmt" - - "github.com/vektah/graphql-go/example/todo" - "github.com/vektah/graphql-go/exec" - "github.com/vektah/graphql-go/jsonw" - "github.com/vektah/graphql-go/query" - "github.com/vektah/graphql-go/schema" + exec "github.com/vektah/graphql-go/exec" + jsonw "github.com/vektah/graphql-go/jsonw" + query "github.com/vektah/graphql-go/query" + schema "github.com/vektah/graphql-go/schema" + todo "github.com/vektah/graphql-go/example/todo" ) type Resolvers interface { + Mutation_createTodo(text string) (todo.Todo, error) + Mutation_updateTodo(id int,done bool) (todo.Todo, error) Query_todo(id int) (*todo.Todo, error) Query_lastTodo() (*todo.Todo, error) - Query_todos() ([]*todo.Todo, error) - - Mutation_createTodo(text string) (todo.Todo, error) - Mutation_updateTodo(id int, done bool) (*todo.Todo, error) + Query_todos() ([]todo.Todo, error) } func NewResolver(r Resolvers) exec.Root { @@ -27,105 +24,79 @@ type resolvers struct { resolvers Resolvers } -func (r *resolvers) Query(ec *exec.ExecutionContext, object interface{}, field string, arguments map[string]interface{}, sels []query.Selection) jsonw.Encodable { +func (r *resolvers) Mutation(ec *exec.ExecutionContext, it interface{}, field string, arguments map[string]interface{}, sels []query.Selection) jsonw.Encodable { switch field { - case "todo": - result, err := r.resolvers.Query_todo(arguments["id"].(int)) - if err != nil { - ec.Error(err) - return jsonw.Null - } - return ec.ExecuteSelectionSet(sels, r.todo, result) - - case "lastTodo": - result, err := r.resolvers.Query_lastTodo() + case "createTodo": + result, err := r.resolvers.Mutation_createTodo( + arguments["text"].(string), + ) if err != nil { ec.Error(err) return jsonw.Null } - return ec.ExecuteSelectionSet(sels, r.todo, result) - - case "todos": - result, err := r.resolvers.Query_todos() + return ec.ExecuteSelectionSet(sels, r.Todo, &result) + + case "updateTodo": + result, err := r.resolvers.Mutation_updateTodo( + arguments["id"].(int), + arguments["done"].(bool), + ) if err != nil { ec.Error(err) return jsonw.Null } - - var enc jsonw.Array - for _, val := range result { - enc = append(enc, ec.ExecuteSelectionSet(sels, r.todo, val)) - } - - return enc + return ec.ExecuteSelectionSet(sels, r.Todo, &result) + } - panic("unknown field " + field) } -func (r *resolvers) Mutation(ec *exec.ExecutionContext, object interface{}, field string, arguments map[string]interface{}, sels []query.Selection) jsonw.Encodable { +func (r *resolvers) Query(ec *exec.ExecutionContext, it interface{}, field string, arguments map[string]interface{}, sels []query.Selection) jsonw.Encodable { switch field { - case "createTodo": - result, err := r.resolvers.Mutation_createTodo(arguments["text"].(string)) + case "todo": + result, err := r.resolvers.Query_todo( + arguments["id"].(int), + ) if err != nil { ec.Error(err) return jsonw.Null } - return ec.ExecuteSelectionSet(sels, r.todo, result) - - case "updateTodo": - result, err := r.resolvers.Mutation_updateTodo(arguments["id"].(int), arguments["done"].(bool)) + return ec.ExecuteSelectionSet(sels, r.Todo, result) + + case "lastTodo": + result, err := r.resolvers.Query_lastTodo() + if err != nil { + ec.Error(err) + return jsonw.Null + } + return ec.ExecuteSelectionSet(sels, r.Todo, result) + + case "todos": + result, err := r.resolvers.Query_todos() if err != nil { ec.Error(err) return jsonw.Null } - return ec.ExecuteSelectionSet(sels, r.todo, result) + return ec.ExecuteSelectionSet(sels, r.Todo, &result) + } - panic("unknown field " + field) } -func (r *resolvers) todo(ec *exec.ExecutionContext, object interface{}, field string, arguments map[string]interface{}, sels []query.Selection) jsonw.Encodable { - fmt.Print("todoExec", object) +func (r *resolvers) Todo(ec *exec.ExecutionContext, object interface{}, field string, arguments map[string]interface{}, sels []query.Selection) jsonw.Encodable { + it := object.(*todo.Todo) switch field { case "id": - return jsonw.Int(object.(*todo.Todo).ID) + return jsonw.ID(it.ID) + case "text": - return jsonw.String(object.(*todo.Todo).Text) + return jsonw.String(it.Text) + case "done": - return jsonw.Bool(object.(*todo.Todo).Done) + return jsonw.Boolean(it.Done) + } - return jsonw.Null -} - -var Schema *schema.Schema - -const schemaStr = `schema { - query: Query - mutation: Mutation -} -type Query { - todo(id: Integer!): Todo - lastTodo: Todo - todos: [Todos!]! - user(id: Integer!): User -} -type Mutation { - createTodo(text: String!): Todo! - updateTodo(id: Integer!, text: String!): Todo! -} -type Todo @go(type:"github.com/99designs/graphql-go/example/todo.Todo") { - id: ID! - text: String! - done: Boolean! - user: User! -} -type User @go(type:"github.com/99designs/graphql-go/example/todo.User"){ - id: ID! - name: String! + panic("unknown field " + field) } -` -func init() { - Schema = schema.MustParse(schemaStr) -} +var Schema = schema.MustParse("\nschema {\n\tquery: Query\n\tmutation: Mutation\n}\n\ntype Query {\n\ttodo(id: Int!): Todo\n\tlastTodo: Todo\n\ttodos: [Todo!]!\n}\n\ntype Mutation {\n\tcreateTodo(text: String!): Todo!\n\tupdateTodo(id: Int!, done: Boolean!): Todo!\n}\n\ntype Todo {\n\tid: ID!\n\ttext: String!\n\tdone: Boolean!\n}\n") diff --git a/example/todo/schema.graphql b/example/todo/schema.graphql index 82700c1e6c1..8cfce613020 100644 --- a/example/todo/schema.graphql +++ b/example/todo/schema.graphql @@ -12,7 +12,7 @@ type Query { type Mutation { createTodo(text: String!): Todo! - updateTodo(id: Int!, text: String!): Todo! + updateTodo(id: Int!, done: Boolean!): Todo! } type Todo { diff --git a/example/todo/todo.go b/example/todo/todo.go index f8b2c57a6d4..4222c62cbf0 100644 --- a/example/todo/todo.go +++ b/example/todo/todo.go @@ -1,5 +1,5 @@ -////go:generate graphgen -schema ./schema.graphql -// +//go:generate ggraphqlc -package gen -out gen/generated.go + package todo import ( @@ -14,13 +14,13 @@ type Todo struct { } type TodoResolver struct { - todos []*Todo + todos []Todo lastID int } func NewResolver() *TodoResolver { return &TodoResolver{ - todos: []*Todo{ + todos: []Todo{ {ID: 1, Text: "A todo not to forget", Done: false, UserID: 1}, {ID: 2, Text: "This is the most important", Done: false, UserID: 1}, {ID: 3, Text: "Please do this or else", Done: false, UserID: 1}, @@ -32,7 +32,7 @@ func NewResolver() *TodoResolver { func (r *TodoResolver) Query_todo(id int) (*Todo, error) { for _, todo := range r.todos { if todo.ID == id { - return todo, nil + return &todo, nil } } return nil, errors.New("not found") @@ -42,10 +42,10 @@ func (r *TodoResolver) Query_lastTodo() (*Todo, error) { if len(r.todos) == 0 { return nil, errors.New("not found") } - return r.todos[len(r.todos)-1], nil + return &r.todos[len(r.todos)-1], nil } -func (r *TodoResolver) Query_todos() ([]*Todo, error) { +func (r *TodoResolver) Query_todos() ([]Todo, error) { return r.todos, nil } @@ -58,22 +58,22 @@ func (r *TodoResolver) Mutation_createTodo(text string) (Todo, error) { Done: false, } - r.todos = append(r.todos, &newTodo) + r.todos = append(r.todos, newTodo) return newTodo, nil } -func (r *TodoResolver) Mutation_updateTodo(id int, done bool) (*Todo, error) { +func (r *TodoResolver) Mutation_updateTodo(id int, done bool) (Todo, error) { var affectedTodo *Todo for i := 0; i < len(r.todos); i++ { if r.todos[i].ID == id { r.todos[i].Done = done - affectedTodo = r.todos[i] + affectedTodo = &r.todos[i] break } } - return affectedTodo, errors.New("not found") + return *affectedTodo, errors.New("not found") } func (r *TodoResolver) id() int { diff --git a/jsonw/output.go b/jsonw/output.go index d27a3b2c45b..8d768fad052 100644 --- a/jsonw/output.go +++ b/jsonw/output.go @@ -69,11 +69,15 @@ func Int(v int) Encodable { return literal{[]byte(fmt.Sprintf("%d", v))} } +func ID(v int) Encodable { + return literal{[]byte(fmt.Sprintf("%d", v))} +} + func String(v string) Encodable { return literal{[]byte(strconv.Quote(v))} } -func Bool(v bool) Encodable { +func Boolean(v bool) Encodable { if v { return True } else {