├── .gitignore ├── cmd ├── data │ ├── model_schema.go │ ├── model.go │ └── model_graph.go ├── main.go └── schema.json ├── process.go ├── README.md ├── common.go ├── object.go ├── mutation.go └── definition.go /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .idea/ 3 | 4 | *.iml 5 | -------------------------------------------------------------------------------- /cmd/data/model_schema.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | gg "github.com/xinhuang327/gographer" 5 | ) 6 | 7 | func GetModelSchemaInfo() *gg.SchemaInfo { 8 | sch := gg.NewSchemaInfo() 9 | 10 | sch.RegType(Todo{}). 11 | SetIDResolver(func(id string) interface{} { 12 | return GetTodo(id) 13 | }). 14 | IDField("id", nil).SimpleFields() 15 | 16 | sch.RegType(User{}). 17 | SetIDResolver(func(id string) interface{} { 18 | return GetUser(id) 19 | }). 20 | IDField("id", nil).ResolvedFields() 21 | 22 | sch.RegType(&Root{}).SetRoot().ResolvedFields() 23 | 24 | sch.RegType(&Mutation{}).SetMutation().MutationFields() 25 | 26 | return sch 27 | } 28 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/graphql/testutil" 8 | "github.com/xinhuang327/gographer/cmd/data" 9 | "io/ioutil" 10 | "log" 11 | "os" 12 | "reflect" 13 | ) 14 | 15 | func main() { 16 | inspectFunc(func(a int, b string) string { 17 | return "hello" 18 | }) 19 | } 20 | 21 | func inspectFunc(fun interface{}) { 22 | typ := reflect.TypeOf(fun) 23 | fmt.Println(typ) 24 | } 25 | 26 | func updateSchema(){ 27 | schemaInfo := data.GetModelSchemaInfo() 28 | schema, err := schemaInfo.GetSchema() 29 | if err != nil { 30 | fmt.Println("Error", err) 31 | } else { 32 | result := graphql.Do(graphql.Params{ 33 | Schema: schema, 34 | RequestString: testutil.IntrospectionQuery, 35 | }) 36 | if result.HasErrors() { 37 | log.Fatalf("ERROR introspecting schema: %v", result.Errors) 38 | return 39 | } else { 40 | b, err := json.MarshalIndent(result, "", " ") 41 | if err != nil { 42 | log.Fatalf("ERROR: %v", err) 43 | } 44 | err = ioutil.WriteFile("schema.json", b, os.ModePerm) 45 | if err != nil { 46 | log.Fatalf("ERROR: %v", err) 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /process.go: -------------------------------------------------------------------------------- 1 | package gographer 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "github.com/graphql-go/relay" 6 | "golang.org/x/net/context" 7 | "reflect" 8 | ) 9 | 10 | func (sch SchemaInfo) GetSchema() (graphql.Schema, error) { 11 | 12 | qlTypes := make(map[string]*graphql.Object) 13 | qlConns := make(map[string]*relay.GraphQLConnectionDefinitions) 14 | var rootType *graphql.Object 15 | var mutationType *graphql.Object 16 | 17 | var nodeDefinitions *relay.NodeDefinitions 18 | var schema graphql.Schema 19 | 20 | // register all the types 21 | nodeDefinitions = relay.NewNodeDefinitions(relay.NodeDefinitionsConfig{ 22 | 23 | IDFetcher: func(id string, info graphql.ResolveInfo, ctx context.Context) (interface{}, error) { 24 | resolvedID := relay.FromGlobalID(id) 25 | if typ, ok := sch.typesByName[resolvedID.Type]; ok { 26 | return typ.idResolver(resolvedID.ID), nil 27 | } 28 | return nil, nil 29 | }, 30 | 31 | TypeResolve: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { 32 | type_ := reflect.ValueOf(value).Elem().Type() 33 | if qlType, ok := qlTypes[type_.Name()]; ok { 34 | return qlType 35 | } else { 36 | Warning("[GetSchema Error]", "cannot resolve type", value) 37 | return nil 38 | } 39 | }, 40 | }) 41 | 42 | // process all the object types, object types must be registered in order of dependency at the time 43 | for _, typ := range sch.types { 44 | if !typ.isMutationType { 45 | 46 | qlType := sch.processObjectType(typ, qlTypes, qlConns, nodeDefinitions) 47 | if typ.isRootType { 48 | rootType = qlType 49 | sch.rootInstance = typ.instance 50 | } 51 | } 52 | } 53 | 54 | // process mutation type, should have only one mutation type 55 | for _, typ := range sch.types { 56 | if typ.isMutationType { 57 | 58 | mutType := sch.processMutationType(typ, qlTypes, qlConns, nodeDefinitions) 59 | mutationType = mutType 60 | sch.mutationInstance = typ.instance 61 | } 62 | } 63 | 64 | schema, err := graphql.NewSchema(graphql.SchemaConfig{ 65 | Query: rootType, 66 | Mutation: mutationType, 67 | }) 68 | return schema, err 69 | } 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gographer 2 | **Still working in progress, have a look at the source if you are interested, I don't think there is a library alike in Go right now, PR is much welcomed.** 3 | 4 | Write GraphQL schema manually really tedious, it also mix model and logic code together, there is obviously much duplicated information. 5 | 6 | With Gographer, 7 | you can generate GraphQL schema on the fly with much clear code. This work is based on Graphql-go project's GraphQL implementation. 8 | 9 | The idea is somewhat like python's graphene, but more flexible, it won't force you to rewrite any code, you can adapt your existing model and logic code to GraphQL schema pretty easily. 10 | The implementation uses Golang's reflection package heavily, it sure will not be performant as hand written static code, but can reduce more than 60% lines of code, and they are much clear than the raw style. 11 | 12 | Currently I'm trying to add enough features to utilize it in my project, before majority of the design been finished, and API been stablized, there won't be much test coverage. 13 | 14 | Automatically bind/write Golang model to GraphQL: 15 | 16 | * Struct field 17 | * Struct methods to computed field/resolved field 18 | * Mutation type and function 19 | * Argument and return value 20 | * Embedded struct field 21 | * Extension field addon for existing code 22 | 23 | 24 | With this tool, you can define GraphQL schema with something like below, which is much compact. You can see the full example in cmd/data folder, in which are schema definition to match original GraphQL TodoMVC example. 25 | ```go 26 | func GetModelSchemaInfo() *gg.SchemaInfo { 27 | sch := gg.NewSchemaInfo() 28 | 29 | sch.RegType(Todo{}). 30 | SetIDResolver(func(id string) interface{} { 31 | return GetTodo(id) 32 | }). 33 | IDField("id", nil).SimpleFields() 34 | 35 | sch.RegType(User{}). 36 | SetIDResolver(func(id string) interface{} { 37 | return GetUser(id) 38 | }). 39 | IDField("id", nil).ResolvedFields() 40 | 41 | sch.RegType(&Root{}).SetRoot().ResolvedFields() 42 | 43 | sch.RegType(&Mutation{}).SetMutation().MutationFields() 44 | 45 | return sch 46 | } 47 | 48 | 49 | type AddTodoInput struct { 50 | Text string `nonNull:"true"` 51 | } 52 | 53 | 54 | type AddTodoOutput struct { 55 | TodoEdge relay.EdgeType //`elemType:"Todo"` 56 | Viewer *User 57 | } 58 | 59 | func (m *Mutation) AddTodo(in AddTodoInput) *AddTodoOutput { 60 | todoId := AddTodo(in.Text, false) 61 | todo := GetTodo(todoId) 62 | // TODO: manage pagination 63 | return &AddTodoOutput{ 64 | TodoEdge: relay.EdgeType{ 65 | Node: todo, 66 | Cursor: relay.CursorForObjectInConnection(TodosToSliceInterface(GetTodos("any")), todo), 67 | }, 68 | Viewer: GetViewer(), 69 | } 70 | } 71 | 72 | type ChangeTodoStatusInput struct { 73 | Id string `nonNull:"true"` 74 | Complete bool `nonNull:"true"` 75 | } 76 | 77 | type ChangeTodoStatusOutput struct { 78 | Todo *Todo 79 | Viewer *User 80 | } 81 | 82 | func (m *Mutation) ChangeTodoStatus(in ChangeTodoStatusInput) *ChangeTodoStatusOutput { 83 | resolvedId := relay.FromGlobalID(in.Id) // TODO: ID conversion could be handled outside the function 84 | todoID := resolvedId.ID 85 | ChangeTodoStatus(todoID, in.Complete) 86 | return &ChangeTodoStatusOutput{GetTodo(todoID), GetViewer()} 87 | } 88 | ``` 89 | 90 | 91 | -------------------------------------------------------------------------------- /cmd/data/model.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import "fmt" 4 | 5 | // Mock authenticated ID 6 | const ViewerId = "me" 7 | 8 | // Model structs 9 | type Todo struct { 10 | ID string `json:"id"` 11 | Text string `json:"text"` 12 | Complete bool `json:"complete"` 13 | } 14 | 15 | type User struct { 16 | ID string `json:"id"` 17 | } 18 | 19 | // Mock data 20 | var viewer = &User{ViewerId} 21 | var usersById = map[string]*User{ 22 | ViewerId: viewer, 23 | } 24 | var todosById = map[string]*Todo{} 25 | var todoIdsByUser = map[string][]string{ 26 | ViewerId: []string{}, 27 | } 28 | var nextTodoId = 0 29 | 30 | // Data methods 31 | 32 | func AddTodo(text string, complete bool) string { 33 | todo := &Todo{ 34 | ID: fmt.Sprintf("%v", nextTodoId), 35 | Text: text, 36 | Complete: complete, 37 | } 38 | nextTodoId = nextTodoId + 1 39 | 40 | todosById[todo.ID] = todo 41 | todoIdsByUser[ViewerId] = append(todoIdsByUser[ViewerId], todo.ID) 42 | 43 | return todo.ID 44 | } 45 | 46 | func GetTodo(id string) *Todo { 47 | if todo, ok := todosById[id]; ok { 48 | return todo 49 | } 50 | return nil 51 | } 52 | 53 | func GetTodos(status string) []*Todo { 54 | todos := []*Todo{} 55 | for _, todoId := range todoIdsByUser[ViewerId] { 56 | if todo := GetTodo(todoId); todo != nil { 57 | 58 | switch status { 59 | case "completed": 60 | if todo.Complete { 61 | todos = append(todos, todo) 62 | } 63 | case "incomplete": 64 | if !todo.Complete { 65 | todos = append(todos, todo) 66 | } 67 | case "any": 68 | fallthrough 69 | default: 70 | todos = append(todos, todo) 71 | } 72 | } 73 | } 74 | return todos 75 | } 76 | 77 | func GetUser(id string) *User { 78 | if user, ok := usersById[id]; ok { 79 | return user 80 | } 81 | return nil 82 | } 83 | 84 | func GetViewer() *User { 85 | return GetUser(ViewerId) 86 | } 87 | 88 | func ChangeTodoStatus(id string, complete bool) { 89 | todo := GetTodo(id) 90 | if todo == nil { 91 | return 92 | } 93 | todo.Complete = complete 94 | } 95 | 96 | func MarkAllTodos(complete bool) []string { 97 | changedTodoIds := []string{} 98 | for _, todo := range GetTodos("any") { 99 | if todo.Complete != complete { 100 | todo.Complete = complete 101 | changedTodoIds = append(changedTodoIds, todo.ID) 102 | } 103 | } 104 | return changedTodoIds 105 | } 106 | 107 | func RemoveTodo(id string) { 108 | 109 | updatedTodoIdsForUser := []string{} 110 | for _, todoId := range todoIdsByUser[ViewerId] { 111 | if todoId != id { 112 | updatedTodoIdsForUser = append(updatedTodoIdsForUser, todoId) 113 | } 114 | } 115 | todoIdsByUser[ViewerId] = updatedTodoIdsForUser 116 | delete(todosById, id) 117 | 118 | } 119 | 120 | func RemoveCompletedTodos() []string { 121 | todosIdRemoved := []string{} 122 | for _, completedTodo := range GetTodos("completed") { 123 | RemoveTodo(completedTodo.ID) 124 | todosIdRemoved = append(todosIdRemoved, completedTodo.ID) 125 | } 126 | return todosIdRemoved 127 | } 128 | 129 | func RenameTodo(id string, text string) { 130 | todo := GetTodo(id) 131 | if todo != nil { 132 | todo.Text = text 133 | } 134 | } 135 | 136 | func TodosToSliceInterface(todos []*Todo) []interface{} { 137 | todosIFace := []interface{}{} 138 | for _, todo := range todos { 139 | todosIFace = append(todosIFace, todo) 140 | } 141 | return todosIFace 142 | } 143 | -------------------------------------------------------------------------------- /cmd/data/model_graph.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "github.com/graphql-go/relay" 5 | ) 6 | 7 | type Root struct{} 8 | 9 | type Mutation struct{} 10 | 11 | type AddTodoInput struct { 12 | Text string `nonNull:"true"` 13 | } 14 | 15 | type AddTodoOutput struct { 16 | TodoEdge relay.EdgeType //`elemType:"Todo"` 17 | Viewer *User 18 | } 19 | 20 | func (m *Mutation) AddTodo(in AddTodoInput) *AddTodoOutput { 21 | todoId := AddTodo(in.Text, false) 22 | todo := GetTodo(todoId) 23 | // TODO: manage pagination 24 | return &AddTodoOutput{ 25 | TodoEdge: relay.EdgeType{ 26 | Node: todo, 27 | Cursor: relay.CursorForObjectInConnection(TodosToSliceInterface(GetTodos("any")), todo), 28 | }, 29 | Viewer: GetViewer(), 30 | } 31 | } 32 | 33 | type ChangeTodoStatusInput struct { 34 | Id string `nonNull:"true"` 35 | Complete bool `nonNull:"true"` 36 | } 37 | 38 | type ChangeTodoStatusOutput struct { 39 | Todo *Todo 40 | Viewer *User 41 | } 42 | 43 | func (m *Mutation) ChangeTodoStatus(in ChangeTodoStatusInput) *ChangeTodoStatusOutput { 44 | resolvedId := relay.FromGlobalID(in.Id) // TODO: ID conversion could be handled outside the function 45 | todoID := resolvedId.ID 46 | ChangeTodoStatus(todoID, in.Complete) 47 | return &ChangeTodoStatusOutput{GetTodo(todoID), GetViewer()} 48 | } 49 | 50 | type MarkAllTodosInput struct { 51 | Complete bool `nonNull:"true"` 52 | } 53 | 54 | type MarkAllTodosOutput struct { 55 | ChangedTodosConnection []*Todo `json:"changedTodos"` 56 | Viewer *User 57 | } 58 | 59 | func (m *Mutation) MarkAllTodos(in MarkAllTodosInput) *MarkAllTodosOutput { 60 | todoIds := MarkAllTodos(in.Complete) 61 | todos := []*Todo{} 62 | for _, todoId := range todoIds { 63 | todo := GetTodo(todoId) 64 | if todo != nil { 65 | todos = append(todos, todo) 66 | } 67 | } 68 | return &MarkAllTodosOutput{todos, GetViewer()} 69 | } 70 | 71 | type RemoveCompletedTodosOutput struct { 72 | DeletedTodoIds []string 73 | Viewer *User 74 | } 75 | 76 | func (m *Mutation) RemoveCompletedTodos() *RemoveCompletedTodosOutput { 77 | return &RemoveCompletedTodosOutput{RemoveCompletedTodos(), GetViewer()} 78 | } 79 | 80 | type RemoveTodoInput struct { 81 | Id string `nonNull:"true"` 82 | } 83 | 84 | type RemoveTodoOutput struct { 85 | DeletedTodoId string 86 | Viewer *User 87 | } 88 | 89 | func (m *Mutation) RemoveTodo(in RemoveTodoInput) *RemoveTodoOutput { 90 | resolvedId := relay.FromGlobalID(in.Id) 91 | RemoveTodo(resolvedId.ID) 92 | return &RemoveTodoOutput{relay.ToGlobalID(resolvedId.Type, resolvedId.ID), GetViewer()} 93 | } 94 | 95 | type RenameTodoInput struct { 96 | Id string `nonNull:"true"` 97 | Text string `nonNull:"true"` 98 | } 99 | 100 | func (m *Mutation) RenameTodo(in RenameTodoInput) *ChangeTodoStatusOutput { 101 | resolvedId := relay.FromGlobalID(in.Id) 102 | todoID := resolvedId.ID 103 | RenameTodo(todoID, in.Text) 104 | return &ChangeTodoStatusOutput{GetTodo(todoID), GetViewer()} 105 | } 106 | 107 | func (r *Root) GetViewer() *User { 108 | return GetViewer() 109 | } 110 | 111 | // Struct arg's field name must be exported (Upper case first letter, will use lower case first letter in GraphQL) 112 | type GetTodosInput struct { 113 | Status string `def:"any"` 114 | } 115 | 116 | func (u *User) GetTodos(p GetTodosInput) []*Todo { 117 | return GetTodos(p.Status) 118 | } 119 | 120 | func (u *User) GetTotalCount() int { 121 | return len(GetTodos("any")) 122 | } 123 | 124 | func (u *User) GetCompletedCount() int { 125 | return len(GetTodos("completed")) 126 | } 127 | -------------------------------------------------------------------------------- /common.go: -------------------------------------------------------------------------------- 1 | package gographer 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "github.com/graphql-go/relay" 6 | "reflect" 7 | "strings" 8 | "unicode" 9 | "unicode/utf8" 10 | "fmt" 11 | "strconv" 12 | "runtime" 13 | "runtime/debug" 14 | ) 15 | 16 | func getComplexQLType( 17 | returnType reflect.Type, 18 | fieldName string, 19 | qlTypes map[string]*graphql.Object, 20 | qlConns map[string]*relay.GraphQLConnectionDefinitions) (graphql.Output, QLTypeKind) { 21 | 22 | var returnQLType graphql.Output 23 | var qlTypeKind QLTypeKind 24 | 25 | isList := returnType.Kind() == reflect.Slice 26 | isPtr := returnType.Kind() == reflect.Ptr 27 | 28 | elemType := returnType 29 | 30 | if isList || isPtr { 31 | elemType = returnType.Elem() 32 | // in case of slice of struct pointers 33 | if elemType.Kind() == reflect.Ptr { 34 | elemType = elemType.Elem() 35 | } 36 | } 37 | 38 | var elemQLType graphql.Output 39 | 40 | var elemTypeName = elemType.Name() 41 | isPrimitive := true 42 | if elemQLType = ToQLType(elemType); elemQLType == nil { 43 | isPrimitive = false 44 | if qlType, ok := qlTypes[elemTypeName]; ok { 45 | elemQLType = qlType 46 | } 47 | } 48 | 49 | inferredElemTypeName := inferTypeNameFromField(fieldName) 50 | 51 | // 52 | //if elemType == reflect.TypeOf(relay.EdgeType{}) { 53 | // if inferredElemTypeName != "" { 54 | // elemQLType := qlTypes[inferredElemTypeName] 55 | // conn := getOrCreateConnection(inferredElemTypeName, elemQLType, qlConns) 56 | // returnQLType = conn.EdgeType 57 | // qlTypeKind = QLTypeKind_Edge 58 | // } else { 59 | // Warning("Cannot infer type name for connection", fieldName, elemType) 60 | // } 61 | //} 62 | 63 | // TODO: Better handling Connection and Edge types... 64 | if elemQLType != nil { 65 | if !isList { 66 | returnQLType = elemQLType 67 | if isPrimitive { 68 | qlTypeKind = QLTypeKind_Simple 69 | } else { 70 | qlTypeKind = QLTypeKind_Struct 71 | } 72 | } else { 73 | isConnection := false 74 | if isConnection { 75 | // connection 76 | conn := getOrCreateConnection(elemTypeName, elemQLType, qlConns) 77 | returnQLType = conn.ConnectionType 78 | qlTypeKind = QLTypeKind_Connection 79 | } else { 80 | // list 81 | returnQLType = graphql.NewList(elemQLType) 82 | qlTypeKind = QLTypeKind_SimpleList 83 | } 84 | } 85 | } else if elemType == reflect.TypeOf(relay.EdgeType{}) { 86 | if inferredElemTypeName != "" { 87 | elemQLType := qlTypes[inferredElemTypeName] 88 | conn := getOrCreateConnection(inferredElemTypeName, elemQLType, qlConns) 89 | returnQLType = conn.EdgeType 90 | qlTypeKind = QLTypeKind_Edge 91 | } else { 92 | Warning("Cannot infer type name for connection", fieldName, elemType) 93 | } 94 | } else { 95 | Warning("Cannot resolve QL type for return type", returnType, "elemType", elemType) 96 | fmt.Println(qlTypes) 97 | } 98 | 99 | return returnQLType, qlTypeKind 100 | } 101 | 102 | func getOrCreateConnection( 103 | elemTypeName string, 104 | elemQLType graphql.Output, 105 | qlConns map[string]*relay.GraphQLConnectionDefinitions) *relay.GraphQLConnectionDefinitions { 106 | var conn *relay.GraphQLConnectionDefinitions 107 | var found bool 108 | 109 | if conn, found = qlConns[elemTypeName]; !found { 110 | conn = relay.ConnectionDefinitions(relay.ConnectionConfig{ 111 | Name: elemTypeName, 112 | NodeType: elemQLType.(*graphql.Object), 113 | }) 114 | qlConns[elemTypeName] = conn 115 | } 116 | 117 | return conn 118 | } 119 | 120 | func toEmptyInterfaceSlice(slice interface{}) []interface{} { 121 | s := reflect.ValueOf(slice) 122 | if s.Kind() != reflect.Slice { 123 | panic("InterfaceSlice() given a non-slice type") 124 | } 125 | 126 | ret := make([]interface{}, s.Len()) 127 | for i := 0; i < s.Len(); i++ { 128 | ret[i] = s.Index(i).Interface() 129 | } 130 | return ret 131 | } 132 | 133 | func lowerFirst(s string) string { 134 | if s == "" { 135 | return "" 136 | } 137 | r, n := utf8.DecodeRuneInString(s) 138 | return string(unicode.ToLower(r)) + s[n:] 139 | } 140 | 141 | func upperFirst(s string) string { 142 | if s == "" { 143 | return "" 144 | } 145 | r, n := utf8.DecodeRuneInString(s) 146 | return string(unicode.ToUpper(r)) + s[n:] 147 | } 148 | 149 | func inferTypeNameFromField(fieldName string) string { 150 | fieldName = upperFirst(fieldName) 151 | if strings.HasSuffix(fieldName, "Edge") { 152 | return strings.TrimSuffix(fieldName, "Edge") 153 | } 154 | if strings.HasSuffix(fieldName, "Connection") { 155 | return strings.TrimSuffix(fieldName, "Connection") 156 | } 157 | return "" 158 | } 159 | 160 | func ToQLType(typ reflect.Type) graphql.Output { 161 | switch typ.Kind() { 162 | case reflect.Slice: // []string 163 | elemType := typ.Elem() 164 | if elemQLType := ToQLType(elemType); elemQLType != nil { 165 | return graphql.NewList(elemQLType) 166 | } else { 167 | return nil 168 | } 169 | case reflect.Float32: 170 | fallthrough 171 | case reflect.Float64: 172 | return graphql.Float 173 | case reflect.String: 174 | return graphql.String 175 | case reflect.Bool: 176 | return graphql.Boolean 177 | case reflect.Int: 178 | fallthrough 179 | case reflect.Int8: 180 | fallthrough 181 | case reflect.Int16: 182 | fallthrough 183 | case reflect.Int32: 184 | fallthrough 185 | case reflect.Int64: 186 | fallthrough 187 | case reflect.Uint: 188 | fallthrough 189 | case reflect.Uint8: 190 | fallthrough 191 | case reflect.Uint16: 192 | fallthrough 193 | case reflect.Uint32: 194 | fallthrough 195 | case reflect.Uint64: 196 | return graphql.Int 197 | default: 198 | return nil 199 | } 200 | } 201 | 202 | func ParseString(str string, typ reflect.Type) interface{} { 203 | switch typ.Kind() { 204 | case reflect.Float32: 205 | fallthrough 206 | case reflect.Float64: 207 | if v, err := strconv.ParseFloat(str, 32); err == nil { 208 | return v 209 | } 210 | case reflect.String: 211 | return str 212 | case reflect.Bool: 213 | if v, err := strconv.ParseBool(str); err == nil { 214 | return v 215 | } 216 | case reflect.Int: 217 | fallthrough 218 | case reflect.Int8: 219 | fallthrough 220 | case reflect.Int16: 221 | fallthrough 222 | case reflect.Int32: 223 | fallthrough 224 | case reflect.Int64: 225 | fallthrough 226 | case reflect.Uint: 227 | fallthrough 228 | case reflect.Uint8: 229 | fallthrough 230 | case reflect.Uint16: 231 | fallthrough 232 | case reflect.Uint32: 233 | fallthrough 234 | case reflect.Uint64: 235 | if v, err := strconv.ParseInt(str, 0, 0); err == nil { 236 | return v 237 | } 238 | default: 239 | return nil 240 | } 241 | return nil 242 | } 243 | 244 | func Warning(a ...interface{}) { 245 | _, file, line, _ := runtime.Caller(1) 246 | idx := strings.LastIndex(file, "/") 247 | prefix := fmt.Sprint("[Gographer warning @", file[idx + 1:], ":", line, "]") 248 | a = append([]interface{}{prefix}, a...) 249 | fmt.Println(a...) 250 | fmt.Printf("%s", debug.Stack()) 251 | } 252 | -------------------------------------------------------------------------------- /object.go: -------------------------------------------------------------------------------- 1 | package gographer 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/relay" 8 | "reflect" 9 | "runtime/debug" 10 | ) 11 | 12 | func (sch *SchemaInfo) processObjectType( 13 | 14 | typ *TypeInfo, 15 | qlTypes map[string]*graphql.Object, 16 | qlConns map[string]*relay.GraphQLConnectionDefinitions, 17 | nodeDefinitions *relay.NodeDefinitions) *graphql.Object { 18 | 19 | qlTypeConf := graphql.ObjectConfig{} 20 | qlTypeConf.Name = typ.Name 21 | 22 | var fieldsGetter = graphql.FieldsThunk(func() graphql.Fields { 23 | 24 | fields := make(graphql.Fields) 25 | 26 | // simple fields 27 | for fieldName, field := range typ.fields { 28 | fields[fieldName] = field 29 | } 30 | 31 | // node field for root 32 | if typ.isRootType { 33 | fields["node"] = nodeDefinitions.NodeField 34 | } 35 | 36 | // resolved fields 37 | for _, rf := range typ.resolvedFields { 38 | 39 | refType := typ.Type 40 | refPtrType := reflect.PtrTo(refType) 41 | 42 | var funcType reflect.Type 43 | 44 | if rf.ExtensionFunc != nil { 45 | funcType = reflect.TypeOf(rf.ExtensionFunc) 46 | } else { 47 | var method reflect.Method 48 | foundMethod := false 49 | if method, foundMethod = refPtrType.MethodByName(rf.MethodName); !foundMethod { 50 | method, foundMethod = refType.MethodByName(rf.MethodName) 51 | } 52 | if foundMethod { 53 | funcType = method.Func.Type() 54 | } else { 55 | Warning("Cannot find method", rf.MethodName, "for type", refType.Name()) 56 | continue 57 | } 58 | } 59 | 60 | returnType := funcType.Out(0) // only use first return value, TODO: handle error 61 | var fieldArgs graphql.FieldConfigArgument 62 | var returnQLType graphql.Output 63 | var qlTypeKind QLTypeKind = QLTypeKind_Simple 64 | 65 | if rf.ManualType == nil { 66 | returnQLType, qlTypeKind = getComplexQLType(returnType, rf.Name, qlTypes, qlConns) 67 | } else { 68 | // extension with manual return type, probably a embedded struct's field 69 | returnQLType = rf.ManualType 70 | } 71 | 72 | resultIsConnection := qlTypeKind == QLTypeKind_Connection 73 | 74 | funcArgs := make(graphql.FieldConfigArgument) 75 | 76 | if rf.AutoArgs { 77 | // use struct args 78 | if funcType.NumIn() == 2 { 79 | argStructType := funcType.In(1) 80 | if argStructType.Kind() == reflect.Struct { 81 | for i := 0; i < argStructType.NumField(); i++ { 82 | 83 | argField := argStructType.Field(i) 84 | argFieldName := lowerFirst(argField.Name) 85 | argQLType := ToQLType(argField.Type) 86 | 87 | var defaultValue interface{} = nil 88 | if defTag := argField.Tag.Get(TAG_DefaultValue); defTag != "" { 89 | defaultValue = ParseString(defTag, argField.Type) 90 | } 91 | if nonNullTag := argField.Tag.Get(TAG_NonNull); nonNullTag == "true" { 92 | argQLType = graphql.NewNonNull(argQLType) 93 | } 94 | funcArgs[argFieldName] = &graphql.ArgumentConfig{ 95 | Type: argQLType, 96 | DefaultValue: defaultValue, 97 | } 98 | } 99 | } else { 100 | Warning("AutoArgs needs a struct value as argument", rf.MethodName) 101 | } 102 | } 103 | } else { 104 | // use manual argument info 105 | for i := 1; i < funcType.NumIn(); i++ { 106 | argQLType := ToQLType(funcType.In(i)) 107 | arg := rf.Args[i - 1] 108 | if arg.NonNull { 109 | argQLType = graphql.NewNonNull(argQLType) 110 | } 111 | funcArgs[arg.Name] = &graphql.ArgumentConfig{ 112 | Type: argQLType, 113 | DefaultValue: arg.DefaultValue, 114 | } 115 | } 116 | } 117 | 118 | if qlTypeKind == QLTypeKind_Connection { 119 | fieldArgs = relay.NewConnectionArgs(funcArgs) 120 | } else { 121 | fieldArgs = funcArgs 122 | } 123 | 124 | // capture infomation for later function call 125 | typCaptured := typ 126 | rfCaptured := rf 127 | 128 | fields[rf.Name] = &graphql.Field{ 129 | Type: returnQLType, 130 | Args: fieldArgs, 131 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 132 | // call the function! 133 | return sch.dynamicCallResolver(rfCaptured, funcType, typCaptured, fieldArgs, resultIsConnection, p) 134 | }, 135 | } 136 | } // end of resolved fields 137 | 138 | return fields 139 | }) 140 | 141 | qlTypeConf.Fields = fieldsGetter 142 | 143 | if !typ.isRootType && !typ.isNonNode { 144 | qlTypeConf.Interfaces = []*graphql.Interface{nodeDefinitions.NodeInterface} 145 | } 146 | qlType := graphql.NewObject(qlTypeConf) 147 | qlTypes[qlTypeConf.Name] = qlType 148 | 149 | return qlType 150 | } 151 | 152 | func (sch *SchemaInfo) dynamicCallResolver( 153 | rf ResolvedFieldInfo, 154 | funcType reflect.Type, 155 | typ *TypeInfo, 156 | fieldArgs graphql.FieldConfigArgument, 157 | resultIsConnection bool, 158 | p graphql.ResolveParams) (interface{}, error) { 159 | 160 | defer func() { 161 | if e := recover(); e != nil { 162 | fmt.Printf("%s: %s", e, debug.Stack()) 163 | } 164 | }() 165 | 166 | fmt.Println("resultIsConnection", resultIsConnection) 167 | fmt.Println("[dynamicCallResolver]", "funcType=", funcType, "rf=", rf, "typ=", typ.Name) 168 | 169 | var objVal reflect.Value 170 | if typ.isRootType { 171 | objVal = reflect.ValueOf(sch.rootInstance) 172 | } else { 173 | objVal = reflect.ValueOf(p.Source) // p.Source would be a pointer to struct 174 | } 175 | if !objVal.IsValid() { 176 | return nil, errors.New("Cannot get source object when calling " + rf.MethodName) 177 | } 178 | 179 | var isExtensionCall = rf.ExtensionFunc != nil 180 | var funcVal reflect.Value 181 | 182 | if !isExtensionCall { 183 | funcVal = objVal.MethodByName(rf.MethodName) 184 | } else { 185 | funcVal = reflect.ValueOf(rf.ExtensionFunc) 186 | } 187 | 188 | if !funcVal.IsValid() { 189 | return nil, errors.New(fmt.Sprint("Cannot get method ", rf.MethodName, " for object ", objVal.Type(), "funcVal", funcVal)) 190 | } 191 | 192 | var inValues []reflect.Value 193 | 194 | if isExtensionCall { 195 | inValues = append(inValues, objVal) // first argument needs to be the source object 196 | } 197 | 198 | if rf.AutoArgs { 199 | // use struct args 200 | if funcType.NumIn() == 2 { 201 | argStructType := funcType.In(1) 202 | argStructVal := reflect.New(argStructType).Elem() 203 | 204 | for i := 0; i < argStructVal.NumField(); i++ { 205 | argStructField := argStructType.Field(i) 206 | argStructFieldVal := argStructVal.Field(i) 207 | lowerFirstFieldName := lowerFirst(argStructField.Name) 208 | 209 | var argObj interface{} = nil 210 | var hasInput bool 211 | if argObj, hasInput = p.Args[lowerFirstFieldName]; !hasInput { 212 | argObj = fieldArgs[lowerFirstFieldName].DefaultValue 213 | } 214 | if argObj != nil { 215 | argStructFieldVal.Set(reflect.ValueOf(argObj)) // bind field value 216 | } 217 | } 218 | inValues = append(inValues, argStructVal) 219 | } 220 | 221 | } else { 222 | // use plain args 223 | for _, arg := range rf.Args { 224 | var argObj interface{} 225 | var hasInput bool 226 | if argObj, hasInput = p.Args[arg.Name]; !hasInput { 227 | argObj = arg.DefaultValue 228 | } 229 | inValues = append(inValues, reflect.ValueOf(argObj)) 230 | } 231 | } 232 | 233 | outValues := funcVal.Call(inValues) 234 | 235 | out := outValues[0].Interface() 236 | 237 | fmt.Println(funcType, rf.MethodName, "Out:", out) 238 | 239 | if resultIsConnection { 240 | resultSlice := toEmptyInterfaceSlice(out) 241 | // TODO: manage pagination 242 | return relay.ConnectionFromArray(resultSlice, relay.NewConnectionArguments(p.Args)), nil 243 | } else { 244 | return out, nil 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /mutation.go: -------------------------------------------------------------------------------- 1 | package gographer 2 | 3 | import ( 4 | "fmt" 5 | "github.com/graphql-go/graphql" 6 | "github.com/graphql-go/relay" 7 | "golang.org/x/net/context" 8 | "reflect" 9 | "strings" 10 | ) 11 | 12 | func (sch *SchemaInfo) processMutationType( 13 | typ *TypeInfo, 14 | qlTypes map[string]*graphql.Object, 15 | qlConns map[string]*relay.GraphQLConnectionDefinitions, 16 | nodeDefinitions *relay.NodeDefinitions) *graphql.Object { 17 | 18 | if len(typ.mutationFields) == 0 { 19 | return nil 20 | } 21 | 22 | refType := typ.Type 23 | refPtrType := reflect.PtrTo(refType) 24 | 25 | var mutationFields = make(graphql.Fields) 26 | 27 | for _, mf := range typ.mutationFields { 28 | 29 | // try find method for pointer type first 30 | var method reflect.Method 31 | foundMethod := false 32 | if method, foundMethod = refPtrType.MethodByName(mf.MethodName); !foundMethod { 33 | method, foundMethod = refType.MethodByName(mf.MethodName) 34 | } 35 | 36 | if foundMethod { 37 | 38 | funcType := method.Func.Type() 39 | mutConf := relay.MutationConfig{} 40 | mutConf.Name = mf.MethodName 41 | 42 | var inputFields = make(graphql.InputObjectConfigFieldMap) 43 | 44 | if mf.AutoArgs { 45 | // use struct args 46 | if funcType.NumIn() == 2 { 47 | argStructType := funcType.In(1) 48 | if argStructType.Kind() == reflect.Struct { 49 | for i := 0; i < argStructType.NumField(); i++ { 50 | 51 | argField := argStructType.Field(i) 52 | argFieldName := lowerFirst(argField.Name) 53 | argQLType := ToQLType(argField.Type) 54 | 55 | var defaultValue interface{} = nil 56 | if defTag := argField.Tag.Get(TAG_DefaultValue); defTag != "" { 57 | defaultValue = ParseString(defTag, argField.Type) 58 | } 59 | if nonNullTag := argField.Tag.Get(TAG_NonNull); nonNullTag == "true" { 60 | argQLType = graphql.NewNonNull(argQLType) 61 | } 62 | inputFields[argFieldName] = &graphql.InputObjectFieldConfig{ 63 | Type: argQLType, 64 | DefaultValue: defaultValue, 65 | } 66 | } 67 | } else { 68 | Warning("AutoArgs needs a struct value as argument", mf.MethodName, argStructType, argStructType.Kind()) 69 | } 70 | } 71 | 72 | } else { 73 | for i := 1; i < funcType.NumIn(); i++ { 74 | argQLType := ToQLType(funcType.In(i)) // TODO: handle GraphQL ID type? 75 | arg := mf.Args[i-1] 76 | if arg.NonNull { 77 | argQLType = graphql.NewNonNull(argQLType) 78 | } 79 | inputFields[arg.Name] = &graphql.InputObjectFieldConfig{ 80 | Type: argQLType, 81 | DefaultValue: arg.DefaultValue, 82 | } 83 | } 84 | } 85 | mutConf.InputFields = inputFields 86 | 87 | var outputFields = make(graphql.Fields) 88 | 89 | //var outTypes []reflect.Type 90 | var outQLTypes []graphql.Output 91 | var outputInfos []OutputInfo 92 | 93 | if mf.AutoOutputs { 94 | 95 | // use struct args to infer output field types 96 | outStructType := funcType.Out(0) 97 | if outStructType.Kind() == reflect.Ptr { 98 | outStructType = outStructType.Elem() // return type may be pointer type to struct 99 | } 100 | if outStructType.Kind() == reflect.Struct { 101 | 102 | for i := 0; i < outStructType.NumField(); i++ { 103 | 104 | outField := outStructType.Field(i) 105 | outFieldName := lowerFirst(outField.Name) 106 | outQLType, qlTypeKind := getComplexQLType(outField.Type, outField.Name, qlTypes, qlConns) // use full name to infer type 107 | 108 | var qlFieldName string 109 | if jsonTag := outField.Tag.Get("json"); jsonTag != "" { 110 | qlFieldName = jsonTag 111 | } else { 112 | qlFieldName = outFieldName 113 | } 114 | 115 | outInfo := OutputInfo{ 116 | Name: qlFieldName, 117 | } 118 | 119 | if qlTypeKind == QLTypeKind_Edge { 120 | if strings.HasSuffix(outField.Name, "Edge") { 121 | outInfo.ElemTypeName = strings.TrimSuffix(outField.Name, "Edge") 122 | } else { 123 | Warning("Invalid field name for EdgeType, need to specify struct type", outField.Name) 124 | } 125 | } else if qlTypeKind == QLTypeKind_Connection { 126 | if strings.HasSuffix(outField.Name, "Connection") { 127 | outInfo.ElemTypeName = strings.TrimSuffix(outField.Name, "Connection") 128 | } else { 129 | Warning("Invalid field name for ConnectionType, need to specify struct type", outField.Name) 130 | } 131 | } 132 | 133 | outQLTypes = append(outQLTypes, outQLType) 134 | outputInfos = append(outputInfos, outInfo) 135 | } 136 | mf.Outputs = outputInfos // save information for dynamicCallMutateAndGetPayload 137 | } else { 138 | Warning("AutoOutputs needs a struct value as argument", mf.MethodName, outStructType, outStructType.Kind()) 139 | } 140 | 141 | } else { 142 | // use manually OutputInfo and function type's output information 143 | for i := 0; i < funcType.NumOut(); i++ { 144 | outputInfo := mf.Outputs[i] 145 | outQLType, _ := getComplexQLType(funcType.Out(i), outputInfo.Name, qlTypes, qlConns) 146 | outQLTypes = append(outQLTypes, outQLType) 147 | outputInfos = append(outputInfos, outputInfo) 148 | } 149 | } 150 | 151 | for i := 0; i < len(outputInfos); i++ { 152 | 153 | outInfo := outputInfos[i] 154 | outQLType := outQLTypes[i] 155 | 156 | outputFields[outInfo.Name] = &graphql.Field{ 157 | Type: outQLType, 158 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 159 | payload := p.Source.(map[string]interface{}) 160 | output := payload[outInfo.Name] 161 | fmt.Println("Got output payload", outInfo.Name, output) 162 | return output, nil 163 | }, 164 | } 165 | 166 | } 167 | mutConf.OutputFields = outputFields 168 | 169 | mfCaptured := mf 170 | mutConf.MutateAndGetPayload = func(inputMap map[string]interface{}, info graphql.ResolveInfo, ctx context.Context) (map[string]interface{}, error) { 171 | return sch.dynamicCallMutateAndGetPayload(mfCaptured, funcType, typ, inputFields, inputMap) 172 | } 173 | 174 | mutationFields[mf.Name] = relay.MutationWithClientMutationID(mutConf) 175 | } else { 176 | Warning("Cannot find method", mf.MethodName, "for type", refType.Name()) 177 | } 178 | } 179 | 180 | mutationType := graphql.NewObject(graphql.ObjectConfig{ 181 | Name: "Mutation", 182 | Fields: mutationFields, 183 | }) 184 | return mutationType 185 | } 186 | 187 | func (sch *SchemaInfo) dynamicCallMutateAndGetPayload( 188 | mf MutationFieldInfo, 189 | funcType reflect.Type, 190 | typ *TypeInfo, 191 | inputFields graphql.InputObjectConfigFieldMap, 192 | inputMap map[string]interface{}) (map[string]interface{}, error) { 193 | 194 | fmt.Println("[dynamicCallMutateAndGetPayload]", "funcType=", funcType, "mf=", mf, "typ=", typ.Name) 195 | 196 | mutVal := reflect.ValueOf(typ.instance) 197 | methodVal := mutVal.MethodByName(mf.MethodName) 198 | 199 | var inValues []reflect.Value 200 | 201 | if mf.AutoArgs { 202 | // use struct args 203 | if funcType.NumIn() == 2 { 204 | argStructType := funcType.In(1) 205 | argStructVal := reflect.New(argStructType).Elem() 206 | 207 | for i := 0; i < argStructVal.NumField(); i++ { 208 | argStructField := argStructType.Field(i) 209 | argStructFieldVal := argStructVal.Field(i) 210 | lowerFirstFieldName := lowerFirst(argStructField.Name) 211 | 212 | var argObj interface{} = nil 213 | var hasInput bool 214 | if argObj, hasInput = inputMap[lowerFirstFieldName]; !hasInput { 215 | argObj = inputFields[lowerFirstFieldName].DefaultValue 216 | } 217 | if argObj != nil { 218 | argStructFieldVal.Set(reflect.ValueOf(argObj)) // bind field value 219 | } 220 | } 221 | inValues = append(inValues, argStructVal) 222 | } 223 | 224 | } else { 225 | // use plain args 226 | for _, arg := range mf.Args { 227 | var argObj interface{} 228 | var hasInput bool 229 | if argObj, hasInput = inputMap[arg.Name]; !hasInput { 230 | argObj = arg.DefaultValue 231 | } 232 | inValues = append(inValues, reflect.ValueOf(argObj)) 233 | } 234 | } 235 | 236 | outValues := methodVal.Call(inValues) // call mutate function! 237 | 238 | outMap := make(map[string]interface{}) 239 | 240 | for i, outInfo := range mf.Outputs { 241 | // set output fields map, will be sent to output fields resolver 242 | if mf.AutoOutputs { 243 | outMap[outInfo.Name] = outValues[0].Elem().Field(i).Interface() // extract field value from output struct 244 | } else { 245 | outMap[outInfo.Name] = outValues[i].Interface() 246 | } 247 | } 248 | 249 | return outMap, nil 250 | } 251 | -------------------------------------------------------------------------------- /definition.go: -------------------------------------------------------------------------------- 1 | package gographer 2 | 3 | import ( 4 | "encoding" 5 | "fmt" 6 | "github.com/graphql-go/graphql" 7 | "github.com/graphql-go/relay" 8 | "reflect" 9 | "strings" 10 | ) 11 | 12 | const ( 13 | TAG_DefaultValue = "def" 14 | TAG_NonNull = "nonNull" 15 | ) 16 | 17 | const ( 18 | QLTypeKind_Simple = "QLTYPE_Simple" 19 | QLTypeKind_SimpleList = "QLTYPE_SimpleList" 20 | QLTypeKind_Struct = "QLTypeKind_Struct" 21 | QLTypeKind_Connection = "QLTYPE_Connection" 22 | QLTypeKind_Edge = "QLTYPE_Edge" 23 | ) 24 | 25 | type QLTypeKind string 26 | 27 | type SchemaInfo struct { 28 | types []*TypeInfo 29 | typesByName map[string]*TypeInfo 30 | rootInstance interface{} 31 | mutationInstance interface{} 32 | } 33 | 34 | func NewSchemaInfo() *SchemaInfo { 35 | return &SchemaInfo{ 36 | typesByName: make(map[string]*TypeInfo), 37 | } 38 | } 39 | 40 | func (sch *SchemaInfo) RegType(instance interface{}) *TypeInfo { 41 | typeDef := NewTypeInfo(instance) 42 | sch.types = append(sch.types, typeDef) 43 | sch.typesByName[typeDef.Name] = typeDef 44 | return typeDef 45 | } 46 | 47 | type TypeInfo struct { 48 | Name string 49 | Type reflect.Type 50 | idResolver IDResolver 51 | fields graphql.Fields 52 | resolvedFields []ResolvedFieldInfo 53 | mutationFields []MutationFieldInfo 54 | isRootType bool 55 | isMutationType bool 56 | instance interface{} 57 | isNonNode bool 58 | embeddedTypes map[string]reflect.Type 59 | } 60 | 61 | type IDResolver func(id string) interface{} 62 | 63 | func NewTypeInfo(instance interface{}) *TypeInfo { 64 | type_ := reflect.TypeOf(instance) 65 | if type_.Kind() == reflect.Ptr { 66 | type_ = type_.Elem() 67 | } 68 | typeDef := TypeInfo{ 69 | Type: type_, 70 | Name: type_.Name(), 71 | fields: make(graphql.Fields), 72 | instance: instance, 73 | embeddedTypes: make(map[string]reflect.Type), 74 | } 75 | return &typeDef 76 | } 77 | 78 | func (typ *TypeInfo) SetNonNode() *TypeInfo { 79 | typ.isNonNode = true 80 | return typ 81 | } 82 | 83 | func (typ *TypeInfo) SetIDResolver(f IDResolver) *TypeInfo { 84 | typ.idResolver = f 85 | return typ 86 | } 87 | 88 | func (typ *TypeInfo) SetEmbeddedTypes(ifaces ...interface{}) *TypeInfo { 89 | for _, iface := range ifaces { 90 | t := reflect.TypeOf(iface) 91 | typ.embeddedTypes[t.Name()] = t 92 | } 93 | return typ 94 | } 95 | 96 | func (typ *TypeInfo) SetRoot() *TypeInfo { 97 | typ.isRootType = true 98 | return typ 99 | } 100 | 101 | func (typ *TypeInfo) SimpleField(name string) *TypeInfo { 102 | for i := 0; i < typ.Type.NumField(); i++ { 103 | field := typ.Type.Field(i) 104 | if field.Name == name || field.Tag.Get("json") == name { 105 | if qlType := ToQLType(field.Type); qlType != nil { 106 | return typ.AddField(name, &graphql.Field{ 107 | Type: qlType, 108 | }) 109 | } 110 | } 111 | } 112 | Warning("SimpleField not found", typ.Name, name) 113 | return typ 114 | } 115 | 116 | func (typ *TypeInfo) IDField(name string, idFetcher relay.GlobalIDFetcherFn) *TypeInfo { 117 | for i := 0; i < typ.Type.NumField(); i++ { 118 | field := typ.Type.Field(i) 119 | if field.Name == name || field.Tag.Get("json") == name { 120 | return typ.AddField(name, relay.GlobalIDField(typ.Name, idFetcher)) 121 | } 122 | } 123 | Warning("IDField not found", typ.Name, name) 124 | return typ 125 | } 126 | 127 | var TextMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() 128 | 129 | // Auto adds simple fields, including embedded struct's fields (implemented with resolved field) 130 | func (typ *TypeInfo) SimpleFields() *TypeInfo { 131 | 132 | typ.processSimpleFields([]string{}, typ.Type) 133 | 134 | return typ 135 | } 136 | 137 | // Recursively process all the fields, including embedded struct fields. 138 | func (typ *TypeInfo) processSimpleFields(nestFieldsInput []string, nestTypeInput reflect.Type) { 139 | //fmt.Println("processSimpleFields", nestTypeInput, nestFieldsInput) 140 | var nestFields []string 141 | for _, nf := range nestFieldsInput { 142 | nestFields = append(nestFields, nf) 143 | } 144 | var nestType = nestTypeInput 145 | 146 | for i := 0; i < nestType.NumField(); i++ { 147 | field := nestType.Field(i) 148 | fmt.Println(nestType.Name(), i, field.Name, field.Type) 149 | var fullFieldName = field.Name 150 | var fieldName string 151 | if jsonTag := field.Tag.Get("json"); jsonTag != "" { 152 | fieldName = jsonTag 153 | } else { 154 | fieldName = field.Name 155 | } 156 | 157 | hasQLType := false 158 | var qlType graphql.Output 159 | if qlType = ToQLType(field.Type); qlType != nil { 160 | if len(nestFields) == 0 { 161 | typ.AddField(fieldName, &graphql.Field{ 162 | Type: qlType, 163 | }) 164 | } else { 165 | typ.resolvedFields = append(typ.resolvedFields, ResolvedFieldInfo{ 166 | Name: fieldName, 167 | Args: nil, 168 | AutoArgs: true, 169 | ManualType: qlType, 170 | ExtensionFunc: func(s interface{}) interface{} { 171 | val := reflect.ValueOf(s) 172 | // iterate field value chain 173 | for _, nf := range nestFields { 174 | val = val.FieldByName(nf) 175 | } 176 | fieldValue := val.FieldByName(fullFieldName) 177 | if fieldValue.IsValid() { 178 | return fieldValue.Interface() 179 | } 180 | return nil 181 | }, 182 | }) 183 | } 184 | hasQLType = true 185 | } 186 | 187 | if !hasQLType { 188 | // deal with struct field... 189 | if field.Type.Kind() == reflect.Struct { 190 | fmt.Println("Struct Field: ", typ.Name, field.Name, field.Type.Name()) 191 | 192 | if field.Type.Implements(TextMarshalerType) { 193 | // time.Time 194 | //fmt.Println("is TextMarshalerType") 195 | fullFieldName := field.Name 196 | typ.ExtensionField(fieldName, func(s interface{}) string { 197 | val := reflect.ValueOf(s) 198 | // iterate field value chain 199 | for _, nf := range nestFields { 200 | val = val.FieldByName(nf) 201 | } 202 | fieldValue := val.FieldByName(fullFieldName) 203 | if fieldValue.IsValid() { 204 | if textMarshaler, ok := fieldValue.Interface().(encoding.TextMarshaler); ok { 205 | text, _ := textMarshaler.MarshalText() 206 | return string(text) 207 | } 208 | } 209 | return "" 210 | }, AutoArgs) 211 | 212 | } else { 213 | // handle embedded struct 214 | if field.Name == field.Type.Name() && field.Type == typ.embeddedTypes[field.Name] { 215 | var nextNestFields []string 216 | for _, nf := range nestFields { 217 | nextNestFields = append(nextNestFields, nf) 218 | } 219 | nextNestFields = append(nextNestFields, field.Name) 220 | //nestType = field.Type will set previous call's nestType, don't do it. 221 | typ.processSimpleFields(nextNestFields, field.Type) 222 | } 223 | } 224 | } 225 | } 226 | } 227 | } 228 | 229 | func (typ *TypeInfo) ResolvedField(name string, methodName string, args []ArgInfo) *TypeInfo { 230 | autoArgs := IsAutoArgs(args) 231 | if autoArgs { 232 | args = nil 233 | } 234 | typ.resolvedFields = append(typ.resolvedFields, ResolvedFieldInfo{ 235 | Name: name, 236 | MethodName: methodName, 237 | Args: args, 238 | AutoArgs: autoArgs, 239 | }) 240 | return typ 241 | } 242 | 243 | func (typ *TypeInfo) ExtensionField(name string, extensionFunc interface{}, args []ArgInfo) *TypeInfo { 244 | autoArgs := IsAutoArgs(args) 245 | if autoArgs { 246 | args = nil 247 | } 248 | typ.resolvedFields = append(typ.resolvedFields, ResolvedFieldInfo{ 249 | Name: name, 250 | ExtensionFunc: extensionFunc, 251 | Args: args, 252 | AutoArgs: autoArgs, 253 | }) 254 | return typ 255 | } 256 | 257 | // Auto adds resolved fields 258 | func (typ *TypeInfo) ResolvedFields() *TypeInfo { 259 | ptrType := reflect.PtrTo(typ.Type) 260 | for i := 0; i < ptrType.NumMethod(); i++ { 261 | method := ptrType.Method(i) 262 | var methodName = method.Name 263 | if strings.HasPrefix(methodName, "Get") { 264 | fieldName := lowerFirst(strings.TrimPrefix(methodName, "Get")) 265 | typ.ResolvedField(fieldName, methodName, AutoArgs) 266 | } 267 | } 268 | return typ 269 | } 270 | 271 | type ArgInfo struct { 272 | Name string 273 | DefaultValue interface{} 274 | NonNull bool 275 | } 276 | 277 | var AutoArgs = []ArgInfo{ArgInfo{"__AutoArgs__", nil, false}} 278 | 279 | func IsAutoArgs(args []ArgInfo) bool { 280 | return len(args) == 1 && args[0] == AutoArgs[0] 281 | } 282 | 283 | func (typ *TypeInfo) AddField(name string, field *graphql.Field) *TypeInfo { 284 | typ.fields[name] = field 285 | return typ 286 | } 287 | 288 | type ResolvedFieldInfo struct { 289 | Name string 290 | MethodName string 291 | Args []ArgInfo 292 | AutoArgs bool 293 | ExtensionFunc interface{} 294 | ManualType graphql.Output 295 | } 296 | 297 | func (typ *TypeInfo) SetMutation() *TypeInfo { 298 | typ.isMutationType = true 299 | return typ 300 | } 301 | 302 | func (typ *TypeInfo) MutationField(name string, methodName string, args []ArgInfo, outputs []OutputInfo) *TypeInfo { 303 | autoArgs := IsAutoArgs(args) 304 | if autoArgs { 305 | args = nil 306 | } 307 | autoOutputs := IsAutoOutputs(outputs) 308 | if autoOutputs { 309 | outputs = nil 310 | } 311 | typ.mutationFields = append(typ.mutationFields, MutationFieldInfo{ 312 | Name: name, 313 | MethodName: methodName, 314 | Args: args, 315 | AutoArgs: autoArgs, 316 | Outputs: outputs, 317 | AutoOutputs: autoOutputs, 318 | }) 319 | return typ 320 | } 321 | 322 | // Auto adds mutation fields 323 | func (typ *TypeInfo) MutationFields() *TypeInfo { 324 | ptrType := reflect.PtrTo(typ.Type) 325 | for i := 0; i < ptrType.NumMethod(); i++ { 326 | method := ptrType.Method(i) 327 | var methodName = method.Name 328 | var qlMutationName = lowerFirst(methodName) 329 | typ.MutationField(qlMutationName, methodName, AutoArgs, AutoOutputs) 330 | } 331 | return typ 332 | } 333 | 334 | type MutationFieldInfo struct { 335 | Name string 336 | MethodName string 337 | Args []ArgInfo 338 | AutoArgs bool 339 | Outputs []OutputInfo 340 | AutoOutputs bool 341 | } 342 | 343 | type OutputInfo struct { 344 | Name string 345 | ElemInterface interface{} 346 | ElemTypeName string 347 | } 348 | 349 | func (outputInfo OutputInfo) GetElementTypeName() string { 350 | if outputInfo.ElemTypeName != "" { 351 | return outputInfo.ElemTypeName 352 | } else if outputInfo.ElemInterface != nil { 353 | return reflect.TypeOf(outputInfo.ElemInterface).Name() 354 | } else { 355 | return "" 356 | } 357 | } 358 | 359 | var AutoOutputs = []OutputInfo{OutputInfo{Name: "__AutoOutputs__"}} 360 | 361 | func IsAutoOutputs(outputs []OutputInfo) bool { 362 | return len(outputs) == 1 && outputs[0] == AutoOutputs[0] 363 | } 364 | -------------------------------------------------------------------------------- /cmd/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "__schema": { 4 | "directives": [ 5 | { 6 | "args": [ 7 | { 8 | "defaultValue": null, 9 | "description": "Included when true.", 10 | "name": "if", 11 | "type": { 12 | "kind": "NON_NULL", 13 | "name": null, 14 | "ofType": { 15 | "kind": "SCALAR", 16 | "name": "Boolean", 17 | "ofType": null 18 | } 19 | } 20 | } 21 | ], 22 | "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", 23 | "name": "include", 24 | "onField": true, 25 | "onFragment": true, 26 | "onOperation": false 27 | }, 28 | { 29 | "args": [ 30 | { 31 | "defaultValue": null, 32 | "description": "Skipped when true.", 33 | "name": "if", 34 | "type": { 35 | "kind": "NON_NULL", 36 | "name": null, 37 | "ofType": { 38 | "kind": "SCALAR", 39 | "name": "Boolean", 40 | "ofType": null 41 | } 42 | } 43 | } 44 | ], 45 | "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", 46 | "name": "skip", 47 | "onField": true, 48 | "onFragment": true, 49 | "onOperation": false 50 | } 51 | ], 52 | "mutationType": { 53 | "name": "Mutation" 54 | }, 55 | "queryType": { 56 | "name": "Root" 57 | }, 58 | "types": [ 59 | { 60 | "description": null, 61 | "enumValues": null, 62 | "fields": null, 63 | "inputFields": null, 64 | "interfaces": null, 65 | "kind": "SCALAR", 66 | "name": "String", 67 | "possibleTypes": null 68 | }, 69 | { 70 | "description": null, 71 | "enumValues": null, 72 | "fields": [ 73 | { 74 | "args": [], 75 | "deprecationReason": null, 76 | "description": "The ID of an object", 77 | "isDeprecated": false, 78 | "name": "id", 79 | "type": { 80 | "kind": "NON_NULL", 81 | "name": null, 82 | "ofType": { 83 | "kind": "SCALAR", 84 | "name": "ID", 85 | "ofType": null 86 | } 87 | } 88 | }, 89 | { 90 | "args": [], 91 | "deprecationReason": null, 92 | "description": null, 93 | "isDeprecated": false, 94 | "name": "text", 95 | "type": { 96 | "kind": "SCALAR", 97 | "name": "String", 98 | "ofType": null 99 | } 100 | }, 101 | { 102 | "args": [], 103 | "deprecationReason": null, 104 | "description": null, 105 | "isDeprecated": false, 106 | "name": "complete", 107 | "type": { 108 | "kind": "SCALAR", 109 | "name": "Boolean", 110 | "ofType": null 111 | } 112 | } 113 | ], 114 | "inputFields": null, 115 | "interfaces": [ 116 | { 117 | "kind": "INTERFACE", 118 | "name": "Node", 119 | "ofType": null 120 | } 121 | ], 122 | "kind": "OBJECT", 123 | "name": "Todo", 124 | "possibleTypes": null 125 | }, 126 | { 127 | "description": "An enum describing what kind of type a given __Type is", 128 | "enumValues": [ 129 | { 130 | "deprecationReason": null, 131 | "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", 132 | "isDeprecated": false, 133 | "name": "OBJECT" 134 | }, 135 | { 136 | "deprecationReason": null, 137 | "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", 138 | "isDeprecated": false, 139 | "name": "INTERFACE" 140 | }, 141 | { 142 | "deprecationReason": null, 143 | "description": "Indicates this type is a union. `possibleTypes` is a valid field.", 144 | "isDeprecated": false, 145 | "name": "UNION" 146 | }, 147 | { 148 | "deprecationReason": null, 149 | "description": "Indicates this type is an enum. `enumValues` is a valid field.", 150 | "isDeprecated": false, 151 | "name": "ENUM" 152 | }, 153 | { 154 | "deprecationReason": null, 155 | "description": "Indicates this type is an input object. `inputFields` is a valid field.", 156 | "isDeprecated": false, 157 | "name": "INPUT_OBJECT" 158 | }, 159 | { 160 | "deprecationReason": null, 161 | "description": "Indicates this type is a list. `ofType` is a valid field.", 162 | "isDeprecated": false, 163 | "name": "LIST" 164 | }, 165 | { 166 | "deprecationReason": null, 167 | "description": "Indicates this type is a non-null. `ofType` is a valid field.", 168 | "isDeprecated": false, 169 | "name": "NON_NULL" 170 | }, 171 | { 172 | "deprecationReason": null, 173 | "description": "Indicates this type is a scalar.", 174 | "isDeprecated": false, 175 | "name": "SCALAR" 176 | } 177 | ], 178 | "fields": null, 179 | "inputFields": null, 180 | "interfaces": null, 181 | "kind": "ENUM", 182 | "name": "__TypeKind", 183 | "possibleTypes": null 184 | }, 185 | { 186 | "description": null, 187 | "enumValues": null, 188 | "fields": [ 189 | { 190 | "args": [], 191 | "deprecationReason": null, 192 | "description": null, 193 | "isDeprecated": false, 194 | "name": "name", 195 | "type": { 196 | "kind": "NON_NULL", 197 | "name": null, 198 | "ofType": { 199 | "kind": "SCALAR", 200 | "name": "String", 201 | "ofType": null 202 | } 203 | } 204 | }, 205 | { 206 | "args": [], 207 | "deprecationReason": null, 208 | "description": null, 209 | "isDeprecated": false, 210 | "name": "description", 211 | "type": { 212 | "kind": "SCALAR", 213 | "name": "String", 214 | "ofType": null 215 | } 216 | }, 217 | { 218 | "args": [], 219 | "deprecationReason": null, 220 | "description": null, 221 | "isDeprecated": false, 222 | "name": "type", 223 | "type": { 224 | "kind": "NON_NULL", 225 | "name": null, 226 | "ofType": { 227 | "kind": "OBJECT", 228 | "name": "__Type", 229 | "ofType": null 230 | } 231 | } 232 | }, 233 | { 234 | "args": [], 235 | "deprecationReason": null, 236 | "description": null, 237 | "isDeprecated": false, 238 | "name": "defaultValue", 239 | "type": { 240 | "kind": "SCALAR", 241 | "name": "String", 242 | "ofType": null 243 | } 244 | } 245 | ], 246 | "inputFields": null, 247 | "interfaces": [], 248 | "kind": "OBJECT", 249 | "name": "__InputValue", 250 | "possibleTypes": null 251 | }, 252 | { 253 | "description": null, 254 | "enumValues": null, 255 | "fields": [ 256 | { 257 | "args": [ 258 | { 259 | "defaultValue": null, 260 | "description": "The ID of an object", 261 | "name": "id", 262 | "type": { 263 | "kind": "NON_NULL", 264 | "name": null, 265 | "ofType": { 266 | "kind": "SCALAR", 267 | "name": "ID", 268 | "ofType": null 269 | } 270 | } 271 | } 272 | ], 273 | "deprecationReason": null, 274 | "description": "Fetches an object given its ID", 275 | "isDeprecated": false, 276 | "name": "node", 277 | "type": { 278 | "kind": "INTERFACE", 279 | "name": "Node", 280 | "ofType": null 281 | } 282 | }, 283 | { 284 | "args": [ 285 | { 286 | "defaultValue": null, 287 | "description": null, 288 | "name": "before", 289 | "type": { 290 | "kind": "SCALAR", 291 | "name": "String", 292 | "ofType": null 293 | } 294 | }, 295 | { 296 | "defaultValue": null, 297 | "description": null, 298 | "name": "after", 299 | "type": { 300 | "kind": "SCALAR", 301 | "name": "String", 302 | "ofType": null 303 | } 304 | }, 305 | { 306 | "defaultValue": null, 307 | "description": null, 308 | "name": "first", 309 | "type": { 310 | "kind": "SCALAR", 311 | "name": "Int", 312 | "ofType": null 313 | } 314 | }, 315 | { 316 | "defaultValue": null, 317 | "description": null, 318 | "name": "last", 319 | "type": { 320 | "kind": "SCALAR", 321 | "name": "Int", 322 | "ofType": null 323 | } 324 | } 325 | ], 326 | "deprecationReason": null, 327 | "description": null, 328 | "isDeprecated": false, 329 | "name": "viewer", 330 | "type": { 331 | "kind": "OBJECT", 332 | "name": "User", 333 | "ofType": null 334 | } 335 | } 336 | ], 337 | "inputFields": null, 338 | "interfaces": [], 339 | "kind": "OBJECT", 340 | "name": "Root", 341 | "possibleTypes": null 342 | }, 343 | { 344 | "description": null, 345 | "enumValues": null, 346 | "fields": null, 347 | "inputFields": [ 348 | { 349 | "defaultValue": null, 350 | "description": null, 351 | "name": "id", 352 | "type": { 353 | "kind": "NON_NULL", 354 | "name": null, 355 | "ofType": { 356 | "kind": "SCALAR", 357 | "name": "String", 358 | "ofType": null 359 | } 360 | } 361 | }, 362 | { 363 | "defaultValue": null, 364 | "description": null, 365 | "name": "text", 366 | "type": { 367 | "kind": "NON_NULL", 368 | "name": null, 369 | "ofType": { 370 | "kind": "SCALAR", 371 | "name": "String", 372 | "ofType": null 373 | } 374 | } 375 | }, 376 | { 377 | "defaultValue": null, 378 | "description": null, 379 | "name": "clientMutationId", 380 | "type": { 381 | "kind": "NON_NULL", 382 | "name": null, 383 | "ofType": { 384 | "kind": "SCALAR", 385 | "name": "String", 386 | "ofType": null 387 | } 388 | } 389 | } 390 | ], 391 | "interfaces": null, 392 | "kind": "INPUT_OBJECT", 393 | "name": "RenameTodoInput", 394 | "possibleTypes": null 395 | }, 396 | { 397 | "description": null, 398 | "enumValues": null, 399 | "fields": [ 400 | { 401 | "args": [], 402 | "deprecationReason": null, 403 | "description": null, 404 | "isDeprecated": false, 405 | "name": "description", 406 | "type": { 407 | "kind": "SCALAR", 408 | "name": "String", 409 | "ofType": null 410 | } 411 | }, 412 | { 413 | "args": [], 414 | "deprecationReason": null, 415 | "description": null, 416 | "isDeprecated": false, 417 | "name": "args", 418 | "type": { 419 | "kind": "NON_NULL", 420 | "name": null, 421 | "ofType": { 422 | "kind": "LIST", 423 | "name": null, 424 | "ofType": { 425 | "kind": "NON_NULL", 426 | "name": null, 427 | "ofType": { 428 | "kind": "OBJECT", 429 | "name": "__InputValue" 430 | } 431 | } 432 | } 433 | } 434 | }, 435 | { 436 | "args": [], 437 | "deprecationReason": null, 438 | "description": null, 439 | "isDeprecated": false, 440 | "name": "onOperation", 441 | "type": { 442 | "kind": "NON_NULL", 443 | "name": null, 444 | "ofType": { 445 | "kind": "SCALAR", 446 | "name": "Boolean", 447 | "ofType": null 448 | } 449 | } 450 | }, 451 | { 452 | "args": [], 453 | "deprecationReason": null, 454 | "description": null, 455 | "isDeprecated": false, 456 | "name": "onFragment", 457 | "type": { 458 | "kind": "NON_NULL", 459 | "name": null, 460 | "ofType": { 461 | "kind": "SCALAR", 462 | "name": "Boolean", 463 | "ofType": null 464 | } 465 | } 466 | }, 467 | { 468 | "args": [], 469 | "deprecationReason": null, 470 | "description": null, 471 | "isDeprecated": false, 472 | "name": "onField", 473 | "type": { 474 | "kind": "NON_NULL", 475 | "name": null, 476 | "ofType": { 477 | "kind": "SCALAR", 478 | "name": "Boolean", 479 | "ofType": null 480 | } 481 | } 482 | }, 483 | { 484 | "args": [], 485 | "deprecationReason": null, 486 | "description": null, 487 | "isDeprecated": false, 488 | "name": "name", 489 | "type": { 490 | "kind": "NON_NULL", 491 | "name": null, 492 | "ofType": { 493 | "kind": "SCALAR", 494 | "name": "String", 495 | "ofType": null 496 | } 497 | } 498 | } 499 | ], 500 | "inputFields": null, 501 | "interfaces": [], 502 | "kind": "OBJECT", 503 | "name": "__Directive", 504 | "possibleTypes": null 505 | }, 506 | { 507 | "description": null, 508 | "enumValues": null, 509 | "fields": null, 510 | "inputFields": [ 511 | { 512 | "defaultValue": null, 513 | "description": null, 514 | "name": "clientMutationId", 515 | "type": { 516 | "kind": "NON_NULL", 517 | "name": null, 518 | "ofType": { 519 | "kind": "SCALAR", 520 | "name": "String", 521 | "ofType": null 522 | } 523 | } 524 | }, 525 | { 526 | "defaultValue": null, 527 | "description": null, 528 | "name": "complete", 529 | "type": { 530 | "kind": "NON_NULL", 531 | "name": null, 532 | "ofType": { 533 | "kind": "SCALAR", 534 | "name": "Boolean", 535 | "ofType": null 536 | } 537 | } 538 | } 539 | ], 540 | "interfaces": null, 541 | "kind": "INPUT_OBJECT", 542 | "name": "MarkAllTodosInput", 543 | "possibleTypes": null 544 | }, 545 | { 546 | "description": null, 547 | "enumValues": null, 548 | "fields": [ 549 | { 550 | "args": [], 551 | "deprecationReason": null, 552 | "description": null, 553 | "isDeprecated": false, 554 | "name": "isDeprecated", 555 | "type": { 556 | "kind": "NON_NULL", 557 | "name": null, 558 | "ofType": { 559 | "kind": "SCALAR", 560 | "name": "Boolean", 561 | "ofType": null 562 | } 563 | } 564 | }, 565 | { 566 | "args": [], 567 | "deprecationReason": null, 568 | "description": null, 569 | "isDeprecated": false, 570 | "name": "deprecationReason", 571 | "type": { 572 | "kind": "SCALAR", 573 | "name": "String", 574 | "ofType": null 575 | } 576 | }, 577 | { 578 | "args": [], 579 | "deprecationReason": null, 580 | "description": null, 581 | "isDeprecated": false, 582 | "name": "name", 583 | "type": { 584 | "kind": "NON_NULL", 585 | "name": null, 586 | "ofType": { 587 | "kind": "SCALAR", 588 | "name": "String", 589 | "ofType": null 590 | } 591 | } 592 | }, 593 | { 594 | "args": [], 595 | "deprecationReason": null, 596 | "description": null, 597 | "isDeprecated": false, 598 | "name": "description", 599 | "type": { 600 | "kind": "SCALAR", 601 | "name": "String", 602 | "ofType": null 603 | } 604 | }, 605 | { 606 | "args": [], 607 | "deprecationReason": null, 608 | "description": null, 609 | "isDeprecated": false, 610 | "name": "args", 611 | "type": { 612 | "kind": "NON_NULL", 613 | "name": null, 614 | "ofType": { 615 | "kind": "LIST", 616 | "name": null, 617 | "ofType": { 618 | "kind": "NON_NULL", 619 | "name": null, 620 | "ofType": { 621 | "kind": "OBJECT", 622 | "name": "__InputValue" 623 | } 624 | } 625 | } 626 | } 627 | }, 628 | { 629 | "args": [], 630 | "deprecationReason": null, 631 | "description": null, 632 | "isDeprecated": false, 633 | "name": "type", 634 | "type": { 635 | "kind": "NON_NULL", 636 | "name": null, 637 | "ofType": { 638 | "kind": "OBJECT", 639 | "name": "__Type", 640 | "ofType": null 641 | } 642 | } 643 | } 644 | ], 645 | "inputFields": null, 646 | "interfaces": [], 647 | "kind": "OBJECT", 648 | "name": "__Field", 649 | "possibleTypes": null 650 | }, 651 | { 652 | "description": "A GraphQL Schema defines the capabilities of a GraphQL\nserver. It exposes all available types and directives on\nthe server, as well as the entry points for query and\nmutation operations.", 653 | "enumValues": null, 654 | "fields": [ 655 | { 656 | "args": [], 657 | "deprecationReason": null, 658 | "description": "If this server support subscription, the type that ' +\n 'subscription operations will be rooted at.", 659 | "isDeprecated": false, 660 | "name": "subscriptionType", 661 | "type": { 662 | "kind": "OBJECT", 663 | "name": "__Type", 664 | "ofType": null 665 | } 666 | }, 667 | { 668 | "args": [], 669 | "deprecationReason": null, 670 | "description": "A list of all directives supported by this server.", 671 | "isDeprecated": false, 672 | "name": "directives", 673 | "type": { 674 | "kind": "NON_NULL", 675 | "name": null, 676 | "ofType": { 677 | "kind": "LIST", 678 | "name": null, 679 | "ofType": { 680 | "kind": "NON_NULL", 681 | "name": null, 682 | "ofType": { 683 | "kind": "OBJECT", 684 | "name": "__Directive" 685 | } 686 | } 687 | } 688 | } 689 | }, 690 | { 691 | "args": [], 692 | "deprecationReason": null, 693 | "description": "A list of all types supported by this server.", 694 | "isDeprecated": false, 695 | "name": "types", 696 | "type": { 697 | "kind": "NON_NULL", 698 | "name": null, 699 | "ofType": { 700 | "kind": "LIST", 701 | "name": null, 702 | "ofType": { 703 | "kind": "NON_NULL", 704 | "name": null, 705 | "ofType": { 706 | "kind": "OBJECT", 707 | "name": "__Type" 708 | } 709 | } 710 | } 711 | } 712 | }, 713 | { 714 | "args": [], 715 | "deprecationReason": null, 716 | "description": "The type that query operations will be rooted at.", 717 | "isDeprecated": false, 718 | "name": "queryType", 719 | "type": { 720 | "kind": "NON_NULL", 721 | "name": null, 722 | "ofType": { 723 | "kind": "OBJECT", 724 | "name": "__Type", 725 | "ofType": null 726 | } 727 | } 728 | }, 729 | { 730 | "args": [], 731 | "deprecationReason": null, 732 | "description": "If this server supports mutation, the type that mutation operations will be rooted at.", 733 | "isDeprecated": false, 734 | "name": "mutationType", 735 | "type": { 736 | "kind": "OBJECT", 737 | "name": "__Type", 738 | "ofType": null 739 | } 740 | } 741 | ], 742 | "inputFields": null, 743 | "interfaces": [], 744 | "kind": "OBJECT", 745 | "name": "__Schema", 746 | "possibleTypes": null 747 | }, 748 | { 749 | "description": null, 750 | "enumValues": null, 751 | "fields": null, 752 | "inputFields": null, 753 | "interfaces": null, 754 | "kind": "SCALAR", 755 | "name": "Boolean", 756 | "possibleTypes": null 757 | }, 758 | { 759 | "description": null, 760 | "enumValues": null, 761 | "fields": [ 762 | { 763 | "args": [ 764 | { 765 | "defaultValue": null, 766 | "description": null, 767 | "name": "input", 768 | "type": { 769 | "kind": "NON_NULL", 770 | "name": null, 771 | "ofType": { 772 | "kind": "INPUT_OBJECT", 773 | "name": "ChangeTodoStatusInput", 774 | "ofType": null 775 | } 776 | } 777 | } 778 | ], 779 | "deprecationReason": null, 780 | "description": null, 781 | "isDeprecated": false, 782 | "name": "changeTodoStatus", 783 | "type": { 784 | "kind": "OBJECT", 785 | "name": "ChangeTodoStatusPayload", 786 | "ofType": null 787 | } 788 | }, 789 | { 790 | "args": [ 791 | { 792 | "defaultValue": null, 793 | "description": null, 794 | "name": "input", 795 | "type": { 796 | "kind": "NON_NULL", 797 | "name": null, 798 | "ofType": { 799 | "kind": "INPUT_OBJECT", 800 | "name": "MarkAllTodosInput", 801 | "ofType": null 802 | } 803 | } 804 | } 805 | ], 806 | "deprecationReason": null, 807 | "description": null, 808 | "isDeprecated": false, 809 | "name": "markAllTodos", 810 | "type": { 811 | "kind": "OBJECT", 812 | "name": "MarkAllTodosPayload", 813 | "ofType": null 814 | } 815 | }, 816 | { 817 | "args": [ 818 | { 819 | "defaultValue": null, 820 | "description": null, 821 | "name": "input", 822 | "type": { 823 | "kind": "NON_NULL", 824 | "name": null, 825 | "ofType": { 826 | "kind": "INPUT_OBJECT", 827 | "name": "RemoveCompletedTodosInput", 828 | "ofType": null 829 | } 830 | } 831 | } 832 | ], 833 | "deprecationReason": null, 834 | "description": null, 835 | "isDeprecated": false, 836 | "name": "removeCompletedTodos", 837 | "type": { 838 | "kind": "OBJECT", 839 | "name": "RemoveCompletedTodosPayload", 840 | "ofType": null 841 | } 842 | }, 843 | { 844 | "args": [ 845 | { 846 | "defaultValue": null, 847 | "description": null, 848 | "name": "input", 849 | "type": { 850 | "kind": "NON_NULL", 851 | "name": null, 852 | "ofType": { 853 | "kind": "INPUT_OBJECT", 854 | "name": "RemoveTodoInput", 855 | "ofType": null 856 | } 857 | } 858 | } 859 | ], 860 | "deprecationReason": null, 861 | "description": null, 862 | "isDeprecated": false, 863 | "name": "removeTodo", 864 | "type": { 865 | "kind": "OBJECT", 866 | "name": "RemoveTodoPayload", 867 | "ofType": null 868 | } 869 | }, 870 | { 871 | "args": [ 872 | { 873 | "defaultValue": null, 874 | "description": null, 875 | "name": "input", 876 | "type": { 877 | "kind": "NON_NULL", 878 | "name": null, 879 | "ofType": { 880 | "kind": "INPUT_OBJECT", 881 | "name": "RenameTodoInput", 882 | "ofType": null 883 | } 884 | } 885 | } 886 | ], 887 | "deprecationReason": null, 888 | "description": null, 889 | "isDeprecated": false, 890 | "name": "renameTodo", 891 | "type": { 892 | "kind": "OBJECT", 893 | "name": "RenameTodoPayload", 894 | "ofType": null 895 | } 896 | }, 897 | { 898 | "args": [ 899 | { 900 | "defaultValue": null, 901 | "description": null, 902 | "name": "input", 903 | "type": { 904 | "kind": "NON_NULL", 905 | "name": null, 906 | "ofType": { 907 | "kind": "INPUT_OBJECT", 908 | "name": "AddTodoInput", 909 | "ofType": null 910 | } 911 | } 912 | } 913 | ], 914 | "deprecationReason": null, 915 | "description": null, 916 | "isDeprecated": false, 917 | "name": "addTodo", 918 | "type": { 919 | "kind": "OBJECT", 920 | "name": "AddTodoPayload", 921 | "ofType": null 922 | } 923 | } 924 | ], 925 | "inputFields": null, 926 | "interfaces": [], 927 | "kind": "OBJECT", 928 | "name": "Mutation", 929 | "possibleTypes": null 930 | }, 931 | { 932 | "description": null, 933 | "enumValues": null, 934 | "fields": [ 935 | { 936 | "args": [], 937 | "deprecationReason": null, 938 | "description": null, 939 | "isDeprecated": false, 940 | "name": "changedTodos", 941 | "type": { 942 | "kind": "LIST", 943 | "name": null, 944 | "ofType": { 945 | "kind": "OBJECT", 946 | "name": "Todo", 947 | "ofType": null 948 | } 949 | } 950 | }, 951 | { 952 | "args": [], 953 | "deprecationReason": null, 954 | "description": null, 955 | "isDeprecated": false, 956 | "name": "viewer", 957 | "type": { 958 | "kind": "OBJECT", 959 | "name": "User", 960 | "ofType": null 961 | } 962 | }, 963 | { 964 | "args": [], 965 | "deprecationReason": null, 966 | "description": null, 967 | "isDeprecated": false, 968 | "name": "clientMutationId", 969 | "type": { 970 | "kind": "NON_NULL", 971 | "name": null, 972 | "ofType": { 973 | "kind": "SCALAR", 974 | "name": "String", 975 | "ofType": null 976 | } 977 | } 978 | } 979 | ], 980 | "inputFields": null, 981 | "interfaces": [], 982 | "kind": "OBJECT", 983 | "name": "MarkAllTodosPayload", 984 | "possibleTypes": null 985 | }, 986 | { 987 | "description": null, 988 | "enumValues": null, 989 | "fields": null, 990 | "inputFields": [ 991 | { 992 | "defaultValue": null, 993 | "description": null, 994 | "name": "clientMutationId", 995 | "type": { 996 | "kind": "NON_NULL", 997 | "name": null, 998 | "ofType": { 999 | "kind": "SCALAR", 1000 | "name": "String", 1001 | "ofType": null 1002 | } 1003 | } 1004 | } 1005 | ], 1006 | "interfaces": null, 1007 | "kind": "INPUT_OBJECT", 1008 | "name": "RemoveCompletedTodosInput", 1009 | "possibleTypes": null 1010 | }, 1011 | { 1012 | "description": null, 1013 | "enumValues": null, 1014 | "fields": [ 1015 | { 1016 | "args": [], 1017 | "deprecationReason": null, 1018 | "description": "The ID of an object", 1019 | "isDeprecated": false, 1020 | "name": "id", 1021 | "type": { 1022 | "kind": "NON_NULL", 1023 | "name": null, 1024 | "ofType": { 1025 | "kind": "SCALAR", 1026 | "name": "ID", 1027 | "ofType": null 1028 | } 1029 | } 1030 | }, 1031 | { 1032 | "args": [ 1033 | { 1034 | "defaultValue": null, 1035 | "description": null, 1036 | "name": "before", 1037 | "type": { 1038 | "kind": "SCALAR", 1039 | "name": "String", 1040 | "ofType": null 1041 | } 1042 | }, 1043 | { 1044 | "defaultValue": null, 1045 | "description": null, 1046 | "name": "after", 1047 | "type": { 1048 | "kind": "SCALAR", 1049 | "name": "String", 1050 | "ofType": null 1051 | } 1052 | }, 1053 | { 1054 | "defaultValue": null, 1055 | "description": null, 1056 | "name": "first", 1057 | "type": { 1058 | "kind": "SCALAR", 1059 | "name": "Int", 1060 | "ofType": null 1061 | } 1062 | }, 1063 | { 1064 | "defaultValue": null, 1065 | "description": null, 1066 | "name": "last", 1067 | "type": { 1068 | "kind": "SCALAR", 1069 | "name": "Int", 1070 | "ofType": null 1071 | } 1072 | } 1073 | ], 1074 | "deprecationReason": null, 1075 | "description": null, 1076 | "isDeprecated": false, 1077 | "name": "completedCount", 1078 | "type": { 1079 | "kind": "SCALAR", 1080 | "name": "Int", 1081 | "ofType": null 1082 | } 1083 | }, 1084 | { 1085 | "args": [ 1086 | { 1087 | "defaultValue": "\"any\"", 1088 | "description": null, 1089 | "name": "status", 1090 | "type": { 1091 | "kind": "SCALAR", 1092 | "name": "String", 1093 | "ofType": null 1094 | } 1095 | }, 1096 | { 1097 | "defaultValue": null, 1098 | "description": null, 1099 | "name": "before", 1100 | "type": { 1101 | "kind": "SCALAR", 1102 | "name": "String", 1103 | "ofType": null 1104 | } 1105 | }, 1106 | { 1107 | "defaultValue": null, 1108 | "description": null, 1109 | "name": "after", 1110 | "type": { 1111 | "kind": "SCALAR", 1112 | "name": "String", 1113 | "ofType": null 1114 | } 1115 | }, 1116 | { 1117 | "defaultValue": null, 1118 | "description": null, 1119 | "name": "first", 1120 | "type": { 1121 | "kind": "SCALAR", 1122 | "name": "Int", 1123 | "ofType": null 1124 | } 1125 | }, 1126 | { 1127 | "defaultValue": null, 1128 | "description": null, 1129 | "name": "last", 1130 | "type": { 1131 | "kind": "SCALAR", 1132 | "name": "Int", 1133 | "ofType": null 1134 | } 1135 | } 1136 | ], 1137 | "deprecationReason": null, 1138 | "description": null, 1139 | "isDeprecated": false, 1140 | "name": "todos", 1141 | "type": { 1142 | "kind": "LIST", 1143 | "name": null, 1144 | "ofType": { 1145 | "kind": "OBJECT", 1146 | "name": "Todo", 1147 | "ofType": null 1148 | } 1149 | } 1150 | }, 1151 | { 1152 | "args": [ 1153 | { 1154 | "defaultValue": null, 1155 | "description": null, 1156 | "name": "before", 1157 | "type": { 1158 | "kind": "SCALAR", 1159 | "name": "String", 1160 | "ofType": null 1161 | } 1162 | }, 1163 | { 1164 | "defaultValue": null, 1165 | "description": null, 1166 | "name": "after", 1167 | "type": { 1168 | "kind": "SCALAR", 1169 | "name": "String", 1170 | "ofType": null 1171 | } 1172 | }, 1173 | { 1174 | "defaultValue": null, 1175 | "description": null, 1176 | "name": "first", 1177 | "type": { 1178 | "kind": "SCALAR", 1179 | "name": "Int", 1180 | "ofType": null 1181 | } 1182 | }, 1183 | { 1184 | "defaultValue": null, 1185 | "description": null, 1186 | "name": "last", 1187 | "type": { 1188 | "kind": "SCALAR", 1189 | "name": "Int", 1190 | "ofType": null 1191 | } 1192 | } 1193 | ], 1194 | "deprecationReason": null, 1195 | "description": null, 1196 | "isDeprecated": false, 1197 | "name": "totalCount", 1198 | "type": { 1199 | "kind": "SCALAR", 1200 | "name": "Int", 1201 | "ofType": null 1202 | } 1203 | } 1204 | ], 1205 | "inputFields": null, 1206 | "interfaces": [ 1207 | { 1208 | "kind": "INTERFACE", 1209 | "name": "Node", 1210 | "ofType": null 1211 | } 1212 | ], 1213 | "kind": "OBJECT", 1214 | "name": "User", 1215 | "possibleTypes": null 1216 | }, 1217 | { 1218 | "description": null, 1219 | "enumValues": null, 1220 | "fields": null, 1221 | "inputFields": null, 1222 | "interfaces": null, 1223 | "kind": "SCALAR", 1224 | "name": "ID", 1225 | "possibleTypes": null 1226 | }, 1227 | { 1228 | "description": null, 1229 | "enumValues": null, 1230 | "fields": [ 1231 | { 1232 | "args": [], 1233 | "deprecationReason": null, 1234 | "description": null, 1235 | "isDeprecated": false, 1236 | "name": "interfaces", 1237 | "type": { 1238 | "kind": "LIST", 1239 | "name": null, 1240 | "ofType": { 1241 | "kind": "NON_NULL", 1242 | "name": null, 1243 | "ofType": { 1244 | "kind": "OBJECT", 1245 | "name": "__Type", 1246 | "ofType": null 1247 | } 1248 | } 1249 | } 1250 | }, 1251 | { 1252 | "args": [], 1253 | "deprecationReason": null, 1254 | "description": null, 1255 | "isDeprecated": false, 1256 | "name": "ofType", 1257 | "type": { 1258 | "kind": "OBJECT", 1259 | "name": "__Type", 1260 | "ofType": null 1261 | } 1262 | }, 1263 | { 1264 | "args": [], 1265 | "deprecationReason": null, 1266 | "description": null, 1267 | "isDeprecated": false, 1268 | "name": "description", 1269 | "type": { 1270 | "kind": "SCALAR", 1271 | "name": "String", 1272 | "ofType": null 1273 | } 1274 | }, 1275 | { 1276 | "args": [ 1277 | { 1278 | "defaultValue": "false", 1279 | "description": null, 1280 | "name": "includeDeprecated", 1281 | "type": { 1282 | "kind": "SCALAR", 1283 | "name": "Boolean", 1284 | "ofType": null 1285 | } 1286 | } 1287 | ], 1288 | "deprecationReason": null, 1289 | "description": null, 1290 | "isDeprecated": false, 1291 | "name": "fields", 1292 | "type": { 1293 | "kind": "LIST", 1294 | "name": null, 1295 | "ofType": { 1296 | "kind": "NON_NULL", 1297 | "name": null, 1298 | "ofType": { 1299 | "kind": "OBJECT", 1300 | "name": "__Field", 1301 | "ofType": null 1302 | } 1303 | } 1304 | } 1305 | }, 1306 | { 1307 | "args": [ 1308 | { 1309 | "defaultValue": "false", 1310 | "description": null, 1311 | "name": "includeDeprecated", 1312 | "type": { 1313 | "kind": "SCALAR", 1314 | "name": "Boolean", 1315 | "ofType": null 1316 | } 1317 | } 1318 | ], 1319 | "deprecationReason": null, 1320 | "description": null, 1321 | "isDeprecated": false, 1322 | "name": "enumValues", 1323 | "type": { 1324 | "kind": "LIST", 1325 | "name": null, 1326 | "ofType": { 1327 | "kind": "NON_NULL", 1328 | "name": null, 1329 | "ofType": { 1330 | "kind": "OBJECT", 1331 | "name": "__EnumValue", 1332 | "ofType": null 1333 | } 1334 | } 1335 | } 1336 | }, 1337 | { 1338 | "args": [], 1339 | "deprecationReason": null, 1340 | "description": null, 1341 | "isDeprecated": false, 1342 | "name": "kind", 1343 | "type": { 1344 | "kind": "NON_NULL", 1345 | "name": null, 1346 | "ofType": { 1347 | "kind": "ENUM", 1348 | "name": "__TypeKind", 1349 | "ofType": null 1350 | } 1351 | } 1352 | }, 1353 | { 1354 | "args": [], 1355 | "deprecationReason": null, 1356 | "description": null, 1357 | "isDeprecated": false, 1358 | "name": "name", 1359 | "type": { 1360 | "kind": "SCALAR", 1361 | "name": "String", 1362 | "ofType": null 1363 | } 1364 | }, 1365 | { 1366 | "args": [], 1367 | "deprecationReason": null, 1368 | "description": null, 1369 | "isDeprecated": false, 1370 | "name": "inputFields", 1371 | "type": { 1372 | "kind": "LIST", 1373 | "name": null, 1374 | "ofType": { 1375 | "kind": "NON_NULL", 1376 | "name": null, 1377 | "ofType": { 1378 | "kind": "OBJECT", 1379 | "name": "__InputValue", 1380 | "ofType": null 1381 | } 1382 | } 1383 | } 1384 | }, 1385 | { 1386 | "args": [], 1387 | "deprecationReason": null, 1388 | "description": null, 1389 | "isDeprecated": false, 1390 | "name": "possibleTypes", 1391 | "type": { 1392 | "kind": "LIST", 1393 | "name": null, 1394 | "ofType": { 1395 | "kind": "NON_NULL", 1396 | "name": null, 1397 | "ofType": { 1398 | "kind": "OBJECT", 1399 | "name": "__Type", 1400 | "ofType": null 1401 | } 1402 | } 1403 | } 1404 | } 1405 | ], 1406 | "inputFields": null, 1407 | "interfaces": [], 1408 | "kind": "OBJECT", 1409 | "name": "__Type", 1410 | "possibleTypes": null 1411 | }, 1412 | { 1413 | "description": null, 1414 | "enumValues": null, 1415 | "fields": [ 1416 | { 1417 | "args": [], 1418 | "deprecationReason": null, 1419 | "description": null, 1420 | "isDeprecated": false, 1421 | "name": "isDeprecated", 1422 | "type": { 1423 | "kind": "NON_NULL", 1424 | "name": null, 1425 | "ofType": { 1426 | "kind": "SCALAR", 1427 | "name": "Boolean", 1428 | "ofType": null 1429 | } 1430 | } 1431 | }, 1432 | { 1433 | "args": [], 1434 | "deprecationReason": null, 1435 | "description": null, 1436 | "isDeprecated": false, 1437 | "name": "deprecationReason", 1438 | "type": { 1439 | "kind": "SCALAR", 1440 | "name": "String", 1441 | "ofType": null 1442 | } 1443 | }, 1444 | { 1445 | "args": [], 1446 | "deprecationReason": null, 1447 | "description": null, 1448 | "isDeprecated": false, 1449 | "name": "name", 1450 | "type": { 1451 | "kind": "NON_NULL", 1452 | "name": null, 1453 | "ofType": { 1454 | "kind": "SCALAR", 1455 | "name": "String", 1456 | "ofType": null 1457 | } 1458 | } 1459 | }, 1460 | { 1461 | "args": [], 1462 | "deprecationReason": null, 1463 | "description": null, 1464 | "isDeprecated": false, 1465 | "name": "description", 1466 | "type": { 1467 | "kind": "SCALAR", 1468 | "name": "String", 1469 | "ofType": null 1470 | } 1471 | } 1472 | ], 1473 | "inputFields": null, 1474 | "interfaces": [], 1475 | "kind": "OBJECT", 1476 | "name": "__EnumValue", 1477 | "possibleTypes": null 1478 | }, 1479 | { 1480 | "description": null, 1481 | "enumValues": null, 1482 | "fields": null, 1483 | "inputFields": [ 1484 | { 1485 | "defaultValue": null, 1486 | "description": null, 1487 | "name": "id", 1488 | "type": { 1489 | "kind": "NON_NULL", 1490 | "name": null, 1491 | "ofType": { 1492 | "kind": "SCALAR", 1493 | "name": "String", 1494 | "ofType": null 1495 | } 1496 | } 1497 | }, 1498 | { 1499 | "defaultValue": null, 1500 | "description": null, 1501 | "name": "clientMutationId", 1502 | "type": { 1503 | "kind": "NON_NULL", 1504 | "name": null, 1505 | "ofType": { 1506 | "kind": "SCALAR", 1507 | "name": "String", 1508 | "ofType": null 1509 | } 1510 | } 1511 | } 1512 | ], 1513 | "interfaces": null, 1514 | "kind": "INPUT_OBJECT", 1515 | "name": "RemoveTodoInput", 1516 | "possibleTypes": null 1517 | }, 1518 | { 1519 | "description": null, 1520 | "enumValues": null, 1521 | "fields": null, 1522 | "inputFields": [ 1523 | { 1524 | "defaultValue": null, 1525 | "description": null, 1526 | "name": "text", 1527 | "type": { 1528 | "kind": "NON_NULL", 1529 | "name": null, 1530 | "ofType": { 1531 | "kind": "SCALAR", 1532 | "name": "String", 1533 | "ofType": null 1534 | } 1535 | } 1536 | }, 1537 | { 1538 | "defaultValue": null, 1539 | "description": null, 1540 | "name": "clientMutationId", 1541 | "type": { 1542 | "kind": "NON_NULL", 1543 | "name": null, 1544 | "ofType": { 1545 | "kind": "SCALAR", 1546 | "name": "String", 1547 | "ofType": null 1548 | } 1549 | } 1550 | } 1551 | ], 1552 | "interfaces": null, 1553 | "kind": "INPUT_OBJECT", 1554 | "name": "AddTodoInput", 1555 | "possibleTypes": null 1556 | }, 1557 | { 1558 | "description": "An edge in a connection", 1559 | "enumValues": null, 1560 | "fields": [ 1561 | { 1562 | "args": [], 1563 | "deprecationReason": null, 1564 | "description": " cursor for use in pagination", 1565 | "isDeprecated": false, 1566 | "name": "cursor", 1567 | "type": { 1568 | "kind": "NON_NULL", 1569 | "name": null, 1570 | "ofType": { 1571 | "kind": "SCALAR", 1572 | "name": "String", 1573 | "ofType": null 1574 | } 1575 | } 1576 | }, 1577 | { 1578 | "args": [], 1579 | "deprecationReason": null, 1580 | "description": "The item at the end of the edge", 1581 | "isDeprecated": false, 1582 | "name": "node", 1583 | "type": { 1584 | "kind": "OBJECT", 1585 | "name": "Todo", 1586 | "ofType": null 1587 | } 1588 | } 1589 | ], 1590 | "inputFields": null, 1591 | "interfaces": [], 1592 | "kind": "OBJECT", 1593 | "name": "TodoEdge", 1594 | "possibleTypes": null 1595 | }, 1596 | { 1597 | "description": null, 1598 | "enumValues": null, 1599 | "fields": null, 1600 | "inputFields": [ 1601 | { 1602 | "defaultValue": null, 1603 | "description": null, 1604 | "name": "id", 1605 | "type": { 1606 | "kind": "NON_NULL", 1607 | "name": null, 1608 | "ofType": { 1609 | "kind": "SCALAR", 1610 | "name": "String", 1611 | "ofType": null 1612 | } 1613 | } 1614 | }, 1615 | { 1616 | "defaultValue": null, 1617 | "description": null, 1618 | "name": "complete", 1619 | "type": { 1620 | "kind": "NON_NULL", 1621 | "name": null, 1622 | "ofType": { 1623 | "kind": "SCALAR", 1624 | "name": "Boolean", 1625 | "ofType": null 1626 | } 1627 | } 1628 | }, 1629 | { 1630 | "defaultValue": null, 1631 | "description": null, 1632 | "name": "clientMutationId", 1633 | "type": { 1634 | "kind": "NON_NULL", 1635 | "name": null, 1636 | "ofType": { 1637 | "kind": "SCALAR", 1638 | "name": "String", 1639 | "ofType": null 1640 | } 1641 | } 1642 | } 1643 | ], 1644 | "interfaces": null, 1645 | "kind": "INPUT_OBJECT", 1646 | "name": "ChangeTodoStatusInput", 1647 | "possibleTypes": null 1648 | }, 1649 | { 1650 | "description": null, 1651 | "enumValues": null, 1652 | "fields": [ 1653 | { 1654 | "args": [], 1655 | "deprecationReason": null, 1656 | "description": null, 1657 | "isDeprecated": false, 1658 | "name": "viewer", 1659 | "type": { 1660 | "kind": "OBJECT", 1661 | "name": "User", 1662 | "ofType": null 1663 | } 1664 | }, 1665 | { 1666 | "args": [], 1667 | "deprecationReason": null, 1668 | "description": null, 1669 | "isDeprecated": false, 1670 | "name": "clientMutationId", 1671 | "type": { 1672 | "kind": "NON_NULL", 1673 | "name": null, 1674 | "ofType": { 1675 | "kind": "SCALAR", 1676 | "name": "String", 1677 | "ofType": null 1678 | } 1679 | } 1680 | }, 1681 | { 1682 | "args": [], 1683 | "deprecationReason": null, 1684 | "description": null, 1685 | "isDeprecated": false, 1686 | "name": "todo", 1687 | "type": { 1688 | "kind": "OBJECT", 1689 | "name": "Todo", 1690 | "ofType": null 1691 | } 1692 | } 1693 | ], 1694 | "inputFields": null, 1695 | "interfaces": [], 1696 | "kind": "OBJECT", 1697 | "name": "ChangeTodoStatusPayload", 1698 | "possibleTypes": null 1699 | }, 1700 | { 1701 | "description": null, 1702 | "enumValues": null, 1703 | "fields": null, 1704 | "inputFields": null, 1705 | "interfaces": null, 1706 | "kind": "SCALAR", 1707 | "name": "Int", 1708 | "possibleTypes": null 1709 | }, 1710 | { 1711 | "description": null, 1712 | "enumValues": null, 1713 | "fields": [ 1714 | { 1715 | "args": [], 1716 | "deprecationReason": null, 1717 | "description": null, 1718 | "isDeprecated": false, 1719 | "name": "viewer", 1720 | "type": { 1721 | "kind": "OBJECT", 1722 | "name": "User", 1723 | "ofType": null 1724 | } 1725 | }, 1726 | { 1727 | "args": [], 1728 | "deprecationReason": null, 1729 | "description": null, 1730 | "isDeprecated": false, 1731 | "name": "clientMutationId", 1732 | "type": { 1733 | "kind": "NON_NULL", 1734 | "name": null, 1735 | "ofType": { 1736 | "kind": "SCALAR", 1737 | "name": "String", 1738 | "ofType": null 1739 | } 1740 | } 1741 | }, 1742 | { 1743 | "args": [], 1744 | "deprecationReason": null, 1745 | "description": null, 1746 | "isDeprecated": false, 1747 | "name": "deletedTodoIds", 1748 | "type": { 1749 | "kind": "LIST", 1750 | "name": null, 1751 | "ofType": { 1752 | "kind": "SCALAR", 1753 | "name": "String", 1754 | "ofType": null 1755 | } 1756 | } 1757 | } 1758 | ], 1759 | "inputFields": null, 1760 | "interfaces": [], 1761 | "kind": "OBJECT", 1762 | "name": "RemoveCompletedTodosPayload", 1763 | "possibleTypes": null 1764 | }, 1765 | { 1766 | "description": null, 1767 | "enumValues": null, 1768 | "fields": [ 1769 | { 1770 | "args": [], 1771 | "deprecationReason": null, 1772 | "description": null, 1773 | "isDeprecated": false, 1774 | "name": "todo", 1775 | "type": { 1776 | "kind": "OBJECT", 1777 | "name": "Todo", 1778 | "ofType": null 1779 | } 1780 | }, 1781 | { 1782 | "args": [], 1783 | "deprecationReason": null, 1784 | "description": null, 1785 | "isDeprecated": false, 1786 | "name": "viewer", 1787 | "type": { 1788 | "kind": "OBJECT", 1789 | "name": "User", 1790 | "ofType": null 1791 | } 1792 | }, 1793 | { 1794 | "args": [], 1795 | "deprecationReason": null, 1796 | "description": null, 1797 | "isDeprecated": false, 1798 | "name": "clientMutationId", 1799 | "type": { 1800 | "kind": "NON_NULL", 1801 | "name": null, 1802 | "ofType": { 1803 | "kind": "SCALAR", 1804 | "name": "String", 1805 | "ofType": null 1806 | } 1807 | } 1808 | } 1809 | ], 1810 | "inputFields": null, 1811 | "interfaces": [], 1812 | "kind": "OBJECT", 1813 | "name": "RenameTodoPayload", 1814 | "possibleTypes": null 1815 | }, 1816 | { 1817 | "description": null, 1818 | "enumValues": null, 1819 | "fields": [ 1820 | { 1821 | "args": [], 1822 | "deprecationReason": null, 1823 | "description": null, 1824 | "isDeprecated": false, 1825 | "name": "todoEdge", 1826 | "type": { 1827 | "kind": "OBJECT", 1828 | "name": "TodoEdge", 1829 | "ofType": null 1830 | } 1831 | }, 1832 | { 1833 | "args": [], 1834 | "deprecationReason": null, 1835 | "description": null, 1836 | "isDeprecated": false, 1837 | "name": "viewer", 1838 | "type": { 1839 | "kind": "OBJECT", 1840 | "name": "User", 1841 | "ofType": null 1842 | } 1843 | }, 1844 | { 1845 | "args": [], 1846 | "deprecationReason": null, 1847 | "description": null, 1848 | "isDeprecated": false, 1849 | "name": "clientMutationId", 1850 | "type": { 1851 | "kind": "NON_NULL", 1852 | "name": null, 1853 | "ofType": { 1854 | "kind": "SCALAR", 1855 | "name": "String", 1856 | "ofType": null 1857 | } 1858 | } 1859 | } 1860 | ], 1861 | "inputFields": null, 1862 | "interfaces": [], 1863 | "kind": "OBJECT", 1864 | "name": "AddTodoPayload", 1865 | "possibleTypes": null 1866 | }, 1867 | { 1868 | "description": "An object with an ID", 1869 | "enumValues": null, 1870 | "fields": [ 1871 | { 1872 | "args": [], 1873 | "deprecationReason": null, 1874 | "description": "The id of the object", 1875 | "isDeprecated": false, 1876 | "name": "id", 1877 | "type": { 1878 | "kind": "NON_NULL", 1879 | "name": null, 1880 | "ofType": { 1881 | "kind": "SCALAR", 1882 | "name": "ID", 1883 | "ofType": null 1884 | } 1885 | } 1886 | } 1887 | ], 1888 | "inputFields": null, 1889 | "interfaces": null, 1890 | "kind": "INTERFACE", 1891 | "name": "Node", 1892 | "possibleTypes": [ 1893 | { 1894 | "kind": "OBJECT", 1895 | "name": "Todo", 1896 | "ofType": null 1897 | }, 1898 | { 1899 | "kind": "OBJECT", 1900 | "name": "User", 1901 | "ofType": null 1902 | } 1903 | ] 1904 | }, 1905 | { 1906 | "description": null, 1907 | "enumValues": null, 1908 | "fields": [ 1909 | { 1910 | "args": [], 1911 | "deprecationReason": null, 1912 | "description": null, 1913 | "isDeprecated": false, 1914 | "name": "deletedTodoId", 1915 | "type": { 1916 | "kind": "SCALAR", 1917 | "name": "String", 1918 | "ofType": null 1919 | } 1920 | }, 1921 | { 1922 | "args": [], 1923 | "deprecationReason": null, 1924 | "description": null, 1925 | "isDeprecated": false, 1926 | "name": "viewer", 1927 | "type": { 1928 | "kind": "OBJECT", 1929 | "name": "User", 1930 | "ofType": null 1931 | } 1932 | }, 1933 | { 1934 | "args": [], 1935 | "deprecationReason": null, 1936 | "description": null, 1937 | "isDeprecated": false, 1938 | "name": "clientMutationId", 1939 | "type": { 1940 | "kind": "NON_NULL", 1941 | "name": null, 1942 | "ofType": { 1943 | "kind": "SCALAR", 1944 | "name": "String", 1945 | "ofType": null 1946 | } 1947 | } 1948 | } 1949 | ], 1950 | "inputFields": null, 1951 | "interfaces": [], 1952 | "kind": "OBJECT", 1953 | "name": "RemoveTodoPayload", 1954 | "possibleTypes": null 1955 | } 1956 | ] 1957 | } 1958 | } 1959 | } --------------------------------------------------------------------------------