diff --git a/cmd/ggraphqlc/dumper.go b/cmd/ggraphqlc/dumper.go index 05c8837b4a7..27866f3ca84 100644 --- a/cmd/ggraphqlc/dumper.go +++ b/cmd/ggraphqlc/dumper.go @@ -5,6 +5,7 @@ import ( "io" "strconv" "strings" + "unicode" ) type writer struct { @@ -73,12 +74,12 @@ func (w *writer) writeInterface() { w.begin("type Resolvers interface {") for _, o := range w.Objects { for _, f := range o.Fields { - if f.Bind != "" { + if f.VarName != "" || f.MethodName != "" { continue } w.emitIndent() - w.emit("%s_%s(", o.Name, f.Name) + w.emit("%s_%s(", o.Name, f.GraphQLName) first := true for _, arg := range f.Args { @@ -122,16 +123,19 @@ func (w *writer) writeObjectResolver(object object) { if object.Type.Name != "interface{}" { w.line("it := object.(*%s)", object.Type.Local()) } + w.line("if it == nil {") + w.line(" return jsonw.Null") + w.line("}") w.line("switch field {") for _, field := range object.Fields { - w.begin("case %s:", strconv.Quote(field.Name)) + w.begin("case %s:", strconv.Quote(field.GraphQLName)) - if field.Bind != "" { - w.writeFieldBind(field) + if field.VarName != "" { + w.writeVarResolver(field) } else { - w.writeFieldResolver(object, field) + w.writeMethodResolver(object, field) } w.end("") @@ -143,32 +147,90 @@ func (w *writer) writeObjectResolver(object object) { w.lf() } -func (w *writer) writeFieldBind(field Field) { - w.line("return jsonw.%s(it.%s)", field.Type.GraphQLName, field.Bind) +func (w *writer) writeMethodResolver(object object, field Field) { + var methodName string + if field.MethodName != "" { + methodName = field.MethodName + } else { + methodName = fmt.Sprintf("r.resolvers.%s_%s", object.Name, field.GraphQLName) + } + + if field.NoErr { + w.emitIndent() + w.emit("res := %s", methodName) + w.writeFuncArgs(field) + } else { + w.emitIndent() + w.emit("res, err := %s", methodName) + w.writeFuncArgs(field) + w.line("if err != nil {") + w.line(" ec.Error(err)") + w.line(" return jsonw.Null") + w.line("}") + } + + w.writeJsonType("json", field.Type, field.Type.Modifiers, "res") + + w.line("return json") +} + +func (w *writer) writeVarResolver(field Field) { + w.writeJsonType("res", field.Type, field.Type.Modifiers, field.VarName) + w.line("return res") } -func (w *writer) writeFieldResolver(object object, field Field) { - call := fmt.Sprintf("result, err := r.resolvers.%s_%s", object.Name, field.Name) +func (w *writer) writeFuncArgs(field Field) { if len(field.Args) == 0 { - w.line(call + "()") + w.emit("()") + w.lf() } else { - w.begin(call + "(") + w.indent++ + w.emit("(") + w.lf() 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("}") +func (w *writer) writeJsonType(result string, t Type, remainingMods []string, val string) { + isPtr := false + for i := 0; i < len(remainingMods); i++ { + switch remainingMods[i] { + case modPtr: + isPtr = true + case modList: + if isPtr { + val = "*" + val + } + w.line("%s := jsonw.Array{}", result) + w.begin("for _, val := range %s {", val) - result := "result" - if !strings.HasPrefix(field.Type.Prefix, "*") { - result = "&result" + w.writeJsonType(result+"1", t, remainingMods[i+1:], "val") + w.line("%s = append(%s, %s)", result, result, result+"1") + w.end("}") + return + } } - w.line("return ec.ExecuteSelectionSet(sels, r.%s, %s)", field.Type.Name, result) + + if t.Basic { + if isPtr { + val = "*" + val + } + w.line("%s := jsonw.%s(%s)", result, ucFirst(t.Name), val) + } else { + if !isPtr { + val = "&" + val + } + w.line("%s := ec.ExecuteSelectionSet(sels, r.%s, %s)", result, t.GraphQLName, val) + } +} + +func ucFirst(s string) string { + r := []rune(s) + r[0] = unicode.ToUpper(r[0]) + return string(r) } func (w *writer) writeSchema() { diff --git a/cmd/ggraphqlc/extractor.go b/cmd/ggraphqlc/extractor.go index 836d7fb8c4e..547ba1c3cbd 100644 --- a/cmd/ggraphqlc/extractor.go +++ b/cmd/ggraphqlc/extractor.go @@ -1,7 +1,6 @@ package main import ( - "bytes" "fmt" "go/types" "os" @@ -9,6 +8,8 @@ import ( "strconv" "strings" + "sort" + "github.com/vektah/graphql-go/common" "github.com/vektah/graphql-go/schema" "golang.org/x/tools/go/loader" @@ -66,8 +67,8 @@ func (e *extractor) getType(name string) Type { } } -func (e *extractor) buildGoTypeString(t common.Type) Type { - prefix := "" +func (e *extractor) buildType(t common.Type) Type { + var modifiers []string usePtr := true for { if _, nonNull := t.(*common.NonNull); nonNull { @@ -76,7 +77,7 @@ func (e *extractor) buildGoTypeString(t common.Type) Type { usePtr = false } else { if usePtr { - prefix += "*" + modifiers = append(modifiers, modPtr) } usePtr = true } @@ -85,33 +86,47 @@ func (e *extractor) buildGoTypeString(t common.Type) Type { case *common.NonNull: t = val.OfType case *common.List: - prefix += "[]" + modifiers = append(modifiers, modList) t = val.OfType case *schema.Scalar: + var goType string + switch val.Name { case "String": - return Type{Prefix: prefix, GraphQLName: "String", Name: "string"} - case "Boolean": - return Type{Prefix: prefix, GraphQLName: "Boolean", Name: "bool"} + goType = "string" case "ID": - return Type{Prefix: prefix, GraphQLName: "ID", Name: "int"} + goType = "string" + case "Boolean": + goType = "bool" case "Int": - return Type{Prefix: prefix, GraphQLName: "Int", Name: "int"} + goType = "int" default: panic(fmt.Errorf("unknown scalar %s", val.Name)) } + return Type{ + Basic: true, + Modifiers: modifiers, + GraphQLName: val.Name, + Name: goType, + } case *schema.Object: t := e.getType(val.Name) - t.Prefix = prefix + t.Modifiers = modifiers return t case *common.TypeName: t := e.getType(val.Name) - t.Prefix = prefix + t.Modifiers = modifiers return t + case *schema.Enum: + return Type{ + Basic: true, + Modifiers: modifiers, + GraphQLName: val.Name, + Name: "string", + } default: panic(fmt.Errorf("unknown type %T", t)) } - } } @@ -119,10 +134,8 @@ 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), @@ -132,19 +145,23 @@ func (e *extractor) extract(s *schema.Schema) { for _, arg := range field.Args { args = append(args, Arg{ Name: arg.Name.Name, - Type: e.buildGoTypeString(arg.Type), + Type: e.buildType(arg.Type), }) } object.Fields = append(object.Fields, Field{ - Name: field.Name, - Type: e.buildGoTypeString(field.Type), - Args: args, + GraphQLName: field.Name, + Type: e.buildType(field.Type), + Args: args, }) } e.Objects = append(e.Objects, object) } } + + sort.Slice(e.Objects, func(i, j int) bool { + return strings.Compare(e.Objects[i].Name, e.Objects[j].Name) == -1 + }) } func (e *extractor) introspect() error { @@ -164,22 +181,74 @@ func (e *extractor) introspect() error { } pkg := prog.Package(o.Type.Package) - for ast, object := range pkg.Defs { - if ast.Name != o.Type.Name { + for astNode, object := range pkg.Defs { + if astNode.Name != o.Type.Name { continue } e.findBindTargets(object.Type(), o) + // todo: break! } } return nil } +func (e *extractor) modifiersFromGoType(t types.Type) []string { + var modifiers []string + for { + switch val := t.(type) { + case *types.Pointer: + modifiers = append(modifiers, modPtr) + t = val.Elem() + case *types.Array: + modifiers = append(modifiers, modList) + t = val.Elem() + case *types.Slice: + modifiers = append(modifiers, modList) + t = val.Elem() + default: + return modifiers + } + } +} + func (e *extractor) findBindTargets(t types.Type, object object) { switch t := t.(type) { case *types.Named: - // Todo: bind to funcs? + for i := 0; i < t.NumMethods(); i++ { + method := t.Method(i) + if methodField := object.GetField(method.Name()); methodField != nil { + methodField.MethodName = "it." + method.Name() + sig := method.Type().(*types.Signature) + + methodField.Type.Modifiers = e.modifiersFromGoType(sig.Results().At(0).Type()) + + // check arg order matches code, not gql + + var newArgs []Arg + l2: + for j := 0; j < sig.Params().Len(); j++ { + param := sig.Params().At(j) + for _, oldArg := range methodField.Args { + if strings.EqualFold(oldArg.Name, param.Name()) { + oldArg.Type.Modifiers = e.modifiersFromGoType(param.Type()) + newArgs = append(newArgs, oldArg) + continue l2 + } + } + e.errorf("cannot match argument " + param.Name() + " to any argument in " + t.String()) + } + methodField.Args = newArgs + + if sig.Results().Len() == 1 { + methodField.NoErr = true + } else if sig.Results().Len() != 2 { + e.errorf("weird number of results on %s. expected either (result), or (result, error)", method.Name()) + } + } + } + e.findBindTargets(t.Underlying(), object) case *types.Struct: @@ -189,69 +258,45 @@ func (e *extractor) findBindTargets(t types.Type, object object) { // Todo: check for type matches before binding too? if objectField := object.GetField(field.Name()); objectField != nil { - objectField.Bind = field.Name() + objectField.VarName = "it." + field.Name() + objectField.Type.Modifiers = e.modifiersFromGoType(field.Type()) } } t.Underlying() + case *types.Signature: + // ignored + default: - panic(fmt.Errorf("unknown type %T", t)) + panic(fmt.Errorf("unknown type %T looking at %s", t, object.Name)) } } -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() -} +const ( + modList = "[]" + modPtr = "*" +) type Type struct { GraphQLName string Name string Package string ImportedAs string - Prefix string + Modifiers []string + Basic bool } func (t Type) Local() string { if t.ImportedAs == "" { - return t.Prefix + t.Name + return strings.Join(t.Modifiers, "") + t.Name } - return t.Prefix + t.ImportedAs + "." + t.Name + return strings.Join(t.Modifiers, "") + t.ImportedAs + "." + t.Name +} + +func (t Type) Ptr() Type { + t.Modifiers = append(t.Modifiers, modPtr) + return t } type object struct { @@ -260,22 +305,38 @@ type object struct { Type Type } +type enum struct { + Name string + Values []string +} + type Field struct { - Name string - Type Type - Args []Arg - Bind string + GraphQLName string + MethodName string + VarName string + Type Type + Args []Arg + NoErr bool } func (o *object) GetField(name string) *Field { for i, field := range o.Fields { - if strings.EqualFold(field.Name, name) { + if strings.EqualFold(field.GraphQLName, name) { return &o.Fields[i] } } return nil } +func (e *extractor) GetObject(name string) *object { + for i, o := range e.Objects { + if strings.EqualFold(o.Name, name) { + return &e.Objects[i] + } + } + return nil +} + type Arg struct { Name string Type Type diff --git a/cmd/ggraphqlc/main.go b/cmd/ggraphqlc/main.go index dc270f29dad..2f157cd1dc8 100644 --- a/cmd/ggraphqlc/main.go +++ b/cmd/ggraphqlc/main.go @@ -7,10 +7,12 @@ import ( "io/ioutil" "os" + "io" + "github.com/vektah/graphql-go/schema" ) -var output = flag.String("out", "gen.go", "the file to write to") +var output = flag.String("out", "-", "the file to write to, - for stdout") 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") @@ -47,7 +49,16 @@ func main() { os.Exit(1) } - goTypes := map[string]string{} + goTypes := map[string]string{ + "__Directive": "github.com/vektah/graphql-go/introspection.Directive", + "__Type": "github.com/vektah/graphql-go/introspection.Type", + "__Field": "github.com/vektah/graphql-go/introspection.Field", + "__EnumValue": "github.com/vektah/graphql-go/introspection.EnumValue", + "__InputValue": "github.com/vektah/graphql-go/introspection.InputValue", + "__Schema": "github.com/vektah/graphql-go/introspection.Schema", + "Query": "interface{}", + "Mutation": "interface{}", + } if err = json.Unmarshal(b, &goTypes); err != nil { fmt.Fprintln(os.Stderr, "unable to parse typemap: "+err.Error()) os.Exit(1) @@ -66,6 +77,22 @@ func main() { } e.extract(schema) + // Poke a few magic methods into query + q := e.GetObject("Query") + q.Fields = append(q.Fields, Field{ + Type: e.getType("__Schema").Ptr(), + GraphQLName: "__schema", + NoErr: true, + MethodName: "ec.IntrospectSchema", + }) + q.Fields = append(q.Fields, Field{ + Type: e.getType("__Type").Ptr(), + GraphQLName: "__type", + NoErr: true, + MethodName: "ec.IntrospectType", + Args: []Arg{{Name: "name", Type: Type{Basic: true, Name: "string"}}}, + }) + if len(e.Errors) != 0 { for _, err := range e.Errors { fmt.Println(os.Stderr, "err: "+err) @@ -78,11 +105,16 @@ func main() { os.Exit(1) } - outFile, err := os.Create(*output) - if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) + var out io.Writer = os.Stdout + if *output != "-" { + outFile, err := os.Create(*output) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + defer outFile.Close() + out = outFile } - defer outFile.Close() - write(e, outFile) + + write(e, out) } diff --git a/example/todo/gen/generated.go b/example/todo/gen/generated.go index 608b10f2cfb..b91a7040db9 100644 --- a/example/todo/gen/generated.go +++ b/example/todo/gen/generated.go @@ -1,11 +1,12 @@ package gen import ( + schema "github.com/vektah/graphql-go/schema" + introspection "github.com/vektah/graphql-go/introspection" + todo "github.com/vektah/graphql-go/example/todo" 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 { @@ -25,19 +26,23 @@ type resolvers struct { } func (r *resolvers) Mutation(ec *exec.ExecutionContext, it interface{}, field string, arguments map[string]interface{}, sels []query.Selection) jsonw.Encodable { + if it == nil { + return jsonw.Null + } switch field { case "createTodo": - result, err := r.resolvers.Mutation_createTodo( + res, err := r.resolvers.Mutation_createTodo( arguments["text"].(string), ) if err != nil { ec.Error(err) return jsonw.Null } - return ec.ExecuteSelectionSet(sels, r.Todo, &result) + json := ec.ExecuteSelectionSet(sels, r.Todo, &res) + return json case "updateTodo": - result, err := r.resolvers.Mutation_updateTodo( + res, err := r.resolvers.Mutation_updateTodo( arguments["id"].(int), arguments["done"].(bool), ) @@ -45,39 +50,62 @@ func (r *resolvers) Mutation(ec *exec.ExecutionContext, it interface{}, field st ec.Error(err) return jsonw.Null } - return ec.ExecuteSelectionSet(sels, r.Todo, &result) + json := ec.ExecuteSelectionSet(sels, r.Todo, &res) + return json } panic("unknown field " + field) } func (r *resolvers) Query(ec *exec.ExecutionContext, it interface{}, field string, arguments map[string]interface{}, sels []query.Selection) jsonw.Encodable { + if it == nil { + return jsonw.Null + } switch field { case "todo": - result, err := r.resolvers.Query_todo( + res, err := r.resolvers.Query_todo( arguments["id"].(int), ) if err != nil { ec.Error(err) return jsonw.Null } - return ec.ExecuteSelectionSet(sels, r.Todo, result) + json := ec.ExecuteSelectionSet(sels, r.Todo, res) + return json case "lastTodo": - result, err := r.resolvers.Query_lastTodo() + res, err := r.resolvers.Query_lastTodo() if err != nil { ec.Error(err) return jsonw.Null } - return ec.ExecuteSelectionSet(sels, r.Todo, result) + json := ec.ExecuteSelectionSet(sels, r.Todo, res) + return json case "todos": - result, err := r.resolvers.Query_todos() + res, err := r.resolvers.Query_todos() if err != nil { ec.Error(err) return jsonw.Null } - return ec.ExecuteSelectionSet(sels, r.Todo, &result) + json := jsonw.Array{} + for _, val := range res { + json1 := ec.ExecuteSelectionSet(sels, r.Todo, &val) + json = append(json, json1) + } + return json + + case "__schema": + res := ec.IntrospectSchema() + json := ec.ExecuteSelectionSet(sels, r.__Schema, res) + return json + + case "__type": + res := ec.IntrospectType( + arguments["name"].(string), + ) + json := ec.ExecuteSelectionSet(sels, r.__Type, res) + return json } panic("unknown field " + field) @@ -85,18 +113,288 @@ func (r *resolvers) Query(ec *exec.ExecutionContext, it interface{}, field strin func (r *resolvers) Todo(ec *exec.ExecutionContext, object interface{}, field string, arguments map[string]interface{}, sels []query.Selection) jsonw.Encodable { it := object.(*todo.Todo) + if it == nil { + return jsonw.Null + } switch field { case "id": - return jsonw.ID(it.ID) + res := jsonw.Int(it.ID) + return res case "text": - return jsonw.String(it.Text) + res := jsonw.String(it.Text) + return res case "done": - return jsonw.Boolean(it.Done) + res := jsonw.Bool(it.Done) + return res + + } + panic("unknown field " + field) +} + +func (r *resolvers) __Directive(ec *exec.ExecutionContext, object interface{}, field string, arguments map[string]interface{}, sels []query.Selection) jsonw.Encodable { + it := object.(*introspection.Directive) + if it == nil { + return jsonw.Null + } + switch field { + case "name": + res := it.Name() + json := jsonw.String(res) + return json + + case "description": + res := it.Description() + json := jsonw.String(*res) + return json + + case "locations": + res := it.Locations() + json := jsonw.Array{} + for _, val := range res { + json1 := jsonw.String(val) + json = append(json, json1) + } + return json + + case "args": + res := it.Args() + json := jsonw.Array{} + for _, val := range res { + json1 := ec.ExecuteSelectionSet(sels, r.__InputValue, val) + json = append(json, json1) + } + return json + + } + panic("unknown field " + field) +} + +func (r *resolvers) __EnumValue(ec *exec.ExecutionContext, object interface{}, field string, arguments map[string]interface{}, sels []query.Selection) jsonw.Encodable { + it := object.(*introspection.EnumValue) + if it == nil { + return jsonw.Null + } + switch field { + case "name": + res := it.Name() + json := jsonw.String(res) + return json + + case "description": + res := it.Description() + json := jsonw.String(*res) + return json + + case "isDeprecated": + res := it.IsDeprecated() + json := jsonw.Bool(res) + return json + + case "deprecationReason": + res := it.DeprecationReason() + json := jsonw.String(*res) + return json + + } + panic("unknown field " + field) +} + +func (r *resolvers) __Field(ec *exec.ExecutionContext, object interface{}, field string, arguments map[string]interface{}, sels []query.Selection) jsonw.Encodable { + it := object.(*introspection.Field) + if it == nil { + return jsonw.Null + } + switch field { + case "name": + res := it.Name() + json := jsonw.String(res) + return json + + case "description": + res := it.Description() + json := jsonw.String(*res) + return json + + case "args": + res := it.Args() + json := jsonw.Array{} + for _, val := range res { + json1 := ec.ExecuteSelectionSet(sels, r.__InputValue, val) + json = append(json, json1) + } + return json + + case "type": + res := it.Type() + json := ec.ExecuteSelectionSet(sels, r.__Type, res) + return json + + case "isDeprecated": + res := it.IsDeprecated() + json := jsonw.Bool(res) + return json + + case "deprecationReason": + res := it.DeprecationReason() + json := jsonw.String(*res) + return json + + } + panic("unknown field " + field) +} + +func (r *resolvers) __InputValue(ec *exec.ExecutionContext, object interface{}, field string, arguments map[string]interface{}, sels []query.Selection) jsonw.Encodable { + it := object.(*introspection.InputValue) + if it == nil { + return jsonw.Null + } + switch field { + case "name": + res := it.Name() + json := jsonw.String(res) + return json + + case "description": + res := it.Description() + json := jsonw.String(*res) + return json + + case "type": + res := it.Type() + json := ec.ExecuteSelectionSet(sels, r.__Type, res) + return json + + case "defaultValue": + res := it.DefaultValue() + json := jsonw.String(*res) + return json + + } + panic("unknown field " + field) +} + +func (r *resolvers) __Schema(ec *exec.ExecutionContext, object interface{}, field string, arguments map[string]interface{}, sels []query.Selection) jsonw.Encodable { + it := object.(*introspection.Schema) + if it == nil { + return jsonw.Null + } + switch field { + case "types": + res := it.Types() + json := jsonw.Array{} + for _, val := range res { + json1 := ec.ExecuteSelectionSet(sels, r.__Type, val) + json = append(json, json1) + } + return json + + case "queryType": + res := it.QueryType() + json := ec.ExecuteSelectionSet(sels, r.__Type, res) + return json + + case "mutationType": + res := it.MutationType() + json := ec.ExecuteSelectionSet(sels, r.__Type, res) + return json + + case "subscriptionType": + res := it.SubscriptionType() + json := ec.ExecuteSelectionSet(sels, r.__Type, res) + return json + + case "directives": + res := it.Directives() + json := jsonw.Array{} + for _, val := range res { + json1 := ec.ExecuteSelectionSet(sels, r.__Directive, val) + json = append(json, json1) + } + return json + + } + panic("unknown field " + field) +} + +func (r *resolvers) __Type(ec *exec.ExecutionContext, object interface{}, field string, arguments map[string]interface{}, sels []query.Selection) jsonw.Encodable { + it := object.(*introspection.Type) + if it == nil { + return jsonw.Null + } + switch field { + case "kind": + res := it.Kind() + json := jsonw.String(res) + return json + + case "name": + res := it.Name() + json := jsonw.String(*res) + return json + + case "description": + res := it.Description() + json := jsonw.String(*res) + return json + + case "fields": + res := it.Fields( + arguments["includeDeprecated"].(bool), + ) + json := jsonw.Array{} + for _, val := range *res { + json1 := ec.ExecuteSelectionSet(sels, r.__Field, val) + json = append(json, json1) + } + return json + + case "interfaces": + res := it.Interfaces() + json := jsonw.Array{} + for _, val := range *res { + json1 := ec.ExecuteSelectionSet(sels, r.__Type, val) + json = append(json, json1) + } + return json + + case "possibleTypes": + res := it.PossibleTypes() + json := jsonw.Array{} + for _, val := range *res { + json1 := ec.ExecuteSelectionSet(sels, r.__Type, val) + json = append(json, json1) + } + return json + + case "enumValues": + res := it.EnumValues( + arguments["includeDeprecated"].(bool), + ) + json := jsonw.Array{} + for _, val := range *res { + json1 := ec.ExecuteSelectionSet(sels, r.__EnumValue, val) + json = append(json, json1) + } + return json + + case "inputFields": + res := it.InputFields() + json := jsonw.Array{} + for _, val := range *res { + json1 := ec.ExecuteSelectionSet(sels, r.__InputValue, val) + json = append(json, json1) + } + return json + + case "ofType": + res := it.OfType() + json := ec.ExecuteSelectionSet(sels, r.__Type, res) + return json } panic("unknown field " + field) } -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") +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: Int!\n\ttext: String!\n\tdone: Boolean!\n}\n") diff --git a/example/todo/schema.graphql b/example/todo/schema.graphql index 8cfce613020..3c034251e54 100644 --- a/example/todo/schema.graphql +++ b/example/todo/schema.graphql @@ -16,7 +16,7 @@ type Mutation { } type Todo { - id: ID! + id: Int! text: String! done: Boolean! } diff --git a/exec/exec.go b/exec/exec.go index a077f780440..d3fcf658043 100644 --- a/exec/exec.go +++ b/exec/exec.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/vektah/graphql-go/errors" + "github.com/vektah/graphql-go/introspection" "github.com/vektah/graphql-go/jsonw" "github.com/vektah/graphql-go/query" "github.com/vektah/graphql-go/schema" @@ -14,6 +15,7 @@ import ( type ExecutionContext struct { variables map[string]interface{} errors []*errors.QueryError + schema *schema.Schema } type Root interface { @@ -31,6 +33,18 @@ func (c *ExecutionContext) Error(err error) { c.errors = append(c.errors, errors.Errorf("%s", err.Error())) } +func (c *ExecutionContext) IntrospectSchema() *introspection.Schema { + return introspection.WrapSchema(c.schema) +} + +func (c *ExecutionContext) IntrospectType(name string) *introspection.Type { + t := c.schema.Resolve(name) + if t == nil { + return nil + } + return introspection.WrapType(t) +} + func getOperation(document *query.Document, operationName string) (*query.Operation, error) { if len(document.Operations) == 0 { return nil, fmt.Errorf("no operations in query document") @@ -72,6 +86,7 @@ func ExecuteRequest(root Root, schema *schema.Schema, document string, operation c := ExecutionContext{ variables: variables, + schema: schema, } var rootType ResolverFunc @@ -85,7 +100,7 @@ func ExecuteRequest(root Root, schema *schema.Schema, document string, operation } // TODO: parallelize if query. - data := c.ExecuteSelectionSet(op.Selections, rootType, nil) + data := c.ExecuteSelectionSet(op.Selections, rootType, true) b := &bytes.Buffer{} data.JSON(b) return &jsonw.Response{ @@ -95,6 +110,9 @@ func ExecuteRequest(root Root, schema *schema.Schema, document string, operation } func (c *ExecutionContext) ExecuteSelectionSet(sel []query.Selection, resolver ResolverFunc, objectValue interface{}) jsonw.Encodable { + if objectValue == nil { + return jsonw.Null + } groupedFieldSet := c.collectFields(sel, map[string]interface{}{}) fmt.Println("ESS grouped selections") for _, s := range groupedFieldSet { @@ -103,7 +121,6 @@ func (c *ExecutionContext) ExecuteSelectionSet(sel []query.Selection, resolver R resultMap := jsonw.Map{} for _, collectedField := range groupedFieldSet { - resultMap.Set(collectedField.Alias, resolver(c, objectValue, collectedField.Name, collectedField.Args, collectedField.Selections)) } return resultMap @@ -148,7 +165,7 @@ func (c *ExecutionContext) collectFields(selSet []query.Selection, visited map[s f := findField(&groupedFields, sel, c.variables) f.Selections = append(f.Selections, sel.Selections...) default: - panic("Unsupported!") + panic(fmt.Errorf("unsupported %T", sel)) } } diff --git a/introspection/introspection.go b/introspection/introspection.go index a924ba59c86..109c78c334b 100644 --- a/introspection/introspection.go +++ b/introspection/introspection.go @@ -100,7 +100,7 @@ func (r *Type) Description() *string { return nil } -func (r *Type) Fields(args *struct{ IncludeDeprecated bool }) *[]*Field { +func (r *Type) Fields(includeDeprecated bool) *[]*Field { var fields schema.FieldList switch t := r.typ.(type) { case *schema.Object: @@ -113,7 +113,7 @@ func (r *Type) Fields(args *struct{ IncludeDeprecated bool }) *[]*Field { var l []*Field for _, f := range fields { - if d := f.Directives.Get("deprecated"); d == nil || args.IncludeDeprecated { + if d := f.Directives.Get("deprecated"); d == nil || includeDeprecated { l = append(l, &Field{f}) } } @@ -151,7 +151,7 @@ func (r *Type) PossibleTypes() *[]*Type { return &l } -func (r *Type) EnumValues(args *struct{ IncludeDeprecated bool }) *[]*EnumValue { +func (r *Type) EnumValues(includeDeprecated bool) *[]*EnumValue { t, ok := r.typ.(*schema.Enum) if !ok { return nil @@ -159,7 +159,7 @@ func (r *Type) EnumValues(args *struct{ IncludeDeprecated bool }) *[]*EnumValue var l []*EnumValue for _, v := range t.Values { - if d := v.Directives.Get("deprecated"); d == nil || args.IncludeDeprecated { + if d := v.Directives.Get("deprecated"); d == nil || includeDeprecated { l = append(l, &EnumValue{v}) } } diff --git a/jsonw/output.go b/jsonw/output.go index 8d768fad052..1d3c33d1720 100644 --- a/jsonw/output.go +++ b/jsonw/output.go @@ -77,7 +77,7 @@ func String(v string) Encodable { return literal{[]byte(strconv.Quote(v))} } -func Boolean(v bool) Encodable { +func Bool(v bool) Encodable { if v { return True } else {